Skip to content
Latchkey

psql "connection refused" - Postgres Service Not Ready in CI

psql tried to open a TCP connection to Postgres and the kernel returned "Connection refused" because nothing is listening yet. The service container is still booting when your step runs - this is a timing problem, not a credentials or SQL problem.

What this error means

A psql or migration step fails immediately with "Connection refused", naming the host and port 5432. It frequently passes on a re-run once the Postgres container has finished initializing and started accepting connections.

psql
psql: error: connection to server at "postgres" (172.18.0.2), port 5432 failed:
Connection refused
	Is the server running on that host and accepting TCP/IP connections?

Common causes

Service container still starting

GitHub Actions starts the Postgres service in parallel with your job. The first migrate/psql call can land before Postgres has bound its socket, so the connection is refused.

No healthcheck gating the job

Without a --health-cmd on the service, the job does not wait for readiness and races the container boot.

Transient network blip

A brief loss of connectivity on the runner network can refuse a connection that succeeds moments later.

How to fix it

Add a service healthcheck so the job waits

  1. Attach a pg_isready healthcheck to the Postgres service.
  2. GitHub Actions holds the job until the service reports healthy.
  3. The race disappears because your step only runs once Postgres accepts connections.
.github/workflows/ci.yml
services:
  postgres:
    image: postgres:16
    env:
      POSTGRES_PASSWORD: postgres
    options: >-
      --health-cmd "pg_isready -U postgres"
      --health-interval 5s
      --health-timeout 5s
      --health-retries 10

Poll for readiness before connecting

Block on pg_isready so the first real query never races the boot.

Terminal
until pg_isready -h "$PGHOST" -p 5432 -U postgres; do
  echo "waiting for postgres..."; sleep 1
done
psql "$DATABASE_URL" -c 'select 1'

How to prevent it

  • Gate every database step on a healthcheck or pg_isready, never a fixed sleep.
  • Use the same readiness wait in local and CI scripts.
  • On managed runners (Latchkey), self-healing auto-retries transient failures, and a database container that is slow to accept connections is a classic transient case.

Frequently asked questions

What causes ""connection to server ... Connection refused""?
GitHub Actions starts the Postgres service in parallel with your job. The first migrate/psql call can land before Postgres has bound its socket, so the connection is refused.
How do I fix "connection to server ... Connection refused"?
Add a service healthcheck so the job waits
Can Latchkey fix this automatically?
Yes. Latchkey runs your GitHub Actions on managed runners that detect this failure, apply the fix, and retry the job automatically - self-healing is on by default.

Related guides

References

Latchkey auto-heals failures like this one - detected, fixed, and retried without you. Start free → 30-day trial · No credit card