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 |
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:
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_tokenIf 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
Tested on Windows 10, macOS, and Linux (amd64). Should also work on Linux (386, arm, arm64), FreeBSD, NetBSD, and OpenBSD.
Changelog
See CHANGELOG.md for release history.
