gitlab-backup2s3: Encrypted GitLab Backups to S3, with Kubernetes Support

Source code: github.com/sgaunet/gitlab-backup2s3 Helm chart: github.com/sgaunet/helm-gitlab-backup2s3

In the previous article, I covered gitlab-backup, a CLI tool for exporting GitLab projects and groups as portable archives. It handles the export, the restore, and supports both local and S3 storage natively.

But for production backup workflows — especially in a Kubernetes environment — you often want more: scheduled execution, optional encryption at rest, and a container image that bundles everything together. That’s exactly what gitlab-backup2s3 brings to the table.


What Is gitlab-backup2s3?

gitlab-backup2s3 is a Docker image that wraps gitlab-backup and adds two things:

  • Optional AES-GCM encryption of archives before upload, powered by gocrypt
  • First-class Kubernetes deployment via raw manifests or a Helm chart

The typical workflow it implements is:

GitLab Export API → tar.gz archive → (optional: gocrypt encrypt) → S3

If you just need gitlab-backup without encryption or container scheduling, the base tool is sufficient. gitlab-backup2s3 targets the use case where backups are automated, run in a cluster, and need to be stored securely in object storage.


A Note on Encryption Compatibility

⚠️ gocrypt v2 introduced AES GCM (Galois/Counter Mode) encryption, which is not backwards compatible with v1. Archives encrypted with v1 cannot be decrypted with v2 and vice versa. gitlab-backup2s3 v2 uses gocrypt v2. If you have archives encrypted with v1, decrypt them first with the v1 tooling before upgrading.


Configuration

gitlab-backup2s3 is configured entirely through environment variables. There is no configuration file — this is intentional for container-native deployments where secrets and settings come from the orchestrator.

VariableDescriptionDefault
GITLAB_TOKENGitLab personal access token(required)
GITALB_URIGitLab base URLhttps://gitlab.com
GITLABPROJECTIDID of a single project to export
GITLABGROUPIDID of a group to export (all sub-projects)
GOCRYPT_KEYEncryption key for AES-GCM encryption(omit to skip encryption)
POSTBACKUPPost-backup hook; set to gocrypt enc --i %INPUTFILE% to encrypt""
S3ENDPOINTS3 endpoint URL (e.g. https://s3.eu-west-3.amazonaws.com)
S3REGIONS3 region
S3BUCKETNAMEDestination S3 bucket
S3BUCKETPATHPath prefix inside the bucket
AWS_ACCESS_KEY_IDAWS access key(optional if using IAM role)
AWS_SECRET_ACCESS_KEYAWS secret key(optional if using IAM role)
LOCALPATHExport archives locally instead of S3""
EXPORT_TIMEOUT_MINPer-project export timeout in minutes10
TMPDIRTemporary working directory/tmp
DEBUGLEVELLog level: debug, info, warn, errorinfo

On AWS credentials: if you’re running in EKS with an IAM role associated to the pod’s service account (IRSA), or on an EC2 instance with an instance profile, you don’t need to supply AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY at all — the SDK will pick up credentials automatically from the instance metadata service.

To enable encryption, set both GOCRYPT_KEY (the AES key) and POSTBACKUP to the gocrypt invocation:

GOCRYPT_KEY="your-secret-key"
POSTBACKUP="gocrypt enc --i %INPUTFILE%"

Deploying on Kubernetes

Raw Manifests

The repository includes ready-to-use Kubernetes manifests in the deploy/k8s folder. These define a CronJob resource that runs gitlab-backup2s3 on a schedule, with environment variables populated from a Secret.

This is the quickest path if you want to stay close to the metal without introducing Helm.

Helm Chart

For teams using Helm, a dedicated chart is available at github.com/sgaunet/helm-gitlab-backup2s3. It wraps the same CronJob manifest with full values.yaml configurability.

Key chart parameters:

KeyDescriptionDefault
scheduleCronJob schedule expression00 01 1 * * (monthly)
concurrencyPolicyWhether to allow concurrent job runsForbid
backoffLimitNumber of retries on failure2
failedJobsHistoryLimitHow many failed job pods to retain10
successfulJobsHistoryLimitHow many successful job pods to retain3
image.repositoryContainer imageghcr.io/sgaunet/gitlab-backup2s3
serviceAccount.createCreate a dedicated service accounttrue
resources.limits.memoryMemory limit128Mi
configuration.*All environment variables above(see values.yaml)

The additionalEnvFrom field lets you inject additional ConfigMap or Secret references into the pod, which is useful for supplying AWS credentials or other secrets managed externally (e.g. via Vault or External Secrets Operator).

A minimal values.yaml for a group backup to AWS S3 might look like:

schedule: "0 2 * * *"   # every night at 2am

configuration:
  GITLAB_TOKEN: "glpat-xxxxx"
  GITLABGROUPID: "123"
  S3ENDPOINT: "https://s3.eu-west-3.amazonaws.com"
  S3REGION: "eu-west-3"
  S3BUCKETNAME: "my-gitlab-backups"
  S3BUCKETPATH: "daily"
  EXPORT_TIMEOUT_MIN: "20"
  GOCRYPT_KEY: "my-aes-key"
  POSTBACKUP: "gocrypt enc --i %INPUTFILE%"

serviceAccount:
  annotations:
    eks.amazonaws.com/role-arn: "arn:aws:iam::123456789:role/gitlab-backup-role"

This will schedule a nightly backup of all projects in group 123, encrypt each archive with AES-GCM, and upload them to S3 — without any static AWS credentials in the pod, thanks to IRSA.


Summary

gitlab-backup handles the export and restore mechanics. gitlab-backup2s3 takes it further for production environments: it adds encryption, packages everything into a container image, and ships with Kubernetes deployment support out of the box.

The full toolchain for a secure, automated GitLab backup pipeline therefore looks like this:

gitlab-backup2s3 (CronJob)
  └── gitlab-backup    export via GitLab API
  └── gocrypt          AES-GCM encryption (optional)
  └── S3               durable remote storage

Both projects are available on GitHub and distributed under open-source licenses. Releases are published on GitHub and the container image is available at ghcr.io/sgaunet/gitlab-backup2s3.