# Hardening overlay — apply on top of docker-compose.yml OR docker-compose.ghcr.yml: # # docker compose -f docker-compose.yml -f docker-compose.hardened.yml up -d # docker compose -f docker-compose.ghcr.yml -f docker-compose.hardened.yml up -d # # Works with both rootful and rootless engines (Docker rootless / Podman). It # adds no-new-privileges, capability dropping, a read-only app rootfs and # resource limits. The app is a static binary that already runs as a non-root # user and writes nothing to its container filesystem, so it can be locked down # hard; Postgres needs a writable data dir and a few capabilities for its # entrypoint, so it is hardened more conservatively. services: app: # Static binary, logs to stdout, photos live in the DB — nothing is written # to the container filesystem, so the rootfs can be read-only. # # Backup temp files (S3 upload and read-back, downloads, the decrypted dump # during a restore) are as large as the database. They go to # /.tmp on the /backups volume, NOT to /tmp: keep # PARKRR_BACKUP_DIR set to a writable volume, even for an S3-only setup (an # empty volume cron keeps volume backups off). Plan free space there of about # twice the archive size (S3 upload plus its read-back copy, or a staged # upload plus the decrypted dump during a restore). Only with an empty # PARKRR_BACKUP_DIR does the app fall back to /tmp, and the 32 MiB tmpfs below # is far too small for that. # Tax ZIP exports separately stage receipts in /tmp (24 MiB default limit). # For larger exports provision a private writable volume and set TMPDIR plus # PARKRR_TAX_EXPORT_MAX_RECEIPT_MB in your own override. Concurrent exports # share that capacity; a full spool aborts before download, without DB writes. read_only: true tmpfs: # Bounded so a runaway temp write cannot consume host RAM up to mem_limit # (tmpfs pages count toward the container's memory). - /tmp:size=32m,mode=1777 security_opt: - no-new-privileges:true cap_drop: - ALL # Match the image's own non-root user (Dockerfile: adduser -u 10001 parkrr) and the # /backups owner. Forcing a different uid (e.g. 65532) makes /backups unwritable and # silently breaks scheduled backups. user: "10001:10001" mem_limit: 256m pids_limit: 128 cpus: 1.0 db: security_opt: - no-new-privileges:true # Drop everything, then add back only what the official Postgres entrypoint # needs: chown the data dir (CHOWN/FOWNER/DAC_OVERRIDE) and drop to the # postgres user via gosu (SETUID/SETGID). Remove this block if your Postgres # image/entrypoint differs and fails to start. cap_drop: - ALL cap_add: - CHOWN - DAC_OVERRIDE - FOWNER - SETGID - SETUID mem_limit: 512m pids_limit: 256