Docker · 50 min

Connect Containers with Docker Networks

Go beyond port publishing and learn how containers actually talk to each other. Explore the default bridge, create a user-defined network with automatic DNS, and prove container-to-container communication by name with real ping and wget tests.

Problem

You already know how to run a container and publish a port to your browser. But real applications are never one container — a web app talks to a database, a cache, a queue. The moment you have two containers that need to reach each other, a new question appears: **how does one container find and talk to another?** Beginners reach for hardcoded IP addresses, and it works — until a container restarts, gets a new IP, and everything breaks. The Docker answer is *networks*: virtual networks that connect containers and, on a user-defined network, give you **automatic DNS** so a container can reach another simply by its name, no matter what IP it lands on. In this lab you'll see exactly why the default `bridge` network isn't enough, create your own network, run two containers on it, and prove they resolve each other by name with real `ping` and `wget` output. You'll also learn to inspect networks, connect and disconnect containers on the fly, and use separate networks to isolate groups of services — the exact mechanism Compose uses under the hood.

Objectives

Prerequisites

What you will build

You won't write application code in this lab. Instead you'll wire up two containers so they can
talk to each other by name
— the single most important networking skill for real,
multi-service apps. You'll first watch the default bridge fail to resolve names, then create
your own network where it works, and prove it with real ping and wget output captured from
inside a container.

The one mental model: containers are isolated until a network connects them

By default a container is a sealed box. Publishing a port with -p opens a door to the host,
but it says nothing about how two containers reach each other. That's what a Docker network
is: a virtual switch that containers plug into. Two containers on the same network can talk;
two containers on different networks cannot (unless you deliberately bridge them).

The three network drivers you'll meet

Driver What it does When to use it
bridge The default. A private virtual network on a single host; containers get internal IPs and can talk to each other Almost always — the standard choice for containers on one machine
host Removes network isolation; the container shares the host's network stack directly (no -p needed) Rare — when you need maximum network performance or the container must bind host ports directly
none Disables networking entirely; the container has only a loopback interface When a container must be fully isolated from any network

There's also overlay, used to connect containers across multiple hosts in a Swarm/cluster —
out of scope for this single-host lab, but worth knowing it exists.

The key insight: default bridge vs user-defined bridge

Both are "bridge" networks, but they behave differently, and this trips up almost everyone:

You'll see both behaviors firsthand. Do the steps in order — each builds on the last, and you'll
collect the outputs you need for the submission in Step 6.

