soba
August 31, 2026 · View on GitHub
Automated, encrypted backups for your Git repositories
soba backs up your Git repositories from GitHub, GitLab, Bitbucket, Azure DevOps, Gitea, and Sourcehut. Each repository is saved as a single-file git bundle, and only new bundles are stored when changes are detected. Bundles can optionally be encrypted with age for secure offsite storage.
Features
| Multi-provider | GitHub, GitLab, Bitbucket, Azure DevOps, Gitea, Sourcehut |
| Efficient storage | Git bundles with change detection — unchanged repos are skipped |
| Encryption | Optional age encryption for bundles, manifests, and LFS archives |
| Built-in scheduler | Interval (24h, 45m) or cron (0 3 * * *) scheduling |
| Smart rotation | Keep only the n most recent backups per repo |
| Git LFS | Back up large file storage objects alongside repo bundles |
| Notifications | Slack, Telegram, webhooks, and ntfy alerts |
| Runs anywhere | Binary, Docker, Kubernetes, or Synology NAS — amd64 and arm64 |
Quick Start
Create git bundles of all repositories in your GitHub account:
mkdir soba-backups
docker run --rm \
-v ./soba-backups:/backups \
-e GIT_BACKUP_DIR=/backups \
-e GITHUB_TOKEN=<your-token> \
ghcr.io/jonhadfield/soba
Installation
Binary
Download the latest release from the releases page, then:
install <soba binary> /usr/local/bin/soba
Set GIT_BACKUP_DIR and your provider credentials, then run:
soba
Docker
docker run --rm -t \
-v /path/to/backups:/backup \
-e GIT_BACKUP_DIR=/backup \
-e GITHUB_TOKEN=$GITHUB_TOKEN \
-e GITLAB_TOKEN=$GITLAB_TOKEN \
ghcr.io/jonhadfield/soba
Kubernetes
Deploy soba as a CronJob. See the Kubernetes guide for manifests and instructions.
Synology NAS
Run soba via the Docker GUI on your NAS. See the Synology guide for step-by-step instructions.
Supported Providers
| Provider | Token Docs | Key Variables |
|---|---|---|
| GitHub | Create token | GITHUB_TOKEN |
| GitLab | Create token | GITLAB_TOKEN |
| Bitbucket | API tokens / OAuth2 | BITBUCKET_EMAIL + BITBUCKET_API_TOKEN |
| Azure DevOps | Create PAT | AZURE_DEVOPS_USERNAME + AZURE_DEVOPS_PAT + AZURE_DEVOPS_ORGS |
| Gitea | Create token | GITEA_APIURL + GITEA_TOKEN |
| Sourcehut | Create PAT | SOURCEHUT_PAT |
For full provider configuration, organisation filtering, comparison modes, and self-hosted endpoints, see the provider documentation.
Configuration
All configuration is via environment variables. Set GIT_BACKUP_DIR for the backup destination and add credentials for each provider you want to back up.
export GIT_BACKUP_DIR="/repo-backups/"
export GITHUB_TOKEN="ghp_..."
Secrets can also be loaded from files using the _FILE suffix:
export GITHUB_TOKEN_FILE=/run/secrets/github_token
If both the variable and _FILE version are set, the variable takes precedence.
Scheduling
soba includes a built-in scheduler so it can run continuously. Set an interval or cron expression:
# Run every 24 hours
export GIT_BACKUP_INTERVAL=24h
# Run every 45 minutes
export GIT_BACKUP_INTERVAL=45m
# Run daily at 3am (cron syntax)
export GIT_BACKUP_CRON='0 3 * * *'
soba can also be triggered by external schedulers like cron or systemd. See the logging and persistence guide for cron examples.
Backup Rotation
Keep only the n most recent backups per provider by setting the relevant variable:
export GITHUB_BACKUPS=7
export GITLAB_BACKUPS=7
export BITBUCKET_BACKUPS=7
export GITEA_BACKUPS=7
export AZURE_DEVOPS_BACKUPS=7
export SOURCEHUT_BACKUPS=7
Git LFS
To include Git LFS objects in your backups, enable it per provider:
export GITHUB_BACKUP_LFS=yes
export GITLAB_BACKUP_LFS=yes
LFS content is stored in a *.lfs.tar.gz file alongside the repository bundle. The Docker image includes git-lfs.
Encryption
Encrypt bundles, manifests, and LFS archives with age encryption by setting a passphrase:
export BUNDLE_PASSPHRASE="your-secure-passphrase"
When enabled:
- Bundles are saved as
.bundle.age - Manifests are saved as
.manifest.age - LFS archives are saved as
.lfs.tar.gz.age
Store the passphrase securely — without it, your backups cannot be decrypted.
Decrypting backups
Install the age CLI, then:
age -d -o repo.bundle repo.bundle.age
You'll be prompted for the passphrase. For batch decryption:
#!/bin/bash
read -s -p "Enter passphrase: " PASSPHRASE
echo
for file in *.age; do
output="${file%.age}"
echo "Decrypting $file to $output..."
echo "$PASSPHRASE" | age -d -o "$output" "$file"
done
Notifications
Get notified when backups complete or fail. To reduce noise on scheduled runs, send notifications only on failure:
export SOBA_NOTIFY_ON_FAILURE_ONLY=true
| Channel | Variables |
|---|---|
| Slack | SLACK_CHANNEL_ID, SLACK_API_TOKEN |
| Telegram | SOBA_TELEGRAM_BOT_TOKEN, SOBA_TELEGRAM_CHAT_ID |
| Webhooks | SOBA_WEBHOOK_URL, SOBA_WEBHOOK_FORMAT (long or short) |
| ntfy | SOBA_NTFY_URL |
Webhook payload examples: long format, short format.
Restoring Backups
A git bundle is a portable archive of a repository. Clone it like any remote:
git clone soba.20180708153107.bundle my-repo
cd my-repo
git remote set-url origin <original-repo-url>
If the bundle is encrypted, decrypt it first.
Supported Platforms
Binaries are published for every combination below on each release:
| OS | amd64 | arm64 |
|---|---|---|
| Linux | ✅ | ✅ |
| macOS | ✅ | ✅ |
| Windows | ✅ | ✅ |
| FreeBSD | ✅ | ✅ |
The Docker image is multi-architecture, so ghcr.io/jonhadfield/soba resolves to the right build automatically and runs natively on Apple Silicon, AWS Graviton, and Raspberry Pi:
docker buildx imagetools inspect ghcr.io/jonhadfield/soba:latest
soba is pure Go with cgo disabled, so it should also build from source for any other target Go supports.
Changelog
See CHANGELOG.md for release history.