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.
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.
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.
Knowledge check
Why inspect aliases and membership before running
wget?
Because a connection depends on name resolution and a shared network first. It is faster and more causal to prove the discovery path before blaming the application.
After reconnecting da18-api, its IP changed. Is
that a failure?
No. Endpoint addresses are dynamically allocated by default. Clients should use the stable service name/alias unless a justified static-address contract exists.
The front container cannot resolve data. What
evidence makes this an expected result?
The front container is attached only to da18-front,
while data is an alias on da18-back.
Alias scope and membership explain the failure.
Should the optional IPv6 lab modify daemon.json if
creation fails?
No. Record the capability limitation and skip it. The mandatory learning path is IPv4/local/disposable and does not require host policy changes.
Why leave busybox:1.36.1 installed during
cleanup?
The image can be shared content. Exact lab cleanup targets containers/networks; deleting shared images is unnecessary and can disrupt other work.
Official references and version notes
-
Docker Docs — Networking overview
— resolver behavior, custom-network embedded DNS, and
127.0.0.11. - Docker Docs — Bridge network driver — user-defined bridge isolation and automatic name/alias resolution.
- Docker Docs — docker network connect — endpoint attachment and network-scoped aliases.
- Docker Docs — Networking in Compose — project networks and service-name discovery.
- Compose Specification — service network aliases — aliases are scoped to the network on which they are declared.
-
Compose Specification — networks
— IPAM,
enable_ipv4,enable_ipv6, internal/external networks, and custom names. - Docker Docs — IPv6 networking — Linux-daemon support, IPv6 network creation, ULA allocation, and Compose examples.
- Docker Engine 29 release notes — current Engine behavior and networking fixes.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.