Files changed: engine/.embcache.json engine/changelog.md engine/lint-report.md vault/.trash/2026-06-19.md vault/docs/hardware/environment.md vault/docs/hardware/ip-allocation.md vault/docs/matrix/archivist.md vault/docs/matrix/matrix_host.md vault/docs/matrix/mautrix_signal.md vault/docs/matrix/synapse.md vault/docs/matrix/synapse_retention_discovery.md vault/docs/navi/cc-rules.md vault/docs/navi/deployment.md vault/docs/navi/themes.md vault/docs/services/ots-setup.md vault/docs/services/services.md vault/docs/services/usenet.md vault/docs/software/authentik.md vault/docs/software/caddy.md vault/docs/software/central.md vault/docs/software/dns.md vault/docs/software/geo-tools.md vault/docs/software/navi.md vault/docs/software/recon.md vault/docs/software/searxng.md vault/glossary.md vault/notes/echo6-landing-page-data-export.md vault/notes/ia-download-queue.md vault/projects/advbbs-project.md vault/projects/argus.md vault/projects/deploy-livesync.md vault/projects/fleet-patch-audit.md vault/projects/fleet-platform-baseline.md vault/projects/matrix-synapse-deployment.md vault/projects/meshai-config-hot-apply.md vault/projects/meshai-region-routing-plan.md vault/projects/meshai.md vault/projects/meshcore-transport.md vault/projects/meshtastic-headscale-runbook.md vault/projects/mmud-project.md vault/projects/nominatim-v5-reimport.md vault/runbooks/add-peertube-channel.md vault/runbooks/authentik-access-groups.md vault/runbooks/authentik-create-invitation.md vault/runbooks/authentik-oidc-application.md vault/runbooks/authentik-upgrade.md vault/runbooks/central-deploy-cutover.md vault/runbooks/ct-runbook.md vault/runbooks/edge2-access-reference.md vault/runbooks/expose-service-contabo.md vault/runbooks/expose-service-edge2.md vault/runbooks/expose-service-home.md vault/runbooks/fleet-magicdns-resolved-migration.md vault/runbooks/headless-browser-page-verification.md vault/runbooks/headscale-oidc-boot-order.md vault/runbooks/headscale-onboard-node.md vault/runbooks/ia-cli-reference.md vault/runbooks/ia-download-mirror.md vault/runbooks/idahomesh-bridge-setup.md vault/runbooks/idahomesh-vpn-device-setup.md vault/runbooks/lxc-service-migration.md vault/runbooks/mailcow-create-mailbox.md vault/runbooks/meshai-prod-compose-override.md vault/runbooks/meshmonitor-password-reset.md vault/runbooks/meshtastic-sidecar-node.md vault/runbooks/meshtasticd-sim-nodes-runbook.md vault/runbooks/nordvpn-lxc.md vault/runbooks/peertube-remote-runner.md vault/runbooks/pg-backup.md vault/runbooks/pi-nas-omv-runbook.md vault/runbooks/pipeline-patterns.md vault/runbooks/proxmox-create-ubuntu-vm.md vault/runbooks/proxmox-onboard-node.md vault/runbooks/pymc-repeater-kiss-tnc-reenumeration.md vault/runbooks/recon-operations.md vault/runbooks/recon-service-integration.md vault/runbooks/syncthing-add-node.md vault/runbooks/toc-cortex-pve9.2-update.md vault/session-resume/SESSION-HANDOFF-meshai-test.md
191 lines
4.4 KiB
Markdown
191 lines
4.4 KiB
Markdown
---
|
|
title: PostgreSQL Backup (Docker)
|
|
type: runbook
|
|
tags:
|
|
- storage
|
|
aliases: []
|
|
related:
|
|
- [[matrix-synapse-deployment]]
|
|
- [[ct-runbook]]
|
|
- [[synapse]]
|
|
- [[meshmonitor-password-reset]]
|
|
- [[recon-operations]]
|
|
updated: 2026-07-13
|
|
---
|
|
# PostgreSQL Backup (Docker)
|
|
|
|
Automated pg_dump backups for any Docker-hosted PostgreSQL instance. Retention, integrity check, and restore testing included.
|
|
|
|
---
|
|
|
|
## Inputs
|
|
|
|
```
|
|
CONTAINER_NAME= # Docker container name (e.g., "matrix-postgres")
|
|
DB_NAME= # Database to back up (e.g., "synapse")
|
|
DB_USER= # Database user (e.g., "synapse")
|
|
BACKUP_DIR= # Host directory for backups (e.g., "/opt/matrix/backups")
|
|
RETENTION_DAYS=14 # Days to keep backups
|
|
CRON_SCHEDULE="0 3 * * *" # Daily at 3AM
|
|
```
|
|
|
|
---
|
|
|
|
## Step 1: Create Backup Directory
|
|
|
|
```bash
|
|
mkdir -p ${BACKUP_DIR}
|
|
chmod 700 ${BACKUP_DIR}
|
|
mkdir -p $(dirname ${BACKUP_DIR})/scripts
|
|
```
|
|
|
|
---
|
|
|
|
## Step 2: Create Backup Script
|
|
|
|
Create `$(dirname ${BACKUP_DIR})/scripts/pg_backup.sh`:
|
|
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# --- Configuration (edit per service) ---
|
|
CONTAINER_NAME="${CONTAINER_NAME}"
|
|
DB_NAME="${DB_NAME}"
|
|
DB_USER="${DB_USER}"
|
|
BACKUP_DIR="${BACKUP_DIR}"
|
|
RETENTION_DAYS=${RETENTION_DAYS}
|
|
|
|
# --- Derived ---
|
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}_${TIMESTAMP}.sql.gz"
|
|
LOG_FILE="${BACKUP_DIR}/backup.log"
|
|
|
|
log() {
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "${LOG_FILE}"
|
|
}
|
|
|
|
# --- Backup ---
|
|
log "Starting backup of ${DB_NAME} from ${CONTAINER_NAME}"
|
|
|
|
docker exec ${CONTAINER_NAME} pg_dump \
|
|
-U ${DB_USER} \
|
|
-d ${DB_NAME} \
|
|
--format=plain \
|
|
--no-owner \
|
|
--no-privileges \
|
|
| gzip > "${BACKUP_FILE}"
|
|
|
|
if [ $? -eq 0 ] && [ -s "${BACKUP_FILE}" ]; then
|
|
SIZE=$(du -h "${BACKUP_FILE}" | cut -f1)
|
|
log "Backup successful: ${BACKUP_FILE} (${SIZE})"
|
|
else
|
|
log "ERROR: Backup failed or produced empty file"
|
|
rm -f "${BACKUP_FILE}"
|
|
exit 1
|
|
fi
|
|
|
|
# --- Retention ---
|
|
DELETED=$(find ${BACKUP_DIR} -name "${DB_NAME}_*.sql.gz" -mtime +${RETENTION_DAYS} -print -delete | wc -l)
|
|
log "Retention cleanup: removed ${DELETED} backups older than ${RETENTION_DAYS} days"
|
|
|
|
# --- Integrity ---
|
|
if gzip -t "${BACKUP_FILE}" 2>/dev/null; then
|
|
log "Integrity check: PASS"
|
|
else
|
|
log "ERROR: Integrity check FAILED — backup is corrupt"
|
|
exit 1
|
|
fi
|
|
|
|
log "Backup complete."
|
|
```
|
|
|
|
Make executable:
|
|
|
|
```bash
|
|
chmod +x $(dirname ${BACKUP_DIR})/scripts/pg_backup.sh
|
|
```
|
|
|
|
---
|
|
|
|
## Step 3: Test Manually
|
|
|
|
```bash
|
|
$(dirname ${BACKUP_DIR})/scripts/pg_backup.sh
|
|
ls -lh ${BACKUP_DIR}/*.sql.gz
|
|
cat ${BACKUP_DIR}/backup.log
|
|
```
|
|
|
|
---
|
|
|
|
## Step 4: Schedule via Cron
|
|
|
|
```bash
|
|
(crontab -l 2>/dev/null; echo "${CRON_SCHEDULE} $(dirname ${BACKUP_DIR})/scripts/pg_backup.sh >> ${BACKUP_DIR}/cron.log 2>&1") | crontab -
|
|
crontab -l | grep pg_backup
|
|
```
|
|
|
|
---
|
|
|
|
## Step 5: Test Restore (Non-Destructive)
|
|
|
|
```bash
|
|
# Create throwaway test database
|
|
docker exec ${CONTAINER_NAME} psql -U ${DB_USER} -c "CREATE DATABASE ${DB_NAME}_restore_test;"
|
|
|
|
# Restore latest backup into it
|
|
LATEST=$(ls -t ${BACKUP_DIR}/${DB_NAME}_*.sql.gz | head -1)
|
|
gunzip -c "${LATEST}" | docker exec -i ${CONTAINER_NAME} psql -U ${DB_USER} -d ${DB_NAME}_restore_test
|
|
|
|
# Spot-check (adjust table names per service)
|
|
docker exec ${CONTAINER_NAME} psql -U ${DB_USER} -d ${DB_NAME}_restore_test -c "\dt" | head -20
|
|
|
|
# Cleanup
|
|
docker exec ${CONTAINER_NAME} psql -U ${DB_USER} -c "DROP DATABASE ${DB_NAME}_restore_test;"
|
|
```
|
|
|
|
---
|
|
|
|
## Emergency Restore
|
|
|
|
```bash
|
|
# 1. Stop the application container (not postgres)
|
|
docker stop <app-container>
|
|
|
|
# 2. Drop and recreate
|
|
docker exec ${CONTAINER_NAME} psql -U ${DB_USER} -c "DROP DATABASE ${DB_NAME};"
|
|
docker exec ${CONTAINER_NAME} psql -U ${DB_USER} -c "CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};"
|
|
|
|
# 3. Restore
|
|
LATEST=$(ls -t ${BACKUP_DIR}/${DB_NAME}_*.sql.gz | head -1)
|
|
gunzip -c "${LATEST}" | docker exec -i ${CONTAINER_NAME} psql -U ${DB_USER} -d ${DB_NAME}
|
|
|
|
# 4. Restart app
|
|
docker start <app-container>
|
|
```
|
|
|
|
---
|
|
|
|
## Monitoring Hook (Optional)
|
|
|
|
Add to your monitoring stack:
|
|
|
|
```bash
|
|
LATEST_AGE=$(( $(date +%s) - $(stat -c %Y $(ls -t ${BACKUP_DIR}/${DB_NAME}_*.sql.gz | head -1)) ))
|
|
if [ ${LATEST_AGE} -gt 90000 ]; then
|
|
echo "WARNING: Latest ${DB_NAME} backup is more than 25 hours old"
|
|
fi
|
|
```
|
|
|
|
---
|
|
|
|
## Checklist
|
|
|
|
```
|
|
□ Manual backup produces valid .sql.gz
|
|
□ Backup log shows success
|
|
□ Gzip integrity check passes
|
|
□ Cron job installed
|
|
□ Test restore succeeds
|
|
□ Test database cleaned up
|
|
```
|