Container Networking Without the Mystery

Container Networking Without the Mystery

July 14, 2026

At first glance, container networking looks like a collection of small details: localhost, 0.0.0.0, eth0, bridge networks, ports, and broadcast addresses. The details matter because each one answers a different question:

  • Where is the application listening?
  • Which network can reach it?
  • Which port is being exposed?
  • Is that exposure intentional?

This guide builds that picture from the inside out. You will learn why localhost inside a container is not the host, why container applications commonly bind to 0.0.0.0, how Docker publishes ports, how Wi-Fi, Ethernet, and bridge interfaces fit into the path, and how to choose a safer default.

The goal is not merely to make a URL work. It is to understand exactly who can reach the service and why.

Tip

The practical rule: make the application inside the container listen on 0.0.0.0, then use Docker’s port binding to decide who can reach it.

The mental model

Containers have their own network namespace. That means the host and the container can both have a localhost, but they are not the same place.

  flowchart LR
    Client["Client browser"] --> Host["Host machine"]
    Host --> Docker["Docker networking"]
    Docker --> App["Application inside container"]

    classDef client fill:#dbeafe,stroke:#2563eb,color:#172554
    classDef host fill:#ffedd5,stroke:#ea580c,color:#7c2d12
    classDef docker fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
    classDef app fill:#d1fae5,stroke:#059669,color:#064e3b
    class Client client
    class Host host
    class Docker docker
    class App app

What localhost means

localhost normally resolves to 127.0.0.1, the loopback address. It means “this network environment.”

  flowchart TB
    subgraph H["Host network namespace"]
        HL["Host localhost<br/>127.0.0.1"]
    end
    subgraph C["Container network namespace"]
        CL["Container localhost<br/>127.0.0.1"]
    end
    HL -. "separate network namespaces" .- CL

    classDef loop fill:#dbeafe,stroke:#2563eb,color:#172554
    class HL,CL loop

If an application inside a container listens only on:

127.0.0.1:8080

it is normally reachable only from inside that same container.

Warning

localhost inside a container does not normally mean the host. It means the container itself.

What 0.0.0.0 does

0.0.0.0 is a wildcard bind address. It tells a server:

Listen on every local IPv4 address.

A container might have:

127.0.0.1    loopback
172.17.0.2   container network address

If the application listens on 0.0.0.0:8080, both local addresses can reach the application on port 8080:

  flowchart LR
    Loopback["127.0.0.1:8080"] --> Server["Application<br/>0.0.0.0:8080"]
    Eth0["172.17.0.2:8080"] --> Server

    classDef endpoint fill:#dbeafe,stroke:#2563eb,color:#172554
    classDef server fill:#d1fae5,stroke:#059669,color:#064e3b
    class Loopback,Eth0 endpoint
    class Server server

It does not mean every port, every machine, or internet access. 0.0.0.0:8080 covers port 8080; it does not automatically cover 8081 or 8082.

Note

0.0.0.0 is normally used when a server binds. It is not an address you normally type into a browser.

Why containers commonly listen on 0.0.0.0

Docker normally reaches a container through its virtual network interface, commonly called eth0:

lo       127.0.0.1
eth0     172.17.0.2

Listening only on 127.0.0.1 can prevent Docker from reaching the application through eth0. Listening on 0.0.0.0 allows Docker to use the container network interface.

Publishing a port with Docker

docker run -p 8080:8080 my-app

This maps the host port to the container port:

  flowchart LR
    Browser["Browser<br/>localhost:8080"] --> HostPort["Host port<br/>8080"]
    HostPort --> ContainerPort["Container port<br/>8080"]
    ContainerPort --> App["Application<br/>0.0.0.0:8080"]

    classDef browser fill:#dbeafe,stroke:#2563eb,color:#172554
    classDef host fill:#ffedd5,stroke:#ea580c,color:#7c2d12
    classDef container fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
    classDef app fill:#d1fae5,stroke:#059669,color:#064e3b
    class Browser browser
    class HostPort host
    class ContainerPort container
    class App app

The full syntax is:

-p host-ip:host-port:container-port

Host-only access

docker run -p 127.0.0.1:8080:8080 my-app

Only the host can access the service through http://localhost:8080.

One specific host interface

If the host’s wired interface has IP address 192.168.1.30:

docker run -p 192.168.1.30:8080:8080 my-app

Other reachable devices can use:

http://192.168.1.30:8080

Use the IP address, not the interface name. This is incorrect:

