Picture this: your Docker Compose setup works perfectly on your local machine. You push to CI, and suddenly every integration test fails with Connection refused.
The database container is "running." The API container is "healthy." The test process starts. Then it cannot connect to the service it needs.
This failure looks random until you remember one thing: local Docker networking and CI Docker networking are not the same environment.
The local setup lies to you
On your machine, you might connect to Postgres at localhost:5432.
Inside a Compose network, another container should usually connect to postgres:5432, where postgres is the service name.
In CI, the test runner may be:
- inside the Compose network
- outside the Compose network on the host
- inside a CI service container
- inside a nested Docker executor
Those four cases use different hostnames.
That is why a connection string can be "correct" locally and wrong in the pipeline.
First, identify where the test process runs
Before changing ports, ask one question:
Is the test command running inside a Compose service or on the CI host?
If tests run inside Compose:
DATABASE_URL=postgres://user:pass@postgres:5432/appIf tests run on the CI host and Compose published the port:
DATABASE_URL=postgres://user:pass@127.0.0.1:5432/appIf tests run in a separate CI container, neither may work until the CI platform's service networking is configured.
The right hostname depends on where the test runner lives. Service names work inside the Compose network. Published localhost ports work from the host.
Do not trust depends_on as readiness
depends_on can control start order. It does not guarantee that Postgres, Redis, or your app is ready to accept connections.
The common bad version:
services:
api:
depends_on:
- postgresThat only means the postgres container starts before api. It does not mean migrations ran. It does not mean TCP is ready. It does not mean the database accepted authentication.
Use health checks or an explicit wait script.
services:
postgres:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
timeout: 5s
retries: 12
api:
depends_on:
postgres:
condition: service_healthyThat still does not solve every CI platform, but it removes the most common race.
