Last active
December 12, 2025 15:01
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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