Skip to main content
Docs Infrastructure Self-Hosted Backup Strategy

Self-Hosted Backup Strategy

Coolify’s built-in backup UI is limited for compose-stack setups. Here’s what actually works.


Coolify built-in backup — limitations

Coolify’s backup tab works reliably only for databases deployed as Standalone Databases via Coolify’s Databases section.

For databases inside Docker Compose stacks (the default for one-click services like Listmonk, Twenty CRM, n8n, Plausible, etc.), the backup tab may not appear at all, or may show “Success” while silently failing to upload to S3.


Postgres backups — PGBackWeb

PGBackWeb is available as a Coolify one-click service. It connects directly to any Postgres container on the VPS and uploads backups to S3-compatible storage.

Setup:

  1. Deploy PGBackWeb via Coolify one-click
  2. For each compose-stack Postgres container, connect it to the PGBackWeb network:
docker network connect <SERVICE_UUID> <pgbackweb_container_name>
  1. In the PGBackWeb UI, add each database using the internal container name as the host
  2. Set up a schedule (e.g. daily at 02:00) and an S3 destination

⚠️ If PGBackWeb is ever redeployed, the network connections are lost and must be re-run.


File and SQLite backups — docker-volume-backup

Services like Vaultwarden and Uptime Kuma use file/SQLite storage — PGBackWeb can’t handle these. Use the offen/docker-volume-backup sidecar:

backup:
  image: offen/docker-volume-backup:v2
  environment:
    BACKUP_CRON_EXPRESSION: "0 2 * * *"
    AWS_S3_BUCKET_NAME: your-backup-bucket
    AWS_S3_PATH: vaultwarden/
    AWS_ENDPOINT: <R2_ENDPOINT_HOSTNAME>  # bare hostname, NO https://
    AWS_ACCESS_KEY_ID: <your_key>
    AWS_SECRET_ACCESS_KEY: <your_secret>
    AWS_S3_FORCE_PATH_STYLE: "true"
    BACKUP_RETENTION_DAYS: "14"
  volumes:
    - vaultwarden-data:/backup/vaultwarden-data:ro
    - /var/run/docker.sock:/var/run/docker.sock:ro

Add this as a sidecar in the service’s docker-compose.yml via SSH (not via Coolify UI — it will be overwritten if you redeploy through Coolify).

⚠️ AWS_ENDPOINT must be a bare hostname only — no https:// prefix.


Healthcheck variable substitution gotcha

${VARIABLE} in Docker healthcheck test commands is substituted by the host shell before being passed to Docker. If the variable is not exported in the host environment, it becomes empty.

# Safe — no variable substitution
healthcheck:
  test: ["CMD-SHELL", "pg_isready"]

# Risky — ${POSTGRES_DB} may be empty at host level
healthcheck:
  test: ["CMD-SHELL", "pg_isready -d ${POSTGRES_DB}"]

Verify every backup manually

After setting up any backup configuration, always confirm the file actually appears in your S3/R2 bucket. Don’t trust UI success messages alone — especially with Coolify + R2, where the upload can silently fail even after a “Success” is shown.

Zenith Stack

We set up and maintain backup strategies as part of our infrastructure service.

Learn more