Sign inSign up

jesulonimii/postgres-s3-backup

By jesulonimii

Updated 24 days ago

Image
0

138

jesulonimii/postgres-s3-backup repository overview

PostgreSQL S3 Backup

Scheduled PostgreSQL backups to S3-compatible storage, packaged as a non-root Docker image. backupctl creates custom-format dumps, verifies every archive before upload, and can perform an isolated restore test — without a Docker socket.

Images are published on Docker Hub as jesulonimii/postgres-s3-backup.

What it does

  • Runs scheduled backups and monthly restore verification inside one container.
  • Uses pg_dump custom format, then checks every archive with pg_restore --list.
  • Uploads a SHA-256 manifest alongside every backup.
  • Restores the newest backup into a temporary PostgreSQL cluster for verification.
  • Emits structured JSON logs and can send failure-only SMTP alerts.
  • Keeps credentials out of logs and status output.

Remote backup deletion is deliberately not supported. Set retention with a bucket lifecycle rule instead.

Quick start with Docker Compose

Create config.yaml with the connection and storage settings. Replace every placeholder before starting the container:

database:
  url: postgresql://backup_user:[email protected]:5432/example?sslmode=verify-full

s3:
  endpoint: https://s3.example.com
  region: auto
  bucket: example-db-backups
  access_key_id: change-me
  secret_access_key: change-me
  force_path_style: false
  server_side_encryption: AES256

backup:
  prefix: staging
  schedule: "0 2 * * *"
  verify_schedule: "0 3 1 * *"
  timezone: Africa/Lagos
  retention_days: 14

# Uncomment all mail values to send alerts on failure.
# mail:
#   smtp_url: smtps://username:[email protected]:465
#   from: [email protected]
#   to: [email protected]

Then create compose.yml alongside it:

services:
  backup:
    image: jesulonimii/postgres-s3-backup:1.0.0
    restart: unless-stopped
    read_only: true
    tmpfs:
      - /var/lib/backupctl/tmp:mode=1777
    security_opt:
      - no-new-privileges:true
    volumes:
      - ./config.yaml:/etc/backupctl/config.yaml:ro

Start the scheduler:

docker compose up -d

The default command is daemon. It runs the backup and verification schedules in the configured timezone. The complete config file is also available as config.example.yaml.

Run a backup now

Use the same Compose configuration for one-off commands:

docker compose run --rm backup run
docker compose run --rm backup verify
docker compose run --rm backup status

status returns redacted JSON. verify downloads the newest successful archive under BACKUP_PREFIX, validates its checksum, and restores it to a temporary in-container PostgreSQL instance.

Configuration

The grouped YAML file above is read from /etc/backupctl/config.yaml. Set CONFIG_FILE to use a different path. The former flat YAML keys, such as database_url and s3_bucket, remain supported for compatibility.

Environment variables are an alternative configuration method and override YAML values when both are supplied. See .env.example for the complete environment-file equivalent.

VariableRequiredDescription
CONFIG_FILENoPath to the config YAML file; default: /etc/backupctl/config.yaml.
DATABASE_URLYes*Source PostgreSQL URL. Must include sslmode=require, verify-ca, or verify-full.
S3_ENDPOINTYesHTTP(S) endpoint for S3 or an S3-compatible provider.
S3_REGIONNoS3 region; default: auto.
S3_BUCKETYesPrivate destination bucket.
S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEYYesCredentials scoped to the environment prefix.
S3_FORCE_PATH_STYLENoSet true for path-style providers such as s3mock.
S3_SERVER_SIDE_ENCRYPTIONNoAES256 or aws:kms.
BACKUP_PREFIXYesPrefix for one environment, for example production or staging.
BACKUP_SCHEDULENoFive-field cron schedule; default: 0 3 * * *.
VERIFY_SCHEDULENoFive-field cron schedule; default: 0 4 1 * *.
TZNoIANA timezone; default: UTC.
RETENTION_DAYSNoInformational only — apply this value in your bucket's lifecycle policy; default: 14.
SMTP_URL, ALERT_FROM, ALERT_TONoConfigure all three to send failure alerts.

* DATABASE_URL is not required for restore; the target URL is supplied to that command instead.

Backups and manifests are stored as:

<BACKUP_PREFIX>/YYYY/MM/DD/<database>_<UTC timestamp>.dump
<BACKUP_PREFIX>/YYYY/MM/DD/<database>_<UTC timestamp>.json

Verification results are stored under <BACKUP_PREFIX>/verification/.

Restore a backup

Restore is intentionally separate from verification and replaces objects in an existing target database. It refuses to target the configured source database, and requires two explicit confirmations for non-interactive use:

docker compose run --rm -it backup restore \
  --database-url 'postgresql://restore_user:[email protected]:5432/staging?sslmode=verify-full' \
  --yes \
  --confirm-restore staging

Omit the restore flags to be prompted interactively. The source DATABASE_URL may be omitted for restore-only use.

Production checklist

  • Create a private bucket and separate prefixes such as production and staging.
  • Apply a lifecycle rule to expire objects under each prefix after RETENTION_DAYS and abort incomplete multipart uploads after 24 hours.
  • Give the image only ListBucket (scoped to its prefix), GetObject, and PutObject; do not grant DeleteObject.
  • Use a dedicated PostgreSQL role with the minimum privileges pg_dump needs, enforce TLS, and prefer sslmode=verify-full with a trusted CA.
  • Test run and verify locally before production.

The image runs as a non-root backupctl user and needs writable storage only for temporary archives and restore data. The read_only and tmpfs options shown above are recommended for production.

License

MIT

Tag summary

Content type

Image

Digest

sha256:8318fe9fd

Size

115.8 MB

Last updated

24 days ago

docker pull jesulonimii/postgres-s3-backup