Bacula GitHub Cloud
Complete GitHub backup through Bacula — code history, wikis, LFS, releases, metadata, gists, and package registries protected on your own infrastructure.
What it is
GitHub backup that goes beyond git clone
Bacula GitHub Cloud is a production-grade Rust orchestrator that plugs into your existing Bacula deployment via standard ClientRunBeforeJob hooks — no custom patches, no extra daemons, no schema changes.
Before each Bacula backup run, it mirrors every repository in your configured GitHub organisations to a local staging directory. Bacula’s FileDaemon then picks up the staged data and writes it to tape or long-term storage, exactly as it would any other FileSet.
What sets it apart is coverage. A plain git clone preserves code history. Bacula GitHub Cloud also captures wikis, Git LFS objects, release binaries, issues, pull requests, comments, labels, milestones, Actions workflows and run logs, Projects v2 boards, organisation metadata, security scanning alerts, gists, and package registry content — everything GitHub exposes through its API, stored alongside the repository mirror.
# /etc/github-backup/config.yaml github: auth: token_env: GITHUB_BACKUP_TOKEN targets: - type: organization name: my-org backup_wiki: true backup_lfs: true backup_releases: true backup_actions: true backup_packages_content: false - type: user name: my-github-username backup_gists: true # last_run.json — written after each run { "repos_total": 87, "repos_failed": 0, "api_calls_total": 4320, "duration_seconds": 1842.7, "staging_size_bytes": 42949672960 }
Why use it
GitHub is not a backup
Repositories can be deleted, corrupted, or made inaccessible in seconds. Bacula GitHub Cloud brings your GitHub data under the same retention policies, access controls, and disaster recovery guarantees as your on-premises infrastructure.
More than code history
A plain git mirror misses wikis, Git LFS objects, releases, issues, pull requests, Actions workflows, Projects v2 boards, gists, package registries, and organisation metadata. Bacula GitHub Cloud captures all of it in one run.
No Bacula patches required
Integrates via standard ClientRunBeforeJob and ClientRunAfterJob hooks. Works with Bacula Community 9+ and Bacula Enterprise 9+. Nothing to patch, fork, or maintain.
Incremental from the second run
HTTP ETags and since= timestamps eliminate redundant API calls. Only genuinely changed data is re-fetched, even across repositories with thousands of issues and comments.
Rate-limit safe
All parallel workers share an async-safe request budget. When remaining allowance drops to a configurable threshold, every task pauses and resumes automatically after GitHub’s reset window — staying safely inside the 5,000 req/hr limit.
Resumable disaster recovery
The restore binary records every completed step in SQLite. An interrupted restore picks up exactly where it stopped — no risk of duplicate issues or half-pushed repositories on retry.
Built-in observability
Prometheus metrics written after every run, structured JSON logging, a per-run summary in last_run.json, and optional Slack or PagerDuty failure alerts.
Coverage
What gets backed up
Everything GitHub exposes through its API, captured and written to a structured staging directory before Bacula’s FileDaemon runs. The restore binary pushes it all back to GitHub automatically.
Backed up & restorable
- Git repository history — full bare mirror, all branches and tags
- Wikis — separate bare mirror per repository
- Git LFS objects — content-addressed, only missing objects downloaded
- Releases and binary assets — streamed, skips already-downloaded files
- Issues, comments, labels, and milestones — JSON, incremental via
since= - Pull request metadata, review comments, and approval records
- GitHub Actions workflows, run history, and per-job logs
- Actions secrets (names), variables (values), and artifact metadata
- Environments and per-environment variable values
- Code scanning, Dependabot, and secret scanning alerts
- Repository traffic (views & clones, 14-day rolling window)
- Discussions, discussion comments, and replies
- Branch protection rules and rulesets
- Collaborators and repository custom properties
- Projects v2 boards and columns — via GraphQL
- Organisation metadata — members, teams, webhooks, secrets, variables, org roles, outside collaborators
- Gists — bare git mirror +
metadata.json+comments.json(user targets, opt-in) - Package registry binaries — OCI layers, npm, Maven, RubyGems, NuGet (org targets, opt-in)
Backed up as reference only
- Actions secrets — the GitHub API never returns secret values; stored as placeholder JSON with names only
- Webhooks — saved as JSON reference; must be manually recreated on restore to avoid unintended triggers
- Deploy keys — private key material never accessible via API
- Pull requests on restore — API limitation; PRs require live source and target branches and cannot be recreated verbatim
Use cases
Built for teams that treat source code as infrastructure
If you already run Bacula to protect servers, databases, and storage, your GitHub repositories should live under the same policy.
SOC 2 & ISO 27001 retention
Place GitHub data under the same retention schedules, volume encryption, and audit trails as the rest of your Bacula volumes — without writing custom tooling.
Full organisation restore
Select a point-in-time from bconsole and the restore binary automatically pushes code, wikis, LFS objects, releases, labels, milestones, and issues back to GitHub.
Unified backup policy
Back up any mix of GitHub organisations and personal accounts from a single config file and a single Bacula job. No per-organisation tooling or separate schedules.
Air-gapped & offline storage
Bacula’s PKI Data Encryption encrypts volumes at rest. Pair with tape storage for a fully air-gapped GitHub archive that satisfies strict data sovereignty requirements.
Hundreds of repositories
Configurable parallelism (1–64 workers) with automatic rate-limit management. Scales from a handful of private repos to organisations with hundreds of active repositories.
Prometheus & alerting integration
Metrics written after every run for ingestion via Node Exporter’s textfile collector. Slack and PagerDuty failure alerts. Drop into your existing monitoring stack with no extra configuration.
Features
Everything included, nothing bolted on
Two self-contained static binaries. No runtime dependencies beyond git and git-lfs. SQLite is bundled.
Full git mirror
Bare --mirror clone on first run; incremental git remote update --prune on every subsequent run.
Wiki mirror
Separate bare mirror for each repository’s wiki. Gracefully skips repositories with wikis disabled.
Git LFS support
Content-addressed LFS object store. Only missing objects are downloaded; existing objects are never re-fetched.
Release assets
Binary assets streamed to local storage. Skips already-downloaded files by comparing remote and local sizes before downloading.
Full metadata export
Issues, pull requests, comments, labels, milestones, Actions workflows, Projects v2 boards, environments, branch protection rules, rulesets, collaborators, discussions, and traffic exported as structured JSON.
Security alert export
Code scanning, Dependabot, and secret scanning alerts exported per repository alongside the rest of the metadata.
Organisation metadata
Members, teams, webhooks, secrets (names), variables, packages, org roles, and outside collaborators captured as JSON alongside repository data in the staging directory.
Gist mirror
Bare git mirror of each user gist plus metadata.json and comments.json. Opt-in per user target with backup_gists: true.
Package registry backup
OCI image layers (ghcr.io), npm tarballs, Maven JARs, RubyGems, and NuGet packages downloaded and stored under packages/content/. Opt-in per org target with backup_packages_content: true.
Mixed public/private auth
Per-repo auto-detection — the token is sent only to private repositories. Public repos in the same organisation are cloned without credentials, and targets can be set to use_token: false for fully public sources.
Incremental sync
HTTP ETags and since= timestamps stored in a bundled SQLite database. Changed data only, from the second backup run onwards.
Resumable restore
A separate SQLite step-tracker records every completed restore action. Interrupted restores pick up exactly where they stopped without duplicating work.
Prometheus metrics
Repos processed, failed, API calls made, rate-limit pauses, run duration, and staging directory size — written to metrics.prom after every run.
Slack & PagerDuty alerts
Set GITHUB_BACKUP_SLACK_WEBHOOK or GITHUB_BACKUP_PAGERDUTY_KEY to receive failure notifications automatically.
Graceful shutdown
SIGTERM and SIGINT are caught at the orchestrator level — in-progress repositories finish cleanly before the process exits, so no partial staging data is left behind.
Service account isolation
Dedicated bacula-github user with no login shell, no sudo access. Staging directory created at mode 0o750; state database at 0o700.
Security-hardened
Token redaction in all log output, HTTPS-only clone enforcement, strict repository name allowlist, config file permission check on startup.
Quick start
Up in three steps
Requires git 2.40+ and git-lfs 3.0+ on the host running the Bacula FileDaemon. Works with Bacula 9+ (Community or Enterprise).
Install
Download the pre-built binary tarball from the release page, verify the GPG signature, and install the two binaries.
V=v0.10.0
OS=linux-x86_64
AR=bacula-ghe-cloud-${V}-${OS}.tar.gz
# Verify checksum + GPG signature
sha256sum -c ${AR}.sha256
gpg --verify ${AR}.asc ${AR}
# Extract and install
tar -xzf ${AR}
DIR=bacula-ghe-cloud-${V}
sudo cp ${DIR}/github-backup \
${DIR}/github-restore \
/usr/local/bin/
Configure
Copy the example config, lock down permissions, and store your GitHub PAT.
sudo mkdir -p /etc/github-backup
sudo cp config.yaml.example \
/etc/github-backup/config.yaml
sudo chmod 0600 \
/etc/github-backup/config.yaml
# Store PAT — token file must be 0600
echo "ghp_..." | sudo tee \
/etc/github-backup/token
sudo chmod 0600 \
/etc/github-backup/token
Wire into Bacula
Add the hook to your Bacula Director job definition and run a test backup from bconsole.
Job {
Name = "GitHubBackup"
Client = backup-client-fd
Client Run Before Job =
"github-backup \
--config \
/etc/github-backup/config.yaml"
FileSet = "GitHubStaging"
Schedule = "WeeklyCycle"
Pool = GitHubPool
}
# bconsole> run job=GitHubBackup
Protect your source code with the infrastructure you already run.
GitHub repositories, wikis, releases, gists, and metadata — backed up by Bacula, retained on your terms, restorable on demand.