CalcSnippets Search
Database 3 min read

PostgreSQL in Docker Compose With a Healthcheck So Your App Stops Booting Into a Race

A practical PostgreSQL and Docker Compose guide that shows how to add a proper healthcheck, why container startup order is not enough, and how to reduce the classic app-vs-database boot race.

Why this failure feels random: the database container is “up,” the app still cannot connect, and everybody argues about whether Docker, Postgres, or the framework is broken. Usually the problem is simpler: the app started before the database was actually ready.

Why startup order is not readiness

In Compose, a container can be started before the service inside it is truly ready for connections. That distinction matters a lot with databases.

A PostgreSQL container may exist, logs may be flowing, and yet the app still hits a connection error because Postgres is still initializing.

The local anti-pattern

People often write:

services:
  db:
    image: postgres:16
  app:
    build: .
    depends_on:
      - db

This says the app depends on the database container starting, but not necessarily on the database becoming ready.

Add a PostgreSQL healthcheck

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app
      POSTGRES_DB: app
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 5s
      retries: 10

That gives Compose a more honest signal than “the process exists.”

Why pg_isready is the right tool

It checks whether PostgreSQL is accepting connections. That is much more relevant than simply checking whether the container process is alive.

For application startup sequencing, connection readiness is the thing that matters.

What the app should still do

Even with healthchecks, your application should still handle temporary database unavailability sensibly. Containers and networks are real systems. Transient connection issues can happen.

That means healthchecks reduce boot races, but they do not eliminate the need for good retry logic or sensible startup behavior in the app itself.

The bigger lesson

Many Docker Compose issues are not really Docker problems. They are readiness problems hiding behind container vocabulary. Healthchecks help because they force you to describe what “ready” actually means for the service.

That is why they improve more than Postgres. They improve your operational honesty.

A useful local standard

If the app can survive a clean docker compose up only on some runs, the stack is not healthy enough yet. A good local environment should boot predictably, not require lucky timing. Healthchecks move you closer to that standard because they make service readiness visible instead of assumed.

Final recommendation

If your app sometimes beats Postgres to the finish line and crashes on boot, stop pretending startup order is enough. Add a real healthcheck, think in terms of readiness, and let the database tell you when it is truly available instead of guessing from container state alone.

That is the real maturity move here. The goal is not more YAML. The goal is replacing an unreliable assumption with an explicit readiness signal the system can act on.

That is how local infrastructure starts becoming dependable instead of merely convenient.

Reliable startup is one of the clearest signals that the stack is maturing.

If the database boot path is stable, developers stop wasting energy on ritual restarts and start trusting the local stack more.

That trust is a bigger productivity gain than most teams expect.

Sources

Keep reading

Related guides