These ten questions run the length of the chapter, and each turns on a Docker 514 behavior that catches experienced developers: name resolution on the default bridge, what COPY does with a directory, shell-form entrypoints, deleted files that still cost space, what EXPOSE really does, volumes that hide or reveal an image's files, Compose 514 's variable precedence, PID 1 and signals, swap limits, and a healthcheck that Podman 47,580 drops. Every snippet ran with Docker Engine 29.8.1 514 , Compose 5.5.1 and Podman 5.7.0 on this chapter's WSL2 6 Ubuntu 26.04 225 host. Read each numbered block, write down what it prints and why, and only then check Appendix H, which gives the real output, the reason and the section it comes from.
Setup
mkdir -p ~/v5-ch4/ty/public ~/v5-ch4/ty/empty && cd ~/v5-ch4/ty
echo '<h1>BookNest</h1>' > public/index.html; head -c 10M /dev/urandom > big.bin
printf '%s\n' 'FROM alpine:3' 'COPY public /site/' > q2.Dockerfile
printf '%s\n' 'FROM alpine:3' 'ENTRYPOINT echo hello from' 'CMD ["cmd"]' > q3a.Dockerfile
printf '%s\n' 'FROM alpine:3' 'ENTRYPOINT ["echo", "hello from"]' \
'CMD ["cmd"]' > q3b.Dockerfile
printf '%s\n' 'FROM alpine:3' 'COPY big.bin /tmp/big.bin' 'RUN rm /tmp/big.bin' > q4.Dockerfile
printf 'services:\n app:\n image: alpine:${TAG}\n' > compose.yaml; echo 'TAG=3.23' > .env
printf '%s\n' 'FROM docker.io/library/alpine:3' 'HEALTHCHECK CMD true' > q10.DockerfileQuestions
# 1. Can one container reach another by name? (Sections 4.6.1 and 4.6.2)
# Predict: which of the two pings resolves its target?
docker network create l3-q1 >/dev/null
docker run -d --name l3-q1a alpine:3 sleep 60 >/dev/null
docker run --rm alpine:3 ping -c1 -W1 l3-q1a 2>&1 | tail -1
docker run -d --name l3-q1b --network l3-q1 alpine:3 sleep 60 >/dev/null
docker run --rm --network l3-q1 alpine:3 ping -c1 -W1 l3-q1b | head -1
docker rm -f l3-q1a l3-q1b >/dev/null; docker network rm l3-q1 >/dev/null
# 2. Where does the directory end up? (Section 4.8.3)
# Predict: /site/public/index.html or /site/index.html?
docker build -q -f q2.Dockerfile -t l3-q2 . >/dev/null && docker run --rm l3-q2 find /site
# 3. Shell form, then exec form, each run without and with an argument. (Sections 4.8.6, 4.8.7)
# Predict: which of the four runs print "cmd" or "world"?
for f in q3a q3b; do docker build -q -f $f.Dockerfile -t l3-$f . >/dev/null
docker run --rm l3-$f; docker run --rm l3-$f world; done
# 4. The file is deleted in the next layer. What did it cost? (Sections 4.7.1 and 4.9.1)
# Predict: the size of each of the two layers,
# and how much more disk the image takes than alpine:3.
docker build -q -f q4.Dockerfile -t l3-q4 . >/dev/null
docker history --format '{{.Size}} {{.CreatedBy}}' l3-q4 | head -2 | cut -c 1-50
docker image ls --format '{{.Repository}}:{{.Tag}} {{.Size}}' \
| grep -E '^(alpine:3|l3-q4:latest) '# 5. nginx's image declares EXPOSE 80, and you run it without -p. (Sections 4.6.4, 4.8.6)
# Predict: how many ports are published, and does the container's own IP answer?
docker run -d --name l3-q5 nginx:1.30-alpine >/dev/null; sleep 1
echo "published: $(docker port l3-q5 | wc -l)"
curl -s -o /dev/null -w '%{http_code}\n' "$(docker inspect -f \
'{{.NetworkSettings.Networks.bridge.IPAddress}}' l3-q5)"; docker rm -f l3-q5 >/dev/null
# 6. An empty named volume, then an empty host folder, over nginx's web root. (Section 4.11)
# Predict: what does each ls list, and what is left in the volume afterwards?
W=/usr/share/nginx/html
docker run --rm -v l3-q6:$W nginx:1.30-alpine ls $W
docker run --rm -v "$PWD/empty:$W" nginx:1.30-alpine ls $W
docker run --rm -v l3-q6:/data alpine:3 ls /data; docker volume rm l3-q6 >/dev/null
# 7. .env says 3.23; the shell says 3.24; then no .env at all. (Section 4.12.4)
# Predict: the three image names.
docker compose -p l3-q7 config --images; TAG=3.24 docker compose -p l3-q7 config --images
docker compose -p l3-q7 --env-file /dev/null config --images 2>&1 | sed 's/.*msg=//'# 8. Node.js with no signal handler as PID 1, without and with --init. (Sections 4.8.7, 4.17.8)
# Predict: each container's exit code and how long docker stop takes.
N='setInterval(() => {}, 1000)'
docker run -d --name l3-q8a node:24-slim node -e "$N" >/dev/null
docker run -d --init --name l3-q8b node:24-slim node -e "$N" >/dev/null
for c in l3-q8a l3-q8b; do s=$SECONDS; docker stop $c >/dev/null
echo "$c exit=$(docker inspect -f '{{.State.ExitCode}}' $c) after $((SECONDS - s)) s"; done
docker rm l3-q8a l3-q8b >/dev/null
# 9. Start with a 64 MB limit, then raise it to 256 MB. (Sections 4.2.6 and 4.17.2)
# Predict: the swap limit, and whether the update succeeds.
docker run -d --name l3-q9 --memory 64m alpine:3 sleep 60 >/dev/null
docker inspect -f '{{.HostConfig.Memory}} {{.HostConfig.MemorySwap}}' l3-q9
docker update --memory 256m l3-q9 2>&1 | sed -E 's/[0-9a-f]{64}/<id>/'
docker rm -f l3-q9 >/dev/null
# 10. One healthcheck, built by Podman twice and by Docker once. (Section 4.19.2)
# Predict: which of the three images keeps it?
podman build -q -f q10.Dockerfile -t q10:oci . >/dev/null 2>&1
podman build -q --format docker -f q10.Dockerfile -t q10:docker . >/dev/null 2>&1
for t in oci docker; do
podman image inspect -f "podman $t: {{json .Config.Healthcheck}}" q10:$t; done
docker build -q -f q10.Dockerfile -t l3-q10 . >/dev/null
docker inspect -f 'docker: {{json .Config.Healthcheck}}' l3-q10