docker run -p eth0:8080:8080 my-app

All host IPv4 interfaces

docker run -p 0.0.0.0:8080:8080 my-app

This makes Docker listen on port 8080 through every host IPv4 interface, including Wi-Fi, Ethernet, and possibly bridge interfaces.

Does this make the service internet-accessible?

Not automatically.

  flowchart LR
    User["External client"] --> Network["Router / cloud network rules"]
    Network --> Firewall["Host firewall"]
    Firewall --> Published["Published host port"]
    Published --> Container["Container port"]
    Container --> Application["Application"]

    classDef external fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef network fill:#ffedd5,stroke:#ea580c,color:#7c2d12
    classDef firewall fill:#fef3c7,stroke:#d97706,color:#78350f
    classDef docker fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
    classDef app fill:#d1fae5,stroke:#059669,color:#064e3b
    class User external
    class Network network
    class Firewall firewall
    class Published,Container docker
    class Application app

External access also depends on firewall rules, cloud security groups, router/NAT forwarding, routing, and application authentication.

Caution

Binding a host port to 0.0.0.0 removes the host-interface restriction. Do not expose an unauthenticated dashboard, database, or development API unintentionally.

Docker, Podman, and host networking

The same concepts apply to both Docker and Podman, but the helper hostname can differ:

RuntimeContainer-to-host hostname
Docker Desktophost.docker.internal
Docker Engine on LinuxOften host.docker.internal with --add-host=host.docker.internal:host-gateway
PodmanPrefer host.containers.internal

Podman may also provide host.docker.internal for compatibility, but host.containers.internal is the Podman-native name. For example:

podman run --rm my-app curl http://host.containers.internal:8080

There is another option: share the host’s network namespace.

podman run --rm --network=host my-app

With host networking, localhost inside the container refers to the host’s network namespace. A service running on the host at 127.0.0.1:8080 can therefore be reached from the container as:

http://127.0.0.1:8080

Host networking also means the container’s application binds directly to host ports, so port collisions are possible and Docker-style -p publishing is unnecessary. More importantly, it removes network isolation: Podman warns that host mode can give the container access to local system services and considers it insecure; Docker similarly documents that the container shares the host network stack and has no separate container IP. See the Podman network-mode documentation and Docker host network documentation.

Warning

Use --network=host for a specific, understood requirement—such as low-level network tooling or a service needing a large range of ports—not as the default way to reach the host. A host-gateway hostname preserves more of the container’s network boundary.

0.0.0.0 is not broadcast

Broadcast is a separate IPv4 concept: sending traffic to every device on a local network.

Common broadcast addresses include:

255.255.255.255
192.168.1.255    # broadcast for 192.168.1.0/24
AddressMeaning
127.0.0.1This network environment only
0.0.0.0Wildcard bind address
255.255.255.255Limited IPv4 broadcast
192.168.1.255Example subnet broadcast

Tip

For a web service, say “publish,” “expose,” or “make reachable.” “Broadcast” usually describes discovery traffic such as DHCP or ARP, not normal HTTP access.

A sensible security pattern

For local development:

docker run -p 127.0.0.1:8080:8080 my-app

Inside the container, configure the application to listen on:

0.0.0.0:8080

This lets Docker reach the application while keeping it accessible only from the host.

If LAN access is required, bind to the host’s LAN IP. Use 0.0.0.0 at the host level only when all host interfaces genuinely need to accept connections.

Apply authentication, HTTPS, firewall rules, least-privilege network access, regular updates, and vulnerability scanning.

This pattern follows two established security principles:

The lesson is simple: reachability is part of the security boundary. A port is not “just a port” once it is published; it becomes a possible path to the application.

The rule worth remembering

Tip

Inside the container, listen on 0.0.0.0 so Docker can reach the application. On the host, publish the port only on the interfaces that genuinely need access.

Client
Host IP and port
Docker published port
Container port
Application listening on 0.0.0.0

For example:

http://192.168.1.30:8080
host 192.168.1.30:8080
container 172.17.0.2:8080
application 0.0.0.0:8080

Closing thought

A container is not secure because it is difficult to see. It is secure when its boundaries are deliberate: the application listens where it must, the host publishes only what is needed, and every reachable path has an owner.

Once you understand that distinction, localhost and 0.0.0.0 stop being mysterious values. They become precise statements about where a service listens, how far it can travel, and how much trust you are willing to grant.

Last updated on