CertGrid CertGrid
Hands-on Lab·Docker

Docker User-Defined Networks and DNS

Why containers on the default bridge cannot find each other by name, and how one command fixes it. Build a user-defined network, resolve containers by name and by alias, attach one container to two networks, then cut a network off from the internet entirely.

Storage and Networking Guide 10 of 46 Beginner

Tested on the versions above. Network IDs and the subnet Docker picks differ on every host. Match the shape of the output, not the exact addresses.

One Docker host is all this guide needs. Nothing here depends on a second machine, and the hardware above is modest on purpose - a 2 core, 4 GB VM runs everything in this path.
Server NameIP AddressOSRolesCPURAMHDD
DOCKER01192.168.0.21Ubuntu 26.04 LTSDocker Host2 Core4 GB50 GB

Before you start

  1. The problem: the default bridge has no DNS

    Containers started without a --network flag land on the built-in bridge network. They can reach each other by IP address, but Docker runs no DNS resolver there, so names do not work. This is the most common reason a freshly written multi-container setup cannot connect, and the error names an address without ever saying the word network.

    bash
    # both containers are on the DEFAULT bridge - name lookup failsdocker exec cg-a ping -c 1 cg-bping: bad address 'cg-b'

    Expected resultA bad address error, not a timeout. The name never resolved to anything.

    Success conditionYou can tell the two failures apart. bad address is DNS; a hang or 100% packet loss after a name resolves is routing or a firewall instead.

  2. Create a user-defined network

    One command is the whole fix. A user-defined bridge network gets an embedded DNS server that resolves container names automatically. Docker allocates a subnet for you, or you can pin one - useful when you must avoid colliding with a corporate range.

    bash Example session
    docker network create cg-nete15d0235e8d3272f32843320394233582c4e1e4879fd25efb5267c8b1dddbbc8docker network create --driver bridge --subnet 172.31.9.0/24 cg-net26cccdc80ebee9eb1c0eb168e857aad782b2acbafaff4047998d80996c9cfed33

    Expected resultA long network ID for each. bridge is already the default driver, so the second --driver flag is explicit rather than necessary.

    Success conditionBoth appear in docker network ls.

  3. Attach containers, and give one an alias

    --network places a container on the network at start time. --network-alias adds a second name it answers to on that network - the mechanism behind blue/green swaps, and behind Compose letting you reach a service by its service name rather than its container name.

    bash Example session
    docker run -d --name cg-web --network cg-net --network-alias api nginx:alpine80d708b8dda854e4105584b2bad17cb07fbf771b36de6065bf30a1ac6947f018docker run -d --name cg-cli --network cg-net alpine:3.22 sleep 300d981c03346bfc24eae222a1c3dc9d82b20b8dff265dbc9ee6a89f79a28cdde05

    Expected resultTwo container IDs. Nothing is published to the host - these containers talk to each other, not to you.

    Success conditiondocker ps lists both as Up.

  4. Resolve by name, and by alias

    The lookup that failed on the default bridge now works, and the alias resolves to the identical address. Note what is not needed: no published ports, no IP addresses, no editing of /etc/hosts. Containers on a user-defined network reach each other on the container port directly.

    bash Example session
    docker exec cg-cli ping -c 2 cg-webPING cg-web (172.19.0.2): 56 data bytes64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.127 ms2 packets transmitted, 2 packets received, 0% packet lossdocker exec cg-cli ping -c 1 apiPING api (172.19.0.2): 56 data bytes

    Expected resultBoth names resolve to the same address, and an HTTP request made against the name returns nginx's welcome page.

    Verify it worked

    bash
    docker exec cg-cli wget -qO- http://cg-web | head -4<!DOCTYPE html><html><head><title>Welcome to nginx!</title>

    Success conditionapi and cg-web return the same IP. The alias is a real DNS name, not a label.

  5. See who is on the network

    docker network inspect lists the attached containers with their addresses. This is the fastest way to answer "is this container actually on the network I think it is", which is usually the real question hiding behind a connection failure.

    bash Example session
    docker network inspect cg-net --format "{{range .Containers}}{{.Name}} {{.IPv4Address}}{{println}}{{end}}"cg-web 172.19.0.2/16cg-cli 172.19.0.3/16

    Expected resultOne line per attached container.

    Success conditionBoth containers are listed. A container you expected to see but do not is the bug.

  6. A container can join more than one network

    docker network connect attaches a running container to an additional network without restarting it, and it gains a second address there. This is how a service sits on both a public-facing network and a private backend network - the arrangement that keeps a database unreachable from anything except the app.

    bash
    docker network connect cg-net2 cg-cli# succeeds silentlydocker network disconnect cg-net2 cg-cli

    Expected resultNo output from either command.

    Success conditionBetween the two commands docker network inspect cg-net2 lists cg-cli; afterwards it does not.

  7. Cut a network off from the internet

    --internal creates a network with no route out. Containers on it still reach each other, but nothing reaches the outside - not even DNS for external names, which is why the failure below is an address error rather than a timeout. For a database tier that is a real containment boundary rather than a convention.

    bash
    docker network create --internal cg-isolated1b0042e8981d9a51eff4280618d71d55671c4548892714af66cab0ee9daecf36docker run --rm --network cg-isolated alpine:3.22 wget -qO- --timeout=5 http://example.comwget: bad address 'example.com'

    Expected resultThe external request fails, exit status 1.

    Success conditionThe container cannot reach the internet. Put a second container on this network and the two will still talk to each other.

  8. Clean up

    Networks are not removed along with the containers that used them, and a network still holding an endpoint refuses to be removed. That is a safety feature rather than an obstacle - remove the containers first.

    bash
    # -f stops and removes these containers immediatelydocker rm -f cg-web cg-clicg-webcg-clidocker network rm cg-net cg-isolatedcg-netcg-isolated

    Expected resultEach name echoed back as it is removed.

    Success conditiondocker network ls shows only the three built-in networks: bridge, host and none.

Troubleshooting

Official sources