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.
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.
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 10Wait in a loop before connecting
For docker-compose or where a healthcheck is not available, poll with pg_isready until it succeeds.
until pg_isready -h localhost -p 5432 -U postgres; do
echo "waiting for postgres"; sleep 1
doneHow to prevent it
- Always define a
--health-cmdon database service containers. - Use
pg_isreadyrather than a fixedsleep, which is either too short or wasteful. - Connect on the mapped port (localhost:5432), not the container hostname, from a step.