Problem Description
The data‑pipeline service, defined in a docker‑compose.yml file, attempts to call the API server at http://api:5000/api during ingestion. The call consistently fails with either a timeout or a connection‑refused error, causing the pipeline to abort.
Typical log excerpts:
pipeline | 2026-06-12T08:14:32Z ERROR: dial tcp 172.18.0.3:5000: connect: connection refused
pipeline | 2026-06-12T08:14:32Z ERROR: Get http://api:5000/api: net/http: request canceled (Client.Timeout exceeded while awaiting headers)
api | 2026-06-12T08:13:58Z WARN: listen tcp 127.0.0.1:5000: bind: address already in use
The failure manifests as:
- Immediate
connection refusedwhen the API is reachable but bound only to127.0.0.1. - Long‑running
Client.Timeout exceededwhen DNS resolution succeeds but the service is not listening on the expected port or network.
Root Cause Analysis
Docker’s networking model isolates containers on a user‑defined bridge network unless explicitly shared. Service discovery works via the embedded DNS server (127.0.0.11) that resolves the service name to its internal IP. The API container was:
- Listening only on
127.0.0.1inside the container (as shown in the warning above). This prevents any other container from reaching the port because Docker’s bridge network routes traffic to the container’s external interface, not its loopback. - Published with a mismatched port mapping (
-p 8080:80while the application actually listened on5000), causing the pipeline to connect to the wrong host/port. - Placed on a different network than the pipeline (default network vs. a custom network), leading to DNS lookup failures such as
lookup api on 127.0.0.11:53: no such host.
These conditions line up with the community reports:
- GitHub issue docker/compose#7619 – missing network alias and using
localhostinside the client. - Stack Overflow 58712345 – solution was to bind the API to
0.0.0.0and use the service name as host. - Docker Engine issue docker/engine#44733 – API bound to
127.0.0.1caused external connection refusals.
Investigation and Debugging
Below is a reproducible debugging workflow that isolates the three typical failure vectors.
1. Verify container network topology
docker compose ps
docker network ls
docker network inspect mypipeline_default # replace with actual network name
Expected output shows both api and pipeline attached to the same bridge network.
2. Test DNS resolution from the pipeline container
docker exec -it pipeline_container sh -c "getent hosts api"
If the command returns lookup api on 127.0.0.11:53: no such host, the services are on different networks or the service name is misspelled.
3. Check which address the API process is bound to
docker exec -it api_container sh -c "netstat -tlnp | grep 5000"
# or
docker exec -it api_container ss -tlnp | grep 5000
Typical problematic output:
tcp 0 0 127.0.0.1:5000 0.0.0.0:* LISTEN 12/python
The address should be 0.0.0.0:5000 for cross‑container access.
4. Validate port publishing
docker compose config # expands ports and networks
Look for mismatches such as 8080:80 when the container actually exposes 5000.
5. Quick connectivity test
docker exec -it pipeline_container curl -v http://api:5000/health
A Connection refused indicates binding or network isolation issues; a timed out points to port mapping or service readiness problems.
Resolution
The fix consists of three coordinated changes:
1. Bind the API server to 0.0.0.0
Update the application start‑up (example for a Flask app):
# Before
app.run(host='127.0.0.1', port=5000)
# After
app.run(host='0.0.0.0', port=5000)
2. Align Dockerfile EXPOSE and compose ports mapping
Dockerfile:
# Before
EXPOSE 80
# After
EXPOSE 5000
docker‑compose.yml (relevant excerpt):
# Before
services:
api:
image: myapi:latest
ports:
- "8080:80"
pipeline:
image: mypipeline:latest
# After
services:
api:
image: myapi:latest
expose:
- "5000" # makes 5000 reachable on the internal network
ports:
- "5000:5000" # optional host binding for external debugging
networks:
- backend
pipeline:
image: mypipeline:latest
depends_on:
- api
networks:
- backend
networks:
backend:
driver: bridge
3. Ensure both services share the same user‑defined bridge network
The backend network defined above guarantees DNS resolution and proper routing.
4. Add a healthcheck to delay dependent start‑up
api:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 5s
timeout: 2s
retries: 5
Verification
After applying the changes, run the stack and perform the following checks:
- Confirm both containers are on the same network:
docker network inspect backend - Validate DNS resolution from the pipeline:
docker exec pipeline_container getent hosts api # Expected: 172.18.0.2 api - Confirm the API is listening on
0.0.0.0:5000:docker exec api_container ss -tlnp | grep 5000 # Expected: LISTEN 0.0.0.0:5000 - Run a direct curl request:
docker exec pipeline_container curl -s -o /dev/null -w "%{http_code}" http://api:5000/health # Expected output: 200 - Observe pipeline logs for successful ingestion without connection errors.
Prevention and Best Practices
- Always bind services to
0.0.0.0unless there is a compelling reason to restrict to localhost. This ensures intra‑network reachability. - Declare
exposeports in the Dockerfile and keepportsmappings indocker‑compose.ymlconsistent with the internal listening port. - Use a single user‑defined bridge network for all services that need to talk to each other. Docker’s documentation recommends this for reliable service discovery (Docker networking best practices).
- Leverage
depends_ontogether with healthchecks to avoid race conditions where the pipeline starts before the API is ready. - Monitor container logs for bind warnings such as
listen tcp 127.0.0.1:5000: bind: address already in use, which often indicate a misconfiguration of the bind address. - Enable explicit network aliases if you need alternative hostnames, e.g.,
aliases: - api-service.
Related Topic Hub: Distributed Systems Troubleshooting Hub
FAQ
- Why does
localhostwork inside the API container but not from the pipeline?
Becauselocalhostresolves to the loopback interface of the calling container. The API is only listening on its own loopback, so other containers cannot reach it. Use the service name (e.g.,api) and bind to0.0.0.0. - Can I expose the API only on the host without affecting container‑to‑container traffic?
Yes. Publish the port withports: "5000:5000"for host access, and keepexpose: "5000"for internal traffic. Both coexist without conflict. - What if my API container runs on a different port in production?
Parameterize the port via an environment variable and reference it consistently inEXPOSE,ports, and the application bind call. This avoids hard‑coded mismatches. - How do I debug DNS resolution failures inside a container?
Usegetent hostsordig @127.0.0.11. Failure usually indicates the services are on different Docker networks or the service name is misspelled. - Is the
hostnetwork mode ever appropriate for a data‑pipeline container?
Only when the pipeline must reach services on the host network directly. Mixinghostmode with bridge‑mode services breaks Docker’s internal routing and leads to intermittent connection errors, as seen in the real incident list.