2026.07 / COMPOSE CHECKLIST 023
Coolify Docker Compose volumes and healthchecks before you deploy a database app
Static sites are forgiving. If a container is replaced, the source files are still in Git. A stateful app is different. The database, uploads, queues, and generated files may live outside the image, and a “successful” deploy can still be wrong if the app starts before the database is ready.
This is the checklist I would run before moving a small database-backed app into Coolify with Docker Compose. It is aimed at the practical long-tail problem: “I have a Compose file that works locally; what do I check before Coolify is allowed to manage it on a VPS?”
Goal: make the data paths, startup rules, healthchecks, and restore path explicit before the first production deploy, not after the first broken redeploy.
The reliable mental model
A Docker image should be disposable. A database volume is not. Docker’s own volume documentation describes volumes as the preferred way to persist data generated and used by containers. Coolify’s Docker Compose documentation supports multi-container Compose apps, persistent storage, healthchecks, and predefined network connections. The shape is familiar, but the stakes are higher once the app stores real data.
Git repository
├─ docker-compose.yml # service shape
├─ app image/Dockerfile # disposable build artifact
└─ migrations/scripts # repeatable app changes
Coolify server
├─ app container # replaceable
├─ database container # replaceable
└─ named volumes/storage # protect and back up
If you cannot point to where the important data lives, you are not ready to deploy. “It is somewhere inside Docker” is not enough.
Start by naming every stateful path
Before importing a Compose file, mark each path as either disposable or persistent:
| Path | Usually persistent? | Why it matters |
|---|---|---|
| Postgres/MySQL data directory | Yes | Losing it means losing the app. |
| Uploaded files | Yes | Often missed when only the database is backed up. |
| Redis cache | Usually no | Depends whether Redis is used as a cache or durable queue. |
| Build artifacts | No | Should be recreated from Git and the build pipeline. |
| Logs | Maybe | Useful for debugging, but not always production data. |
That table should decide the Compose volumes: section. Do not mount the whole project directory into production just because it made local development convenient. Bind mounts are handy for development; named volumes or explicit persistent storage are usually cleaner for production.
A safer Compose skeleton
The exact image names and commands will change, but this pattern is the one I look for: named database storage, an app healthcheck, a database readiness check, and an app dependency that waits for health rather than merely container creation.
services:
app:
image: ghcr.io/example/app:latest
environment:
DATABASE_URL: ${DATABASE_URL}
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3000/health || exit 1"]
interval: 30s
timeout: 5s
retries: 5
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 10s
timeout: 5s
retries: 10
restart: unless-stopped
volumes:
db_data:
Docker’s Compose startup-order documentation is explicit that Compose does not wait until a service is “ready” unless you model that readiness with conditions and healthchecks. The app container starting is not the same thing as the database accepting connections.
Healthchecks should test the app, not just the process
A weak healthcheck asks “is the process running?” A useful one asks “can the app answer the smallest request a real router or operator cares about?” For a web app, that is often /health. For a worker, it might be a lightweight command that checks dependencies without mutating data.
Keep the endpoint boring:
- return
200only when the app is ready to serve, - avoid leaking environment variables, versions, hostnames, or internal IPs,
- avoid expensive database scans,
- make it stable enough that deploys do not flap.
Then verify the healthcheck in Coolify after deploy. A green container list is useful, but the live application URL and the application logs still matter.
Backups are part of the deploy, not a later chore
The first production deploy should already have a restore story. For a small single-VPS setup, I want at least three layers:
- Database dumps: scheduled logical backups that can be restored into a clean database version.
- Volume or storage backups: uploaded files and other non-database state copied off the server.
- VPS/provider snapshots: useful for fast rollback, but not a substitute for app-level backups.
The test is simple: can you create a throwaway environment, restore the dump and files, start the app, and log in without touching production? If not, the backup process is still theoretical.
Coolify-specific pre-deploy checks
- Confirm the Compose file uses production-safe images, not a development bind mount.
- Confirm every persistent path has a named volume or explicit persistent storage entry.
- Store secrets as Coolify environment variables, not committed values.
- Make the app listen on the expected internal port and expose only the web service publicly.
- Add healthchecks for the database and web service.
- Run migrations deliberately and know whether they are reversible.
- Take a backup before the first real deploy and before every risky migration.
- After deploy, verify logs, health, public HTTPS, and one real user path.
This sits next to my Coolify static-site deploy checklist, but the emphasis changes. For static HTML I mostly worry about discovery files, cache busting, and live-page verification. For a stateful app I care most about data ownership, startup order, health, migrations, and recovery.
Common mistakes
- Only backing up the database. User uploads, generated invoices, and import files may live in separate volumes.
- Using
latestwithout control. Pin versions for databases and critical services so a rebuild does not silently change major versions. - Letting the app race the database. Use healthchecks and readiness-aware dependencies.
- Publishing internal details in docs. Use placeholders for domains, tokens, network names, and host paths.
- Never restoring a backup. A backup that has not been restored is just a file with good intentions.
The practical rule
Before Coolify manages a stateful Compose app, make this sentence true: “I know where the data lives, I know when the app is healthy, and I know how to restore it somewhere else.” If that sentence feels vague, keep the app in staging and tighten the Compose file first.
That discipline is not glamorous, but it is what lets a small VPS run real projects without turning every redeploy into a gamble.