← Back home

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:

PathUsually persistent?Why it matters
Postgres/MySQL data directoryYesLosing it means losing the app.
Uploaded filesYesOften missed when only the database is backed up.
Redis cacheUsually noDepends whether Redis is used as a cache or durable queue.
Build artifactsNoShould be recreated from Git and the build pipeline.
LogsMaybeUseful 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:

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:

  1. Database dumps: scheduled logical backups that can be restored into a clean database version.
  2. Volume or storage backups: uploaded files and other non-database state copied off the server.
  3. 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

  1. Confirm the Compose file uses production-safe images, not a development bind mount.
  2. Confirm every persistent path has a named volume or explicit persistent storage entry.
  3. Store secrets as Coolify environment variables, not committed values.
  4. Make the app listen on the expected internal port and expose only the web service publicly.
  5. Add healthchecks for the database and web service.
  6. Run migrations deliberately and know whether they are reversible.
  7. Take a backup before the first real deploy and before every risky migration.
  8. 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

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.