Steps

  1. Understand Docker's network model and drivers

    Before creating anything, get the map in your head: what a Docker network is and which driver
    does what.

    Why networks exist

    A container is isolated by default. When you ran docker run -d -p 8080:80 nginx, the -p
    flag opened a path from your host to the container — but that's host-to-container. It
    says nothing about how a second container would reach the first. For that, both containers
    must share a network.

    List the networks Docker already has

    A fresh Docker install ships with three networks. Look at them:

    docker network ls
    

    You'll see at least:

    NETWORK ID     NAME      DRIVER    SCOPE
    xxxxxxxxxxxx   bridge    bridge    local
    xxxxxxxxxxxx   host      host      local
    xxxxxxxxxxxx   none      null      local
    

    These map directly to the three drivers you'll use on a single host:

    • bridge — the default private network. Any container started without --network joins
      this one. Containers here get an internal IP and can reach each other by IP.
    • host — no isolation; the container uses the host's network directly. Start a container
      with --network host and its ports are the host's ports (no -p mapping). Fast, but you
      lose isolation and can't run two containers that want the same port.
    • none — networking disabled. --network none gives the container only a loopback
      interface. Useful for a job that must not touch the network at all.

    The decision in one line

    Use bridge (almost always), reach for host only when you need raw host networking,
    and use none when a container must be network-isolated. For multi-host clusters there's
    overlay, which this lab doesn't cover.

    Checkpoint

    • Which driver do containers use if you don't pass --network? (bridge — the default)
    • Does -p 8080:80 let two containers talk to each other? (No — it only connects the host to a container. Container-to-container needs a shared network.)
    • Which driver would you pick for a batch job that must never make a network call? (none)
  2. Explore the default bridge and hit its DNS limitation

    Let's prove why the default bridge isn't enough, so the custom network in Step 3 feels
    earned rather than arbitrary.

    Pull a small, ping-friendly image

    You'll use busybox — a tiny image that bundles ping, wget, nslookup and a shell,
    perfect for network testing.

    docker pull busybox:1.36
    docker pull nginx:1.27-alpine
    

    Run two containers on the default bridge

    Start two long-lived busybox containers without a --network flag, so they join the
    default bridge. sleep 3600 just keeps them alive so you can exec into them.

    docker run -d --name c1 busybox:1.36 sleep 3600
    docker run -d --name c2 busybox:1.36 sleep 3600
    docker ps
    

    Inspect the default bridge

    See who's connected and what IPs they got:

    docker network inspect bridge
    

    Scroll to the "Containers" section — you'll see c1 and c2, each with an IPv4Address
    like 172.17.0.2/16 and 172.17.0.3/16. Note c2's IP; you'll use it in a second.

    The limitation: name resolution fails

    Try to reach c2 by name from inside c1:

    docker exec c1 ping -c 2 c2
    

    This fails — ping: bad address 'c2'. On the default bridge there is no DNS, so the name
    c2 means nothing. Now try the same container by its IP (use the address you saw in
    the inspect output):

    docker exec c1 ping -c 2 172.17.0.3
    

    This succeeds. The containers can reach each other — they're on the same network — but
    only by IP. And IPs change every time a container restarts, which makes hardcoding them a
    trap.

    Checkpoint

    • On the default bridge, why did ping c2 fail but ping <c2's IP> work? (No DNS on the default bridge — names aren't resolved, only raw IPs route.)
    • Why is depending on the IP a bad idea? (IPs are assigned dynamically and change on restart; your config would break.)

    Deliverable for this step: capture the failing ping c2 output and the succeeding
    ping <IP> output — they're the "before" half of your submission.

  3. Create a user-defined network with automatic DNS

    Now the payoff. On a network you create, Docker runs an embedded DNS server and containers
    resolve each other by name.

    Create the network

    docker network create minha-rede
    

    Docker prints the new network's ID. By default it uses the bridge driver (you can be
    explicit with --driver bridge, but bridge is the default for a single host). Confirm it
    exists:

    docker network ls
    

    You'll see minha-rede in the list alongside the built-in bridge, host and none.

    Run two containers on the new network

    The --network flag attaches a container to a network at start time. Run an nginx web server
    and a busybox client, both on minha-rede:

    docker run -d --network minha-rede --name web nginx:1.27-alpine
    docker run -d --network minha-rede --name cliente busybox:1.36 sleep 3600
    
    • web — the nginx server, reachable on port 80 inside the network (note: no -p needed
      for container-to-container traffic; you only publish a port when the host/browser needs
      in).
    • cliente — a busybox box you'll run test commands from.

    Inspect the custom network

    docker network inspect minha-rede
    

    In the "Containers" block you'll see both web and cliente attached, each with an IP on
    this network's subnet. Same as the default bridge so far — the difference is invisible until
    you use a name, which is the next step.

    Why this matters

    The instant two containers share a user-defined network, Docker registers their names in its
    internal DNS. web and cliente can now find each other by name — no IPs, no config files,
    no restarts breaking anything. This is precisely what Docker Compose sets up for you
    automatically behind the scenes; here you're doing it by hand so you understand the machinery.

    Checkpoint

    • What does docker network create default the driver to on a single host? (bridge)
    • Why didn't you pass -p when starting web? (Because cliente reaches it over the shared network on port 80 directly; -p is only for exposing a container to the host.)

    Deliverable for this step: the docker network ls output showing minha-rede, and the
    docker network inspect minha-rede output showing both containers attached.

  4. Prove container-to-container communication by name

    This is the heart of the lab: run real tests from inside one container against another, using
    only names. This is the "after" that contrasts with Step 2's failure.

    Ping by name — it works now

    From inside cliente, ping web by its name:

    docker exec cliente ping -c 3 web
    

    Unlike Step 2, this succeeds: you'll see replies from web, and crucially the name
    resolves to an IP on minha-rede:

    PING web (172.19.0.2): 56 data bytes
    64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.089 ms
    64 bytes from 172.19.0.2: seq=1 ttl=64 time=0.101 ms
    ...
    

    Docker's embedded DNS turned web into the right IP automatically. This is the whole point
    of user-defined networks.

    Confirm the DNS resolution explicitly

    docker exec cliente nslookup web
    

    You'll see web resolve to its container IP via the Docker DNS server at 127.0.0.11 — the
    embedded resolver every user-defined network gets.

    Fetch the actual web page by name

    Ping proves reachability; now prove the service answers. web runs nginx on port 80, so
    request its homepage from cliente using the name:

    docker exec cliente wget -qO- http://web
    

    You'll get the HTML of nginx's "Welcome to nginx!" page, retrieved over the network by name.
    If your busybox wget output is quiet, wget -S http://web -O /dev/null shows the response
    headers (a 200 OK from nginx) instead.

    Note on curl: busybox ships wget, not curl. If you prefer curl, run it from an
    image that has it, e.g. docker run --rm --network minha-rede curlimages/curl:8.9.1 -s http://web.
    The lesson is identical — the name web resolves inside the network.

    Why this is the big deal

    You just addressed another container by a stable name that survives restarts, instead of a
    fragile IP. Your web app can point at db, your worker at redis, and it keeps working no
    matter which IPs Docker hands out. Name-based service discovery is the foundation every
    Compose file and orchestration tool relies on.

    Checkpoint

    • What made ping web succeed here when ping c2 failed in Step 2? (This network is user-defined, so Docker's embedded DNS resolves container names; the default bridge has no such DNS.)
    • Which DNS server IP did nslookup report? (127.0.0.11 — Docker's embedded resolver.)

    Deliverable for this step: capture the successful ping -c 3 web, the nslookup web
    output, and the wget -qO- http://web (or -S) output showing nginx answered by name.

  5. Manage networks: connect, disconnect, isolate, diagnose

    Networks aren't static. You can attach and detach running containers, and use separate
    networks to isolate groups of services — the real reason networks are a security tool, not
    just a convenience.

    Isolation: a container on a different network can't reach in

    Start a third container on the default bridge (not minha-rede) and try to reach web:

    docker run -d --name estranho busybox:1.36 sleep 3600
    docker exec estranho ping -c 2 web
    

    This fails — estranho isn't on minha-rede, so it can't even resolve or route to web.
    Separate networks isolate services: exactly how you'd keep a database off the public-facing
    network. This mirrors the ebook's webnet vs dbnet split, where a web container and a db
    container on different networks simply cannot talk.

    Connect a running container to a network

    You don't have to recreate a container to network it. Attach estranho to minha-rede live:

    docker network connect minha-rede estranho
    docker exec estranho ping -c 2 web
    

    Now it works — estranho gained a second interface on minha-rede and can resolve web.
    This is the "shared network" trick from the ebook: two otherwise-isolated groups can be
    bridged deliberately by joining a common network, without tearing anything down.

    Disconnect it again

    docker network disconnect minha-rede estranho
    docker exec estranho ping -c 2 web    # fails again — back to isolated
    

    Diagnose from inside a container

    When something can't connect, these tools (all in busybox) tell you why:

    docker exec cliente nslookup web      # is the name resolving?
    docker exec cliente ping -c 2 web     # is the host reachable?
    docker exec cliente wget -S http://web -O /dev/null   # is the service answering (200)?
    docker network inspect minha-rede     # who is actually attached?
    

    Work top-down: DNS → reachability → service response → network membership. Most "it can't
    connect" bugs are one container simply not being on the expected network — docker network inspect reveals it instantly.

    Clean up the networks

    Disconnect or stop containers, then remove the network. A network can't be removed while
    containers are still attached:

    docker rm -f web cliente c1 c2 estranho
    docker network rm minha-rede
    docker network prune          # removes ALL unused networks; asks for confirmation
    

    Checkpoint

    • Why couldn't estranho reach web at first? (It was on the default bridge, not on minha-rede — different networks are isolated.)
    • How did you bridge them without recreating the container? (docker network connect minha-rede estranho attached it live.)
    • What must be true before docker network rm succeeds? (No containers are attached to that network.)

    Deliverable for this step: capture the isolation failure, the docker network connect +
    successful ping, and the final cleanup confirming the network is gone.

  6. Submit: document your network and connectivity tests

    Prove you wired two containers together and made them talk by name by capturing the real
    evidence in a small repository.

    Build your submission

    Create a folder, initialize git, and write a SUBMISSION.md documenting what you did. Paste
    the real outputs you collected — not invented text. The contrast between the default
    bridge (Step 2) and your custom network (Step 4) is the core of the submission.

    # Docker Lab 4 — Connect Containers with Docker Networks
    
    ## 1. docker network ls (default networks + minha-rede)
    <paste your `docker network ls` showing bridge/host/none and minha-rede>
    
    ## 2. Default bridge: name FAILS, IP works
    Output of `docker exec c1 ping -c 2 c2` (should FAIL: bad address):
    <paste it>
    Output of `docker exec c1 ping -c 2 <c2's IP>` (should SUCCEED):
    <paste it>
    
    ## 3. docker network inspect minha-rede
    <paste it, showing web and cliente attached>
    
    ## 4. Custom network: name RESOLVES
    Output of `docker exec cliente ping -c 3 web` (should SUCCEED):
    <paste it>
    Output of `docker exec cliente nslookup web`:
    <paste it — note the 127.0.0.11 resolver>
    Output of `docker exec cliente wget -qO- http://web` (or `-S`):
    <paste it — nginx answered by name>
    
    ## 5. Isolation + connect/disconnect
    `docker exec estranho ping web` before connect (FAILS):
    <paste it>
    After `docker network connect minha-rede estranho`, ping web (SUCCEEDS):
    <paste it>
    
    ## 6. Clean up
    <paste `docker network rm minha-rede` and a final `docker network ls`>
    
    ## Reflection (2-3 sentences)
    In my own words: why the default bridge couldn't resolve names, what a user-defined network
    adds (embedded DNS), and why name-based discovery beats hardcoded IPs.
    

    Submit

    git init
    git add SUBMISSION.md
    git commit -m "Docker lab 4 — container networking"
    # push to a public repo or create a gist, then submit that URL
    

    Submit the repository or gist URL as your lab deliverable.

    Submission criteria (self-check)

    • docker network ls output shows your minha-rede alongside the built-in networks
    • You captured the default bridge failing to resolve a name (ping <name> → bad address) but succeeding by IP
    • docker network inspect minha-rede shows both containers attached
    • ping -c 3 web from cliente succeeds by name on the custom network
    • nslookup web shows resolution via the 127.0.0.11 embedded DNS
    • wget http://web returned nginx's response (page HTML or a 200 header) — a real service answered by name
    • You demonstrated isolation and a live docker network connect fixing it
    • A final docker network ls confirms minha-rede was removed (clean host)
    • Your reflection correctly explains default bridge vs user-defined DNS and why names beat IPs

    Anti-stub check

    Every output must be real and yours. Invented IPs, copy-pasted example blocks, or a
    ping that "would work" are not a submission. The grader is looking for the contrast: the
    same ping <name> that fails on the default bridge and succeeds on minha-rede. If both look
    identical, you didn't actually run one of them.

    What's next

    You can now connect containers into networks and have them discover each other by name — the
    backbone of every multi-service app. But everything you've done so far is ephemeral: delete
    a container and its data vanishes. In the next Docker lab you'll fix that with volumes and
    data persistence
    — keeping databases and files alive across container restarts and rebuilds.