Backs up all repositories in a GitHub organisation to Azure Blob Storage. Runs as a weekly Kubernetes CronJob using Workload Identity — no credentials stored in the cluster.
For each repository in the org it:
- Clones a bare mirror (
git clone --mirror) - Compresses it to a
.tar.gz - Uploads it to Azure Blob Storage under
{date}/{org}/{repo}.tar.gz
A manifest-{N}.json is written alongside each run with a summary (repos backed up, skipped, failed). Blob retention is managed by an Azure Storage lifecycle policy.
# Install dependencies
uv sync
# Copy and fill in env vars
cp .env.example .env
# Dry run (no uploads)
uv run --env-file .env backup.pyThe GITHUB_TOKEN in .env bypasses Key Vault. Set DRY_RUN=true to skip uploads entirely.
Prerequisites: AKS cluster with OIDC issuer and Workload Identity enabled.
-
Create a User-Assigned Managed Identity and grant it:
Key Vault Secrets Useron the Key Vault holding the GitHub PATStorage Blob Data Contributoron the backup container
-
Federate the identity to the Kubernetes ServiceAccount:
az identity federated-credential create \ --name github-backup-federated \ --identity-resource-id <MANAGED_IDENTITY_RESOURCE_ID> \ --issuer <AKS_OIDC_ISSUER_URL> \ --subject system:serviceaccount:github-backup:github-backup-sa
-
Fill in the placeholders in
k8s/configmap.yamlandk8s/serviceaccount.yaml. -
Apply:
kubectl apply -f k8s/
The CronJob runs every Sunday at 18:00. If repos fail, re-running the job is safe — already-uploaded repos are skipped automatically.
Backups are stored as bare mirror tarballs at {date}/{org}/{repo}.tar.gz in Azure Blob Storage. Restoring means downloading the archive, extracting the bare clone, then pushing it to a (new or existing) GitHub repository.
- Open the Azure Portal or Azure Storage Explorer application and navigate to the storage account.
- Select Storage browser → Blob containers → github-backups.
- Browse to the desired date folder (e.g.
2025-06-01/<ORG>/). Each repo is stored as<REPO>.tar.gz. Amanifest.jsonin the date folder lists all repos backed up in that run. - Click the
<REPO>.tar.gzblob and select Download.
tar -xzf <REPO>.tar.gz
# Produces <REPO>.git — a bare mirror cloneClone the bare directory into a working copy and inspect it:
git clone <REPO>.git <REPO>
cd <REPO>
git log --oneline -10
git branch -aCreate a new (empty) repository on GitHub first, then push all refs from the cloned working copy:
cd <REPO>
git remote set-url origin https://github.com/<ORG>/<REPO>.git
# Push all branches, tags, and notes
git push --mirror originNote:
--mirrorrewrites the remote completely. If the repository already exists and has commits you want to keep, usegit push --allandgit push --tagsinstead to avoid overwriting diverged history.
Create a GitHub release with a v* tag. The Actions workflow builds and pushes the image to ghcr.io/equinor/github-org-backup:{version}.