# Legion backup — restic to Cloudflare R2 # Dumps all Postgres databases + Gitea data nightly at 2am # Retains 30 daily / 12 weekly / 6 monthly snapshots resource "kubernetes_secret" "backup_credentials" { metadata { name = "backup-credentials" namespace = "git" } data = { RESTIC_PASSWORD = "gitea-restic-2026" AWS_ACCESS_KEY_ID = var.r2_access_key_id AWS_SECRET_ACCESS_KEY = var.r2_secret_access_key RESTIC_REPOSITORY = "s3:${var.r2_endpoint}/legion-gitea-backup" POSTGRES_PASSWORD = var.postgres_password } } resource "kubernetes_cron_job_v1" "gitea_backup" { metadata { name = "gitea-backup" namespace = "git" } spec { schedule = "0 2 * * *" concurrency_policy = "Forbid" failed_jobs_history_limit = 3 successful_jobs_history_limit = 3 job_template { metadata {} spec { backoff_limit = 2 template { metadata {} spec { restart_policy = "OnFailure" # Dump all Postgres databases before restic runs init_container { name = "pg-dump" image = "postgres:18-alpine" command = ["/bin/sh", "-c", "pg_dumpall -h postgres-postgresql.platform.svc.cluster.local -U postgres --no-role-passwords > /dump/all-databases.sql && echo 'DB dump complete, size:' $(du -sh /dump/all-databases.sql)" ] env_from { secret_ref { name = kubernetes_secret.backup_credentials.metadata[0].name } } volume_mount { name = "dump" mount_path = "/dump" } resources { requests = { memory = "128Mi", cpu = "100m" } limits = { memory = "256Mi" } } } container { name = "backup" image = "restic/restic:latest" command = ["/bin/sh", "-c", <<-EOT set -e # Init repo if first run restic snapshots 2>/dev/null || restic init # Back up Gitea data + full DB dump echo "Running restic backup..." restic backup \ /gitea-data \ /dump/all-databases.sql \ --tag legion \ --host legion # Prune old snapshots restic forget \ --keep-daily 30 \ --keep-weekly 12 \ --keep-monthly 6 \ --prune echo "Backup complete." restic snapshots --latest 3 EOT ] env_from { secret_ref { name = kubernetes_secret.backup_credentials.metadata[0].name } } volume_mount { name = "gitea-data" mount_path = "/gitea-data" read_only = true } volume_mount { name = "dump" mount_path = "/dump" } resources { requests = { memory = "256Mi", cpu = "100m" } limits = { memory = "512Mi" } } } volume { name = "gitea-data" persistent_volume_claim { claim_name = "gitea-data" read_only = true } } volume { name = "dump" empty_dir {} } } } } } } }