Fail PostgreSQL backups before publishing incomplete dumps - #1233
Merged
Merged
Conversation
Contributor
Static Validation✅ YAML Lint: Passed Summary✅ Homepage Coverage: Passed Argo CD Diff PreviewSummary: Modified (1):
± base-configs (+17|-9)base-configs (bootstrap/appsets/config-apps.yaml)CronJob: databases/pg-backup - name: PGPASSWORD
valueFrom:
secretKeyRef:
key: postgres-password
name: postgres-admin-secret
- image: postgres:alpine
+ image: postgres:18.6-alpine@sha256:6c538e7206ea40ff740ef27883529390a690b6ead6ba96b44c67a9f7c638e8fd
name: pg-backup
volumeMounts:
- mountPath: /backup
name: backup-storage
- mountPath: /scriptsConfigMap: databases/pg-backup-script apiVersion: v1
data:
backup.sh: |
#!/bin/sh
- set -e
+ set -eu
+ umask 077
BACKUP_DIR=/backup
RETENTION_DAYS=7
TIMESTAMP=$(date -u +%Y-%m-%d_%H%M%S)
DUMP_FILE="${BACKUP_DIR}/pg_dumpall_${TIMESTAMP}.sql.gz"
echo "Starting PostgreSQL backup at ${TIMESTAMP}"
+ # Separate commands preserve producer failures on POSIX sh (no pipefail).
+ SQL_FILE="${DUMP_FILE}.sql.tmp"
+ trap 'rm -f "$SQL_FILE" "${DUMP_FILE}.tmp"' EXIT
+ trap 'exit 1' HUP INT TERM
+
echo "Running pg_dumpall..."
- if pg_dumpall --clean --if-exists | gzip > "${DUMP_FILE}.tmp"; then
- mv "${DUMP_FILE}.tmp" "${DUMP_FILE}"
- SIZE=$(du -h "${DUMP_FILE}" | cut -f1)
- echo " ✓ Backup saved: ${DUMP_FILE} (${SIZE})"
- else
- echo " ✗ pg_dumpall failed!"
- rm -f "${DUMP_FILE}.tmp"
+ pg_dumpall --clean --if-exists > "$SQL_FILE"
+ if [ ! -s "$SQL_FILE" ] || ! grep -q '^-- PostgreSQL database cluster dump complete$' "$SQL_FILE"; then
+ echo " ✗ Empty or incomplete PostgreSQL dump" >&2
exit 1
fi
+ gzip -c "$SQL_FILE" > "${DUMP_FILE}.tmp"
+ gzip -t "${DUMP_FILE}.tmp"
+ mv "${DUMP_FILE}.tmp" "$DUMP_FILE"
+ rm -f "$SQL_FILE"
+ SIZE=$(du -h "$DUMP_FILE" | cut -f1)
+ echo " ✓ Backup saved: ${DUMP_FILE} (${SIZE})"
echo "Cleaning backups older than ${RETENTION_DAYS} days..."
find "${BACKUP_DIR}" -name "pg_dumpall_*.sql.gz" -mtime +${RETENTION_DAYS} -print -delete
echo "Current backups:"Stats: |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The PostgreSQL backup job can report success when
pg_dumpallfails because POSIX sh returns the gzip pipeline status. That publishes an empty archive and then prunes older backups.Run dumping and compression separately, require nonempty SQL with the cluster-dump completion marker, validate gzip before atomic publication, and clean private temporary files on failure. Retention runs only after successful publication. Pin the client to PostgreSQL18.6 and a verified digest, matching the production server major. Add a regression check to CI.
Validation:
All temporary pods, restore containers and private dumps/logs were removed. Production PostgreSQL stayed2/2Ready with zero restarts. The scheduled CronJob and retained backups, including the known invalid archive, remain unchanged until merge/Argo sync. After rollout, run and inspect one fresh CronJob-derived backup. Same-NAS archives still need independent backup protection.
Evidence:
docs/superpowers/reports/2026-09-21-postgres-backup-reliability.md.