Skip to content
Latchkey

Missing "wait for Postgres" Healthcheck - Migration Race in CI

The job starts a database service and immediately runs migrations without waiting for the service to be ready. Sometimes the database is up in time and sometimes it is not, producing flaky "connection refused" failures driven purely by a startup race.

What this error means

The same migrate step passes on some runs and fails with "connection refused" on others, with no code change. The flakiness tracks how quickly the database container happens to boot.

psql
Run npx prisma migrate deploy
Error: P1001: Can't reach database server at `localhost`:`5432`
# (passes on the next run)

Common causes

No healthcheck on the service

Without a --health-cmd, GitHub Actions does not wait for the database before starting steps, so the migrate step can win the race.

Fixed sleep instead of readiness probe

A hard-coded sleep 5 is sometimes too short on a slow runner, reintroducing the race.

How to fix it

Add a service healthcheck

Let the job block until the database reports healthy.

.github/workflows/ci.yml
services:
  db:
    image: postgres:16
    env: { POSTGRES_PASSWORD: postgres }
    options: >-
      --health-cmd "pg_isready -U postgres"
      --health-interval 5s --health-timeout 5s --health-retries 10

Poll readiness instead of sleeping

Replace fixed sleeps with an actual readiness probe.

Terminal
until pg_isready -h localhost -p 5432; do sleep 1; done

How to prevent it

  • Always pair a database service with a healthcheck or readiness probe.
  • Never rely on a fixed sleep to mask a startup race.
  • On managed runners (Latchkey), self-healing auto-retries transient failures, and a database that is slow to accept connections is the canonical transient case.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →