Intro
Dockerfile networking issues can turn a working container into an isolated box that cannot reach the database, resolve service names, or accept inbound traffic. This guide moves beyond generic Docker advice and focuses on practical troubleshooting steps for networking problems that originate in Dockerfiles and the runtime configurations they produce.
You will learn how to diagnose DNS failures across custom bridge networks, verify port mappings between the host and containers, and test connectivity between linked services. Each section includes concrete commands, expected output, and failure signals that help you decide whether to adjust a Dockerfile instruction, recreate a network, or inspect a running container.
The goal is operational safety: observe before changing, limit the blast radius, use placeholders instead of secrets, verify the result, and document how to recover if the expected state is not reached.
Version and Environment Inventory
Before touching a Dockerfile or restarting containers, capture a precise snapshot of the current environment. Start with the Docker Engine and client versions because networking behavior changes between releases.
docker version --format '{{.Server.Version}}'
Expected output: 20.10.12 or similar. If the client and server versions differ significantly, upgrade or match them to reduce edge-case bugs.
Next, list the defined networks and inspect the one your containers use.
docker network ls
Output shows network IDs, names, drivers (bridge, overlay, host, none), and scope. A container on a user-defined bridge network gets automatic DNS resolution for other containers on the same network. A container on the default bridge does not; it can only reach others by IP.
For the involved container, inspect its network configuration:
docker inspect --format '{{json .NetworkSettings.Networks}}' <container-name>
This returns JSON with per-network details: IP address, gateway, DNS settings, and aliases. Check that the container is attached to the expected network and has an IP in the subnet.
Example failure signal: a service named web cannot resolve db but both are on the same custom network. The expected output would show both containers in the network inspection:
docker network inspect <network-name> --format '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{"\n"}}{{end}}'
If db is missing, then the database container is not attached to that network, or its container name differs.
Prerequisites for reliable networking checks:
- Docker Engine 20.10 or later (for modern DNS and network features)
- Docker CLI that matches the engine version
- A disposable test container with network tools, such as
nicolaka/netshoot
Use a test container to ping services inside the same network without modifying production containers:
docker run --rm --network <network-name> nicolaka/netshoot ping -c 4 db
If DNS fails, you will see ping: bad address 'db'. If DNS works but the service is unreachable, you will see 4 packets transmitted, 0 received, 100% packet loss, which points to a service not listening or a firewall rule.
When dealing with persistent data, confirm where files are stored before changing containers. A named volume such as app_data:/var/lib/app is managed by Docker and is usually easier to reuse across container rebuilds. A bind mount such as ./data:/var/lib/app maps a host directory directly and is useful for local development, but it can expose permission, portability, and backup problems if the same path does not exist on another machine. Run a restart test: stop the container, recreate it, and confirm the application still sees the expected files. If the data disappears, the service was probably writing to the container filesystem instead of a volume or mount.
Capture current state and timestamps as part of the inventory. For example:
date +%Y-%m-%dT%H:%M:%S%z
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
docker logs --tail 100 <container-name> > /tmp/container-$(date +%s).log
This creates a timestamped snapshot and stores recent logs for later comparison. Protect credentials and private material: never print environment variables or connection strings into logs. Use redaction if necessary. Change one scoped item only when its blast radius and recovery path are understood.
Safe Configuration Path
Dockerfile instructions influence networking in ways that are not always obvious. EXPOSE does not publish ports; it only documents intent. CMD and ENTRYPOINT determine which process listens, and USER can prevent a service from binding to a low port.
Common safe configuration changes involve:
- Replacing
localhostwith a service name or the container's own DNS alias. - Moving from the default bridge to a custom network to gain DNS resolution.
- Adjusting
HEALTHCHECKto use a network endpoint instead of a local file check.
Before making any change, observe the current state with read-only commands:
docker port <container-name>
This shows the actual port mappings and is more reliable than reading docker ps, which can be truncated. Expected output might be:
80/tcp -> 0.0.0.0:8080
80/tcp -> :::8080
If the container should expose port 443 but only 80 appears, the Dockerfile may lack an EXPOSE 443 or the container process is not listening on 443.
To change a port mapping, you must recreate the container; docker run -p and docker compose ports are set at creation. An unsafe change is to edit the container's live config, because it will be lost on next restart. Instead, update the Dockerfile or Compose file, then redeploy.
Example safe change: a web app in a Dockerfile uses RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf to avoid a warning, but then DNS resolution fails because Apache now sends localhost as the host to backend services. Replace localhost with a meaningful alias like web that resolves via Docker's embedded DNS. After the change, rebuild and restart, then verify with:
docker exec <web-container> curl -I http://db:8080/health
Expected output: HTTP/1.1 200 OK. If you get Could not resolve host: db, the DNS change did not take effect, or the network attachment is wrong.
For configuration management, avoid baking environment-specific values into the image. Use environment variables or a config file mounted at runtime. The Dockerfile should not contain secrets. A safe pattern:
FROM node:20-alpine
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Then at runtime:
docker run -d --name app --network frontend -e DATABASE_URL=postgres://db:5432/app app:latest
DATABASE_URL uses the service name db, which resolves only if a container named db is on the same network and the embedded DNS server works. Test connectivity with:
docker exec app nslookup db
Expected output includes the IP address of the db container. If not, check that both containers are on the same network and that the network's DNS is enabled (it is by default on user-defined networks).
A production-like local test should include a restart test: stop the container, recreate it, and confirm the application still sees the expected files and network peers. If the data disappears or the DNS entries vanish, the configuration was not persistent.
Limit the blast radius by testing changes on a copy of the container first. Use docker run --rm with the same image and network settings, run the diagnostic, then apply the change to the primary container only after verification.
Verification and Diagnostics
Systematic verification separates correlated events from root causes. Start with layer 3 and layer 4 tests, then move to application-level checks.
Ping tests whether a host is reachable and DNS resolves:
docker exec web ping -c 4 db
This sends ICMP packets. If the network uses non-ICMP filtering, ping may fail even though the service is reachable over TCP. A better test for a database is TCP connectivity:
docker exec web nc -zv db 5432
Expected output: Connection to db 5432 port [tcp/postgresql] succeeded! If it hangs or says refused, either the database container is not listening on 5432 or a firewall on the database container blocks the port.
Check listening ports inside the container:
docker exec db netstat -tulpn | grep LISTEN
If nothing is listening on 5432, the PostgreSQL service inside the container may have failed to start. Check its logs:
docker logs db --tail 50
Look for FATAL: could not create shared memory segment or permission denied. If the process is running but not listening on the expected interface, it may be binding to 127.0.0.1 inside the container instead of 0.0.0.0. In PostgreSQL, this is controlled by listen_addresses in postgresql.conf. The Dockerfile or entrypoint script should set it to '*' for container operation.
For DNS-specific diagnostics, use nslookup or dig:
docker exec web nslookup db
Inspect the DNS server used. Docker's embedded DNS server is at 127.0.0.11 inside containers. Query it directly:
docker exec web nslookup db 127.0.0.11
If this works but the default resolver does not, an override may be set in /etc/resolv.conf, possibly from the Dockerfile or a mount. Check the file:
docker exec web cat /etc/resolv.conf
Expected entry: nameserver 127.0.0.11. If it shows an external DNS, the container will not resolve Docker network aliases.
To test application-level connectivity, use curl with verbose output:
docker exec web curl -v http://db:8080/health
Verbose output shows the DNS resolution, TCP connection, TLS handshake, and HTTP response. A successful response proves the full path works. If it fails with Connection refused, the service is not listening or a firewall rejects the connection. If it fails with Could not resolve host, DNS is broken.
Capture diagnostics in a structured way. For example, create a file with all outputs:
{
echo "=== date ==="; date
echo "=== docker version ==="; docker version
echo "=== networks ==="; docker network ls
echo "=== container network inspect ==="; docker inspect web --format '{{json .NetworkSettings.Networks}}'
echo "=== ping db ==="; docker exec web ping -c 4 db
echo "=== nc db 5432 ==="; docker exec web nc -zv db 5432
echo "=== logs db tail ==="; docker logs db --tail 20
} > /tmp/network-debug-$(date +%s).txt
Review the file chronologically to identify the first failing step.
Failure Modes and Recovery
Common Dockerfile networking failures fall into predictable categories. Here are the most frequent ones, why they happen, and how to recover.
Failure: DNS resolution fails for a service name
Why: Containers are on the default bridge, or the service name is misspelled, or the container is not attached to the custom network.
Diagnosis: docker exec web nslookup db returns server can't find db: NXDOMAIN or times out.
Recovery: Attach both containers to a user-defined network. If they are already on a custom network, verify the service name matches the container name or network alias. For example:
docker network create frontend
docker network connect frontend web
docker network connect frontend db --alias database
Now web can resolve both db and database. Test with docker exec web nslookup database.
Failure: Port mapping not working from host
Why: The container is running but no ports are published, or the port mapping was configured for a different interface.
Diagnosis: docker port web returns nothing, or curl localhost:8080 fails.
Recovery: If using Dockerfile only, there is no runtime port mapping; it must be specified with docker run -p 8080:80 or in a Compose file. Recreate the container with the correct mapping. Inside the container, verify the service listens:
docker exec web netstat -tulpn | grep :80
If nothing is listening, the service is down. Check logs and restart the service inside the container if possible.
Failure: Connection refused between containers on the same network
Why: The target service is not running or is bound to localhost inside its container.
Diagnosis: nc -zv db 5432 shows connection refused.
Recovery: Check the target container's listening ports. If the service is not listening, start it. If it is listening on 127.0.0.1, reconfigure the service to listen on 0.0.0.0 or the container's IP. For example, in PostgreSQL, set listen_addresses = '*' in postgresql.conf and restart.
Failure: No route to host
Why: The destination IP is not reachable because of network segmentation or because the container's gateway is misconfigured.
Diagnosis: ping <ip> returns connect: no route to host.
Recovery: Check the container's network settings: docker inspect web --format '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{.Gateway}}{{"\n"}}{{end}}'. Ensure the gateway is correct for the subnet. If necessary, recreate the network and reconnect containers.
Failure: Application cannot connect to a service on another Docker network
Why: Docker networks are isolated by design. Containers on different networks cannot communicate directly.
Diagnosis: docker exec web nc -zv db 5432 fails even though both containers exist.
Recovery: Connect a container to both networks, or use a service that bridges networks, like a reverse proxy. If the target service should be reachable from the web container, attach the web container to the database network:
docker network connect dbnet web
Then web can reach db via its IP on dbnet. DNS resolution across networks is not automatic.
Common pitfalls
- Using
localhostinside a container to reach another container.localhostis the container itself. Use the service name or DNS alias. - Relying on
EXPOSEin Dockerfile to publish ports.EXPOSEonly documents; it does not map ports. Use-por Composeports. - Forgetting to attach containers to the same custom network. DNS resolution and automatic service discovery only work on user-defined networks.
- Ignoring network mode. A container with
--network hostbypasses Docker's network stack and DNS; a container with--network nonehas no connectivity. Check the network mode withdocker inspect. - Changing network settings on a running container. Network attachments and port mappings are immutable; recreate the container.
- Hardcoding IP addresses. IPs change on container recreation. Use DNS names and aliases.
- Not testing after a change. Always verify with a connectivity test, not just assume.
Operations Checklist
Use this checklist to standardize Dockerfile networking troubleshooting and avoid missed steps.
| Step | Action | Command or Check | Expected Result | Owner | Frequency |
|---|---|---|---|---|---|
| 1 | Record environment version | docker version | Client and server versions compatible | DevOps Engineer, Priya Shah | Every troubleshooting session |
| 2 | List networks | docker network ls | Expected networks present | DevOps Engineer, Priya Shah | Every session |
| 3 | Inspect container network | docker inspect --format '{{json .NetworkSettings.Networks}}' <container> | Container attached to intended network, correct IP | DevOps Engineer, Priya Shah | Every session |
| 4 | Check port mappings | docker port <container> | Expected host:container port pairs | DevOps Engineer, Priya Shah | Every session |
| 5 | Test DNS resolution | docker exec <container> nslookup <service-name> | Resolves to container IP | DevOps Engineer, Priya Shah | Every session |
| 6 | Test TCP connectivity | docker exec <container> nc -zv <service> <port> | Connection succeeded | DevOps Engineer, Priya Shah | Every session |
| 7 | Check service listening ports | docker exec <container> netstat -tulpn | Service listening on expected port | App Developer, Marcus Chen | On service changes |
| 8 | Review logs | docker logs <container> --tail 100 | No fatal errors related to networking | App Developer, Marcus Chen | On failure |
| 9 | Apply configuration change | Update Dockerfile or Compose file | One scoped change only | App Developer, Marcus Chen | As needed |
| 10 | Verify after change | Repeat steps 5-7 | All checks pass | DevOps Engineer, Priya Shah | After every change |
| 11 | Document recovery and lessons | Update runbook | Steps and commands recorded | Tech Lead, Amara Okafor | Monthly review |
Each row has a single accountable owner (a named individual, not a group) to ensure responsibility. The frequency column indicates when the action is performed: every troubleshooting session, on specific events, or during periodic reviews. Review the checklist monthly to add new failure patterns and retire obsolete steps.
Use this checklist as a template and adapt it to your stack. For example, replace nc with telnet if nc is not available, and use dig instead of nslookup if preferred.
Conclusion
Dockerfile networking troubleshooting becomes manageable when you separate observation from intervention, verify each layer of the stack, and document recovery paths. The commands and scenarios in this guide give you a repeatable process: inventory the environment, inspect DNS and ports, test connectivity, then make minimal changes and verify.
As a next step, choose one low-risk verification from the Operations Checklist, such as confirming DNS resolution for a service name. Record the current state, run the check, and compare the result with the expected signal. If it fails, work through the failure modes until you isolate the cause, then apply a safe fix and re-verify.
A reliable technical workflow makes failure visible, protects sensitive values, limits changes to the intended resource, and defines recovery verification before an incident forces the decision. Keep your runbook current and share it with your team.