Once more than one container exists, networking becomes one of the most practical Docker skills.
Teams need to know how services discover each other, which ports are internal versus exposed, and where traffic enters the system.
Beginners often mix host ports and container ports mentally, which leads to confusion. Professionals care about network boundaries, naming, and service clarity.
A good container network design keeps communication predictable without exposing more than necessary.
A very useful distinction is separating container-to-container traffic from traffic that needs to reach the host machine or outside users. Many services only need to talk internally and should not be publicly exposed.
When beginners publish every port out of convenience, they lose sight of this difference. Internal communication and external access deserve different decisions.
In containerized systems, service discovery should rely on stable names or service definitions rather than temporary runtime IP addresses. This keeps multi-service setups more resilient and easier to understand.
Professionals want traffic paths that survive restarts and recreations without manual reconfiguration. Service names are usually the friendlier stable contract.
A container has its own network namespace. Inside a container, localhost refers to that same container, not the host and not another service. Put related containers on a user-defined bridge network and connect by container or service DNS name. Docker supplies DNS resolution for names on that network.
Container ports describe where an application listens. Publishing a port maps a host address and port to the container port so callers outside the Docker network can connect. Container-to-container traffic normally uses the private service name and container port without publishing the database or cache to the host.
An application must listen on the container interface, commonly 0.0.0.0, rather than only 127.0.0.1. Test DNS, connection, and application protocol separately. A successful name lookup does not prove the process is listening, and an open TCP port does not prove the application is ready.
Networking bugs often feel mysterious because the app itself may be healthy while traffic still fails to reach it. Good debugging begins by checking one hop at a time: is the process listening, is the port correct, is the service on the right network, and is the caller using the right destination?
This hop-by-hop method is more reliable than changing random port mappings and hoping something starts working.
Place an API and database on one user-defined network and connect by service name. Then access the API from the host through a published port to distinguish container-to-container traffic from host-to-container traffic.
Using localhost inside the API points back to the API container, not the database. A published port is normally unnecessary for private service-to-service communication.
Verification must use evidence that matches the concept. Resolve the database service name inside the API container, inspect network membership, and test the host-facing port separately. Repeat the check after deliberately introducing the failure, then after the fix. The contrast between those runs is the part that turns a definition into practical understanding.
Use separate networks to limit communication paths, such as edge-to-web and web-to-data. Attaching every container to one broad network weakens isolation. Docker networks are not a complete zero-trust policy, so production platforms may need stronger network policy, identity, and service authorization.
Preserve TLS and identity according to trust boundaries. Internal traffic is not automatically trustworthy. Validate certificates and hostnames for sensitive dependencies, rotate credentials, and avoid embedding secrets in connection URLs printed to logs. Set connection, read, and request deadlines so a failed dependency does not hold resources indefinitely.
Inspect network membership, DNS resolution, routes, listening sockets, and logs. Reproduce from inside the caller container because host connectivity can differ. Monitor connection errors, pool saturation, retransmissions, and dependency latency. Avoid relying on fixed container IPs because replacement changes them.
A service must first listen on the correct interface inside its container. Binding only to 127.0.0.1 makes it reachable from that same container but not through the container network. Binding to 0.0.0.0 accepts traffic arriving on any container interface; access is then narrowed by network membership, published ports, host firewall rules, and application authentication.
EXPOSE is image metadata documenting an intended port. It does not publish the port to the host. The -p option or a Compose ports entry creates a host-to-container mapping, while expose or ordinary service listening can support container-to-container traffic without host publication. Publish only entry points that host or external callers need; databases and caches commonly stay on an internal application network.
Containers on a user-defined network can resolve service or container aliases through Docker DNS. The caller uses the destination container port, not the host-published port. If a database listens on 5432 and the host maps 15432:5432, another container connects to db:5432; a host tool connects to localhost:15432. Mixing those viewpoints is a common cause of connection failures.
This is the sort of route a developer should be able to describe clearly.
Browser -> published web service port -> app container -> internal database service name on shared Docker network
docker network create appnet
docker run -d --name db --network appnet postgres:16
docker run --rm --network appnet busybox nslookup db
docker network inspect appnet
The database is discoverable to the API without a host port.
docker network create appnet
docker run -d --name db --network appnet postgres:16
docker run -d --name api --network appnet -p 8080:8080 \
-e DATABASE_HOST=db example/api:1.0
docker run --rm --network appnet busybox nslookup db
docker network inspect appnet
Check resolution, socket reachability, and HTTP behavior in order.
docker exec api getent hosts db
docker exec api sh -c \"nc -vz db 5432\"
docker exec api sh -c \"wget -S -O- http://web:8080/health\"
docker logs --tail=100 api
docker inspect api --format \"{{json .NetworkSettings.Networks}}\"
No. If they share the right Docker network, they can often communicate internally without exposing those ports to the host.
Inside container networks, the service name usually represents the correct destination. `localhost` inside one container refers only to that same container.
Explore 500+ free tutorials across 20+ languages and frameworks.