Skip to content

Instantly share code, notes, and snippets.

@pawgajda-drs
Last active December 12, 2025 15:01
Show Gist options
  • Select an option

  • Save pawgajda-drs/c7ce652cbebb527b99075758c8896526 to your computer and use it in GitHub Desktop.

Select an option

Save pawgajda-drs/c7ce652cbebb527b99075758c8896526 to your computer and use it in GitHub Desktop.
How to backup GitHub Repositories to Azure Blob Storage using Kubernetes (AKS) CronJobs
FROM debian:bookworm-slim
RUN apt update && apt upgrade -y \
&& apt install -y apt-transport-https ca-certificates curl gnupg lsb-release wget git \
# install Github CLI
&& out=$(mktemp) && wget -nv -O$out https://cli.github.com/packages/githubcli-archive-keyring.gpg \
&& cat $out | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \
&& chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& mkdir -p -m 755 /etc/apt/sources.list.d \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list \
&& apt update \
&& apt install -y gh \
# install Azure CLI
&& mkdir -p /etc/apt/keyrings \
&& curl -sLS https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | tee /etc/apt/keyrings/microsoft.gpg > /dev/null \
&& chmod go+r /etc/apt/keyrings/microsoft.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/microsoft.gpg] https://packages.microsoft.com/repos/azure-cli $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/azure-cli.list \
&& apt-get update \
&& apt-get install -y azure-cli \
# clean up
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY ./github-backup.sh /usr/local/bin/github-backup.sh
RUN chmod +x /usr/local/bin/github-backup.sh && ls -l /usr/local/bin/github-backup.sh
ENTRYPOINT ["/usr/local/bin/github-backup.sh"]
name: Build and Push Docker Image
on:
push:
branches:
- "main"
- "master"
tags:
- "v*"
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Get Docker Image Metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ github.repository }}
tags: |
type=sha
type=ref,event=tag
type=raw,value=latest,enable={{ is_default_branch }}
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push Docker Image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
#!/usr/bin/env bash
# Check for required Environment Variables
REQUIRED_ENV_VARS=("AZURE_STORAGE_ACCOUNT" "BLOB_STORAGE_CONTAINER" "GITHUB_ORG" "GITHUB_TOKEN")
for VAR in ${REQUIRED_ENV_VARS}
do
if [ -z ${VAR} ]; then
echo "[ERROR] Variable ${VAR} not set!"
exit 1
fi
done
# Check for Blob Storage Access Tokens
if [ -n ${AZURE_STORAGE_SAS_TOKEN} ] || [ -n ${AZURE_STORAGE_KEY} ]; then
echo "[INFO] Azure Storage Account Credentials are set!"
else
echo "[ERROR] Credentials for Azure Blob Storage not set!"
echo "[ERROR] AZURE_STORAGE_SAS_TOKEN or AZURE_STORAGE_KEY environment variable not set!"
exit 1
fi
set -u
# Internal Script Settings
GITMIRROR_DIR="/backups/git"
GITBUNDLE_DIR="/backups/gitbundles"
BACKUP_DIR_PREFIX="backup"
BACKUP_DATE=$(date +"%Y-%m-%d_%H-%M-%S%Z")
# Fetch list of git repositories
GITHUB_REPOS=$(gh repo list ${GITHUB_ORG} --json url --jq '.[].url')
# Clone Repositories
mkdir -p ${GITMIRROR_DIR} && cd ${GITMIRROR_DIR}
for REPO in ${GITHUB_REPOS}
do
echo "[INFO] Mirroring ${REPO}"
git clone --mirror https://github:${GITHUB_TOKEN}@${REPO#https://}
done
# Create Git Bundles
mkdir -p ${GITBUNDLE_DIR}
for REPODIR in ${GITMIRROR_DIR}/*.git
do
echo "[INFO] Creating Git Bundle for ${REPODIR}"
# generate proper name for git bundle
BUNDLE_NAME=${REPODIR#${GITMIRROR_DIR}/}
BUNDLE_NAME=${BUNDLE_NAME%.git}
# create git bundle
cd ${REPODIR}
git bundle create "${GITBUNDLE_DIR}/${BUNDLE_NAME}.bundle" --all
done
# change CWD
cd /
# Upload Bundles
echo "[INFO] Uploading Git Bundles to ${AZURE_STORAGE_ACCOUNT}/${BLOB_STORAGE_CONTAINER}/${BACKUP_DIR_PREFIX}_${BACKUP_DATE}"
az storage blob upload-batch \
--account-name ${AZURE_STORAGE_ACCOUNT} \
--destination ${BLOB_STORAGE_CONTAINER} \
--destination-path "${BACKUP_DIR_PREFIX}_${BACKUP_DATE}" \
--source ${GITBUNDLE_DIR}
# Clean Up
echo "[INFO] Clean up"
rm -rf ${GITMIRROR_DIR}
rm -rf ${GITBUNDLE_DIR}
apiVersion: v1
kind: ConfigMap
metadata:
name: infrastructure-job-cm
data:
AZURE_STORAGE_ACCOUNT: __AZURE_STORAGE_ACCOUNT__
BLOB_STORAGE_CONTAINER: github-infrastructure-backups
GITHUB_ORG: __GITHUB_ORG_OR_USERNAME__
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: github-backups-secret
spec:
secretStoreRef:
name: secret-store-keyvault
kind: SecretStore
target:
name: github-backups-secret
template:
data:
GITHUB_TOKEN: "{{ .githubToken }}"
AZURE_STORAGE_KEY: "{{ .azureBlobSecret }}"
data:
- secretKey: githubToken
remoteRef:
key: github-backups-token
- secretKey: azureBlobSecret
remoteRef:
key: github-backups-azure-storage-key
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: github-backup-infrastructure
spec:
schedule: "30 4 * * *"
failedJobsHistoryLimit: 14
successfulJobsHistoryLimit: 14
jobTemplate:
spec:
template:
spec:
nodeSelector:
agentpool: workernp1
kubernetes.azure.com/mode: user
imagePullSecrets:
- name: github-registry
containers:
- name: github-backup
image: __DOCKER_IMAGE_REGISTRY__/github-backup-job:latest
imagePullPolicy: IfNotPresent
envFrom:
- configMapRef:
name: infrastructure-job-cm
- secretRef:
name: github-backups-secret
volumeMounts:
- name: temp-storage
mountPath: /backups
restartPolicy: OnFailure
volumes:
- name: temp-storage
emptyDir: {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment