Skip to content

Zero to a verified, immutable backup of one repo, in about 10 minutes.

gitdr is a single static Linux binary that runs as a one-shot job. On macOS, run the container (ghcr.io/gitdr-io/gitdr).

Terminal window
# container
docker run --rm ghcr.io/gitdr-io/gitdr version
# or via Go (1.26+)
go install gitdr.io/gitdr/cmd/gitdr@latest

Static binaries with checksums and cosign signatures are also on the releases page.

gitdr strongly recommends an immutable destination. It warns if the bucket isn’t WORM, and --require-worm makes it fail closed. Immutability is the whole point of a ransomware-resistant backup, so set one up. Pick your provider.

AWS S3, Object Lock (only settable at creation):

Terminal window
aws s3api create-bucket --bucket my-worm-bucket --region us-east-1 \
--object-lock-enabled-for-bucket
aws s3api put-object-lock-configuration --bucket my-worm-bucket \
--object-lock-configuration \
'{"ObjectLockEnabled":"Enabled","Rule":{"DefaultRetention":{"Mode":"COMPLIANCE","Days":30}}}'

Google Cloud Storage, locked retention policy (the lock is irreversible):

Terminal window
gcloud storage buckets create gs://my-worm-bucket --location=US \
--uniform-bucket-level-access --public-access-prevention --retention-period=30d
gcloud storage buckets update gs://my-worm-bucket --lock-retention-period

Azure Blob, create a storage account with version-level immutability plus blob versioning, then a container (Azure docs).

Scope the credential gitdr uses to create/put only. It never needs delete.

  • GitHub. Create a GitHub App with read-only repo and metadata, install it, note the App ID and Installation ID, and download the private key (PEM).
  • GitLab. A project or group access token with read_api and read_repository.

Every run writes an ed25519-signed manifest. Make the keypair once.

Terminal window
openssl genpkey -algorithm ed25519 -out manifest-signing.pem
openssl pkey -in manifest-signing.pem -pubout -out manifest-public.pem

Keep manifest-signing.pem secret and off the runner if you can. verify only needs the public key.

Copy config.example.yaml to config.yaml and set the source and destination. A minimal GitHub to S3 setup:

source:
type: github
repo: "acme/api"
github: { appID: 123456, installationID: 7890123 }
destination:
type: s3
s3: { bucket: my-worm-bucket, region: us-east-1 }
retention: { mode: COMPLIANCE, days: 30 }
manifest:
publicKeyPath: ./manifest-public.pem
worm: { require: false } # warn on non-WORM and proceed. true means fail closed
Terminal window
export GITDR_GITHUB_APP_PRIVATE_KEY="$(cat github-app.pem)"
export GITDR_MANIFEST_SIGNING_KEY="$(cat manifest-signing.pem)"
export AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... # or use an instance role
Terminal window
gitdr doctor --config config.yaml # checks config, source auth, and the WORM lock
gitdr backup --config config.yaml # clone, bundle, sha256, immutable upload, signed manifest

doctor writes nothing. backup exits non-zero on any failure, so treat that as a failed backup. The run prints the manifest key.

Terminal window
gitdr verify --config config.yaml --manifest <manifest-key-from-step-7>

signature valid: true, artifacts N/N ok means the run is signed and intact. To prove a real restore, follow the restore runbook.

To keep the storage provider from reading your data, set encryption.enabled: true and give it a 32-byte key in GITDR_ENCRYPTION_KEY (64-char hex, base64, or raw). verify stays key-free. restore needs the key.