A container running in absolute isolation is useless. It needs to accept incoming HTTP requests, query databases, and talk to other microservices.
However, because Docker relies on Linux Network Namespaces, every container has its own isolated network stack by default. Getting packets in and out of that isolated stack requires understanding Docker's networking models.
In this module, we will explore the three primary network drivers (bridge, host, and none), understand the critical difference between exposing and publishing ports, and see how containers resolve each other via internal DNS.
The Three Primary Network Drivers
When you launch a container, you assign it to a network. If you don't specify one, Docker assigns it to the default bridge network.
1. The none Driver (Total Isolation)
If you start a container with --network none, Docker provisions a Network Namespace but does not configure any interfaces inside it (except the loopback interface, localhost).
The container has no external IP address, cannot reach the internet, and cannot be reached by other containers. It is entirely air-gapped. This is rarely used in typical web applications but is useful for highly secure, localized data-processing containers.
2. The host Driver (No Isolation)
If you start a container with --network host, Docker disables the Network Namespace entirely.
The container shares the exact same network stack as the host operating system. If your Node.js application listens on port 3000 inside the container, it is literally binding to port 3000 on the host machine.
Pros: Maximum performance. There is no Network Address Translation (NAT) overhead.
Cons: Security and port conflicts. If two containers try to bind to port 3000 using --network host, the second one will crash with an EADDRINUSE error.
[!NOTE]
The host networking driver only works on native Linux hosts. It does not work as expected on Docker Desktop for macOS or Windows because the "host" in that context is the hidden Linux VM, not your physical Mac/PC.
3. The bridge Driver (Default & Most Common)
The bridge network is a software-defined switch that sits inside the Linux kernel.
When you use the bridge driver (the default), Docker assigns the container an internal private IP address (e.g., 172.17.0.2). All containers attached to the same bridge network can communicate with each other using these internal IPs.
The bridge is connected to the host's physical network interface via NAT. This allows containers to make outbound requests to the public internet, but prevents the outside world from reaching in—unless you explicitly publish a port.
Exposing vs. Publishing Ports
This is one of the most confusing concepts for Docker beginners.
EXPOSE (Documentation Only)
In your Dockerfile, you might write:
dockerfile
This does absolutely nothing at runtime. It does not open a firewall rule. It does not map a port. It is purely documentation for the developer, stating: "This application expects to listen on port 3000."
Publishing (-p or --publish)
To actually allow traffic from the host machine to reach the container on a bridge network, you must publish the port at runtime using the -p flag:
bash
This syntax is HOST_PORT:CONTAINER_PORT.
Docker creates an iptables rule on the Linux host. When traffic hits port 8080 on the host machine, Docker's NAT intercepts it and forwards it to port 3000 on the container's isolated internal IP address.
[!CAUTION]
If you run docker run -p 3000:3000, Docker binds to 0.0.0.0:3000 on the host. This means the port is exposed to the entire public internet if the host does not have a separate firewall! To bind only to the local machine, use -p 127.0.0.1:3000:3000.
Container-to-Container Communication (DNS)
In a microservices architecture, your Node.js API container needs to talk to your PostgreSQL database container.
You should never use internal IP addresses (like 172.17.0.3) because containers are ephemeral. If the database container restarts, it will get a new IP address, and your API will break.
Instead, you use User-Defined Bridge Networks and Docker's internal DNS resolver.
1. Create a custom network
bash
2. Launch the database
bash
3. Launch the API
bash
Notice the DATABASE_URL. The host is postgres-db.
Because both containers are on the same user-defined network (my-app-network), Docker runs an internal DNS server. When node-api tries to resolve the hostname postgres-db, Docker dynamically returns the correct internal IP address of the database container, even if it changes!
[!IMPORTANT]
The default bridge network (named bridge) does not support automatic DNS resolution. You must create a custom user-defined network to get hostname resolution. This is a common pitfall!
Key Takeaways
Host vs Bridge: The bridge network provides isolation with NAT overhead. The host network disables isolation for maximum performance but risks port conflicts.
Publishing Ports: The EXPOSE instruction is just documentation. You must use -p HOST:CONTAINER to actually route external traffic into the container.
Internal DNS: Never use IP addresses to connect containers. Place them on a custom network and use their container names (--name) as hostnames.
Security: By default, -p binds to 0.0.0.0. Be careful not to accidentally expose private databases to the public internet.
Knowledge Check
You have an Express API and a Redis cache. How should the Express API connect to the Redis container?
A junior developer adds EXPOSE 5432 to the Dockerfile of a custom PostgreSQL image, but complains that they still cannot connect to the database from their host machine using a local pgAdmin client. What is the technical reason for this failure?
What is the critical security implication of running a container with the command docker run -p 6379:6379 redis on a public-facing cloud server (like an AWS EC2 instance or DigitalOcean droplet) that does not have a separate external cloud firewall configured?
Test your knowledge with more question sets
Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.