Chapter 18Lesson 02~140 minutes

Docker DNS, Service Discovery, Aliases, IPv4/IPv6, Custom Subnets, and Multi-Network Applications: Guided Hands-On Workflow and Core Operations

Build a disposable Docker DNS and multi-network lab with aliases, custom subnets, per-network endpoint inspection, safe optional IPv6, and bounded cleanup.

Hands-onEmbedded DNSCustom subnetIPv6Evidence

Learning objectives

  • Create disposable front/back bridge networks with labels and conflict-checked custom IPv4 subnets.
  • Prove container-name and alias resolution on a user-defined network before testing application connectivity.
  • Attach one API container to two networks and inspect its separate endpoint addresses, gateways, aliases, and resolver state.
  • Demonstrate that a front-only container cannot resolve/reach a back-only database name while the dual-homed API can.
  • Run an optional local IPv6 test only when the daemon/platform supports it, then clean up exact lab resources.
Safety boundary. This lab creates only local bridge networks and disposable BusyBox containers labeled devops-academy.lab=ch18. It does not publish ports, edit firewall/daemon settings, expose the Docker socket, or require cloud/registry credentials. Verify custom subnets do not overlap your VPN/LAN/Docker networks before creation.

1. Preflight: record the daemon and existing address space

Before selecting custom subnets, identify the daemon, platform, and current Docker networks. The example uses 172.30.18.0/24 and 172.30.19.0/24 only if they do not conflict in your environment. If either range overlaps a host/VPN route or existing Docker IPAM range, choose another unused RFC1918 range and keep that choice in your evidence notes.

docker version
docker info
docker context show
docker compose version || true

docker network ls
docker ps -a --no-trunc

# Record the lab image identity before use.
docker pull busybox:1.36.1
docker image inspect busybox:1.36.1 \
  --format 'ID={{.Id}} RepoDigests={{json .RepoDigests}}'
docker network ls --no-trunc
for n in $(docker network ls -q); do
  docker network inspect "$n" --format '{{.Name}} {{json .IPAM.Config}}'
done

# Native Linux host only, read-only; useful for VPN/LAN overlap checks:
ip route 2>/dev/null || true

2. Create two exact, labeled bridge networks

The front network represents client-facing application traffic; the back network represents API-to-data traffic. The labels make ownership visible and cleanup targetable. No static container address is requested—the network allocates endpoint addresses dynamically.

docker network create \
  --driver bridge \
  --subnet 172.30.18.0/24 \
  --label devops-academy.lab=ch18 \
  da18-front

docker network create \
  --driver bridge \
  --subnet 172.30.19.0/24 \
  --label devops-academy.lab=ch18 \
  da18-back

docker network inspect da18-front da18-back

State change: the daemon now owns two network objects and their IPAM pools. No container endpoint exists yet.

3. Start front, API, and database workloads with deliberate membership

The API starts on the front network with alias api, then gains a second endpoint on the back network with alias api-internal. The database exists only on the back network. BusyBox httpd gives us an application-layer test without installing packages.

docker run -d --name da18-front \
  --label devops-academy.lab=ch18 \
  --network da18-front \
  busybox:1.36.1 sh -c 'sleep 3600'

docker run -d --name da18-api \
  --label devops-academy.lab=ch18 \
  --network da18-front --network-alias api \
  busybox:1.36.1 sh -c   'mkdir -p /www; echo api-ok >/www/index.html; httpd -f -p 8080 -h /www'

docker network connect --alias api-internal da18-back da18-api

docker run -d --name da18-db \
  --label devops-academy.lab=ch18 \
  --network da18-back --network-alias data \
  busybox:1.36.1 sh -c   'mkdir -p /www; echo db-ok >/www/index.html; httpd -f -p 9090 -h /www'

docker ps --filter label=devops-academy.lab=ch18

4. Inspect endpoint and resolver evidence before connectivity tests

docker inspect da18-api --format '{{json .NetworkSettings.Networks}}'
docker inspect da18-front --format '{{json .NetworkSettings.Networks}}'
docker inspect da18-db --format '{{json .NetworkSettings.Networks}}'

docker network inspect da18-front \
  --format 'IPAM={{json .IPAM.Config}} Containers={{json .Containers}}'
docker network inspect da18-back \
  --format 'IPAM={{json .IPAM.Config}} Containers={{json .Containers}}'

docker exec da18-front cat /etc/resolv.conf
docker exec da18-api cat /etc/resolv.conf

Expected: da18-api has two entries under NetworkSettings.Networks; the front and database containers each have one. Containers on these custom networks should show the embedded resolver path. The exact IPs are evidence, not constants for later commands.

5. Prove scoped DNS before testing HTTP

# Front can resolve API on their shared network.
docker exec da18-front nslookup api

# The front-only container should not resolve the back-only database name.
docker exec da18-front nslookup da18-db || true
docker exec da18-front nslookup data || true

