Skip to content
Latchkey

PostgreSQL "Connection refused" before the service container is ready in CI

The Postgres container has started but postgres inside it is not yet listening on port 5432 when your step runs. The TCP connect is refused. This is a startup race, not a config error: a healthcheck or a wait loop fixes it.

What this error means

Tests or migrations fail on the very first connection with "could not connect to server: Connection refused. Is the server running on host 'localhost' (127.0.0.1) and accepting TCP/IP connections on port 5432?" - and pass on a re-run when the container is warm.

Terminal
psql: error: could not connect to server: Connection refused
	Is the server running on host "localhost" (127.0.0.1) and accepting
	TCP/IP connections on port 5432?

Common causes

The job step runs before Postgres finishes initializing

The container image is pulled and the process launches, but initdb and recovery take a second or two. During that window the port is open to nobody, so the connect is refused.

No healthcheck gates the dependent steps

Without a health-cmd, GitHub Actions only waits for the container to exist, not for Postgres to be ready, so steps start too early.

How to fix it

Add a pg_isready healthcheck to the service

GitHub Actions will not run job steps until the service container reports healthy when you define options with a health command.

.github/workflows/ci.yml
services:
  postgres:
    image: postgres:16
    env:
      POSTGRES_PASSWORD: postgres
    ports: ['5432:5432']
    options: >-
      --health-cmd "pg_isready -U postgres"
      --health-interval 5s
      --health-timeout 5s
      --health-retries 10

Wait in a loop before connecting

For docker-compose or where a healthcheck is not available, poll with pg_isready until it succeeds.

Terminal
until pg_isready -h localhost -p 5432 -U postgres; do
  echo "waiting for postgres"; sleep 1
done

How to prevent it

  • Always define a --health-cmd on database service containers.
  • Use pg_isready rather than a fixed sleep, which is either too short or wasteful.
  • Connect on the mapped port (localhost:5432), not the container hostname, from a step.

Related guides

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