# The dual-homed API can resolve the database on da18-back.
docker exec da18-api nslookup da18-db
docker exec da18-api nslookup data

# The back-only database can resolve the API's back-network alias.
docker exec da18-db nslookup api-internal

# But that alias is not declared on da18-front.
docker exec da18-front nslookup api-internal || true

A failed lookup from da18-front to data is the intended isolation evidence. Do not “fix” it with a static hosts entry. If the front-end genuinely needs database access, that is an architecture decision requiring explicit network membership—not a DNS workaround.

6. Separate resolution success from application success

docker exec da18-front wget -qO- http://api:8080/
docker exec da18-api wget -qO- http://data:9090/

# Expected to fail because da18-front is not on da18-back:
docker exec da18-front wget -T 2 -qO- http://data:9090/ || true

The first two requests should return api-ok and db-ok. The third failure is not evidence that httpd is broken; DNS/network membership already explains the path. Preserve both lookup and connection results in the evidence packet.

7. Add and remove an alias through exact endpoint reconciliation

Docker aliases belong to a network attachment. To change the alias set with the CLI, disconnect and reconnect the endpoint intentionally; this briefly changes connectivity on that one network, so do it only in the disposable lab.

docker network disconnect da18-front da18-api
docker network connect --alias api --alias edge-api da18-front da18-api

docker exec da18-front nslookup edge-api
docker inspect da18-api --format '{{json .NetworkSettings.Networks}}'

Observation: endpoint identity/address can change after reconnect. The logical alias remains the client contract; this is another reason not to persist the old endpoint IP.

8. Optional local IPv6 extension

Run this only if your Docker daemon supports IPv6 networking and your environment allows a local ULA subnet. It does not require or prove public IPv6 Internet routing. The ULA range below is intentionally local; change it if it conflicts with your environment.

# Capability check / version evidence first.
docker version
docker info

# Optional: local-only dual-stack network.
docker network create --ipv6 \
  --subnet 172.30.28.0/24 \
  --subnet fd00:da:18::/64 \
  --label devops-academy.lab=ch18 \
  da18-dual

docker run -d --name da18-v6a --network da18-dual \
  --label devops-academy.lab=ch18 busybox:1.36.1 sleep 3600
docker run -d --name da18-v6b --network da18-dual \
  --label devops-academy.lab=ch18 busybox:1.36.1 sleep 3600

docker inspect da18-v6a --format '{{json .NetworkSettings.Networks}}'
docker exec da18-v6a nslookup da18-v6b

# If BusyBox/daemon supports it, local IPv6 reachability may be tested by the
# dynamically observed IPv6 address. Do not hard-code an address as identity.

If network creation fails because IPv6 is unsupported or restricted, record that as the environment capability result and skip the extension. Do not edit daemon configuration merely to make this chapter pass.

9. Challenge: choose the failing layer

You see: nslookup data fails from da18-front, but nslookup data and HTTP to data:9090 both succeed from da18-api. Which layer should you change?

Answer after reasoning: the evidence says the database and application are healthy and Docker DNS works on the back network. The front container simply lacks back-network membership. Decide whether that isolation is intentional. If yes, change nothing. If no, explicitly attach the front service or redesign the API boundary; do not hard-code the database IP.

10. Exact cleanup and verification

# Remove only named lab containers.
docker rm -f da18-front da18-api da18-db 2>/dev/null || true
docker rm -f da18-v6a da18-v6b 2>/dev/null || true

# Remove only named lab networks.
docker network rm da18-front da18-back 2>/dev/null || true
docker network rm da18-dual 2>/dev/null || true

# Verify no Chapter 18 resources remain.
docker ps -a --filter label=devops-academy.lab=ch18
docker network ls --filter label=devops-academy.lab=ch18

No system-wide prune is needed. The BusyBox image may be shared with other work, so the lab deliberately leaves the image cache alone.

Next lesson

Next: Configuration, Design Choices, and Tradeoffs

Turn the lab into design rules: when to use service names versus aliases, dynamic versus static addressing, one versus multiple networks, Docker DNS versus external DNS, and IPv4-only versus dual-stack.

Knowledge check

Why inspect aliases and membership before running wget?

After reconnecting da18-api, its IP changed. Is that a failure?

The front container cannot resolve data. What evidence makes this an expected result?

Should the optional IPv6 lab modify daemon.json if creation fails?

Why leave busybox:1.36.1 installed during cleanup?

Official references and version notes

Version baseline, verified 2026-09-21.

Docker Engine 29.8.1 (released 2026-09-15) is the current Engine 29 release in the primary release notes, and Docker Compose v5.5.1 is the current upstream Compose release. The runnable labs deliberately record your Engine/CLI/Compose versions and context because DNS, IPv6, Desktop networking, firewall integration, and helper-tool availability vary by platform. The mandatory path uses only local disposable networks and containers; IPv6 is an optional capability-gated extension.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.