Test yourself
Docker for beginners
Final exam · 38 questions · answers explained as you pick
Docker basics
8 questions
01You are in the
docker group, yet docker version prints the Client block and then, in place of the Server block, an error saying the client cannot connect to the daemon. Where do you look next?Incorrect — You are already in the group. A group problem shows up as
permission denied on the socket; this error says nothing answered.Incorrect — The Client block printed in full, so the CLI works. What is missing is the daemon behind the socket.
Correct — The client reached no daemon, so check whether
dockerd is running and read its journal for the reason it stopped or never started.Incorrect — Every
docker run goes through the same daemon. With no daemon running it fails before any pull.02Both run Docker 29, but
docker info on an older production server reports Storage Driver: overlay2, while the lab VM reports overlayfs with driver-type: io.containerd.snapshotter.v1. What explains the difference?Correct — Fresh Docker 29 installs use the containerd image store; a host upgraded from an older Engine keeps
overlay2 until someone migrates it.Incorrect — The CPU architecture does not choose the image store. Fresh installs on amd64 use the containerd image store as well.
Incorrect — containerd comes with every Docker Engine install. The storage line only says where images are kept.
Incorrect — The kit changed nothing here. The containerd image store is the default for fresh installs since Docker Engine 29.
03On the lab VM,
apt-mark showhold lists docker-ce, containerd.io and the plugins. Why is holding the Docker packages a sensible habit on a server too?Incorrect — A hold only stops apt from changing the installed version. It has no effect on how the daemon starts or runs.
Incorrect — Images do not tie the daemon to an Engine version like that. The reason is what an upgrade does to running containers.
Incorrect — The repository accepts plain installs. The lab kit pins and holds by choice, so its versions match the lessons.
Correct — Upgrading restarts
dockerd and with it every container; live restore keeps containers running only across patch upgrades. A hold stops a routine apt upgrade, by hand or by configuration management, from doing that at a moment you did not choose.04On an arm64 lab VM,
docker image ls --tree node:24-alpine shows 0B for the linux/amd64 and linux/s390x rows and 237MB for linux/arm64/v8. What do the 0B rows mean?Incorrect — Nothing is damaged. The rows are 0B because nothing was downloaded for those platforms.
Correct — An image index points to one variant per platform, and Docker pulled only the one that matches this CPU.
Incorrect — Nothing for those platforms is on disk, and the course sets up no emulation. Only the matching variant was pulled.
Incorrect — Compiled programs differ per CPU, so each variant is a separate image with its own ID, not a layer of another.
05A tool needs a kernel feature that only a newer Linux kernel than your host's provides. A colleague suggests running the tool from an image based on the newest distribution release. Will that work?
Incorrect — Containers boot nothing. A kernel package in an image is only files; the process runs on the host kernel.
Incorrect — The right architecture lets the binaries run, but they still make system calls into the host kernel, which lacks the feature.
Correct —
uname -sr inside any container prints the host kernel. Run the tool on a host or VM that has the newer kernel.Incorrect — The lab runs Alpine containers on an Ubuntu VM. The distribution can differ; the kernel cannot.
06For a running container
lab-idle, ps on the VM shows sleep 600 as PID 117155, owned by root, while docker exec lab-idle ps shows the same sleep 600 as PID 1. Why the two numbers?Correct — The process is an ordinary host process; inside its PID namespace it is PID 1 and sees a much shorter process list.
Incorrect — There is one process. The host PID came from
docker inspect for the same container.Incorrect — There is no guest kernel. Both views come from the same host kernel through different namespaces.
Incorrect —
ps inside the container reads the live list; it even showed itself as a new process.07
docker stats --no-stream lab-idle shows 584KiB / 5.76GiB under MEM USAGE / LIMIT. You never set a memory limit. What is the 5.76GiB?Incorrect — Docker sets no default memory limit. The figure follows the machine it runs on.
Incorrect — Nothing is reserved. The container uses under 600 KiB and the rest stays free for everything else.
Incorrect — The alpine image is 13.4 MB on disk, and images are not loaded into memory as a whole.
Correct — Without a limit the container may use whatever the host has, so
docker stats shows the host total.08A security advisory names the OpenSSL version inside
node:24-alpine, which your service has used for months. How does the fix reach the running service?Incorrect — An image freezes its contents, and Docker never changes running containers. Nothing happens until you act.
Correct — Fixes arrive as a newer image, and only a new container started from it runs the fixed library.
Incorrect — That change lives in the writable layer and is lost the next time the container is replaced from the image.
Incorrect — Containers share the host kernel, not its libraries. The OpenSSL in the image is its own copy.
8 questions · explanations appear as you answer
Working with containers
11 questions
01Yesterday you stopped
lab-web with docker stop. Today docker run -d --name lab-web nginx:1.30-alpine fails with Conflict. The container name "/lab-web" is already in use. You want the old container back, files and all. What do you run?Incorrect —
--rm does not touch the existing container, so the name is still taken, and a new container would start from a clean writable layer anyway.Incorrect — That frees the name, but
docker rm deletes the writable layer, which holds the files you wanted to keep.Correct —
docker start runs the existing container again from its own writable layer, as the marker file in the lab showed.Incorrect — Pulling affects the image, not the container. The name clash stays exactly as it was.
02
docker ps is empty, yet docker rmi nginx:1.30-alpine fails with container cc2929c71cc1 is using its referenced image. What is going on?Correct — Docker will not delete an image that any container uses, running or stopped. Remove the containers first, then the image.
Incorrect — No pull is running. The message names a container ID, not a download.
Incorrect — A second tag makes
rmi print Untagged and keep the content. It does not cause a container conflict.Incorrect — Containers are not stored inside images. The conflict is with a real container that still exists.
03You run
docker tag nginx:1.30-alpine lab-nginx:mine, and later docker rmi lab-nginx:mine prints only Untagged: lab-nginx:mine. How much disk space did that free?Incorrect — Those 92.9MB belong to content that
nginx:1.30-alpine still references, so they stay.Incorrect —
docker tag made no copy. Both names showed the same image ID.Incorrect — Layers are not split between tags. The content stays whole while any name points to it.
Correct — Docker deletes content, and prints
Deleted: sha256:..., only when the last reference goes.04A CI step runs
docker run --name migrate myapp:3 ./migrate.sh, and the shell reports exit status 125. Which part failed?Incorrect — A kill by a signal gives 128 plus the signal number, such as 137 or 143.
Correct — 125 is the code for a failure of
docker itself, as with the name clash in the lab; the script never ran.Incorrect — That is 126: the command exists but cannot be executed.
Incorrect — That is 127. The container is created, but its process never starts.
05
docker ps shows an nginx container as Up with 127.0.0.1:8083->8080/tcp, and curl http://127.0.0.1:8083/ fails with Recv failure: Connection reset by peer. What fixes it?Incorrect — Publishing on loopback works; the lab served nginx on
127.0.0.1:8080. The problem is the container side of the mapping.Incorrect — nginx starts in well under a second, and it never listens on 8080 however long you wait.
Correct — The right-hand number must be the port the program inside listens on, and nginx listens on 80.
Incorrect — The connection to the host port succeeded. The reset came from the forward into the container, where nothing listened on 8080.
06A container runs
sh -c 'echo started; exit 1' with --restart on-failure:3. A minute later, how many started lines does docker logs show?Correct — The daemon restarted it three times, then gave up and left it exited with status 1.
Incorrect — The limit counts restarts. The first start is not a restart, so there is one more line.
Incorrect — The log keeps the output of every run of the same container; the lab showed four lines.
Incorrect — That describes
always or unless-stopped. on-failure:3 stops after three restarts.07
docker logs web | grep -c error prints a pile of lines containing error straight to the terminal and then 0. What is happening?Incorrect — grep does not care about timestamps. It never received those lines.
Incorrect —
docker logs does not page. It replays the whole log unless you use --tail.Incorrect — There is no rotation by default, and the lines did print, just not through grep.
Correct —
docker logs keeps the container's stdout and stderr apart, and a plain pipe carries only stdout.08A busy host keeps running out of disk. For its chattiest container,
docker inspect -f '{{.HostConfig.LogConfig.Type}} {{.HostConfig.LogConfig.Config}}' app prints json-file map[]. What does that tell you?Incorrect —
json-file is the default driver and it is on: every line the container prints is stored.Correct — With no options the file under
/var/lib/docker/containers/<id>/ grows until the container is removed.Incorrect — That would be a different driver.
json-file writes a file per container under Docker's data directory.Incorrect — Docker captures stdout and stderr outside the container, in a root-owned file on the host.
09A CI job runs
docker run --rm -e API_URL myimg:2, but the job's environment never defines API_URL. What does the program in the container see?Incorrect — An empty value needs
-e API_URL=. With a bare name and nothing to copy, Docker leaves the variable out.Incorrect — A bare name means "copy the value from my environment", not "use the name as the value".
Correct —
-e NAME copies the value from the docker command's environment, and here there is nothing to copy.Incorrect — Docker accepts the flag. It has nothing to copy, so the container starts without the variable.
10You change
LOG_LEVEL in app.env and run docker restart app, but the app still logs at the old level, and docker inspect -f '{{json .Config.Env}}' app shows the old value. What do you do?Correct — The environment is fixed when the container is created; restarting reuses it.
Incorrect —
docker restart takes no env file. Changing a value means starting a new container.Incorrect —
docker start reuses the stored configuration just as restart does. Neither reads the file.Incorrect — Run-time values never go into the image. They are stored in the container's configuration.
11Someone with access only to the Docker host, not to the application, recovers the production database password in a minute. The container was started with
-e DB_PASSWORD=.... How, and what is the better pattern?Incorrect — History is one leak, but an env file ends up in the same container configuration that
docker inspect prints.Incorrect — An ENV value in the image is readable by anyone who has the image, with
docker image inspect.Incorrect — No network access was needed. The value is stored in plain text in the container's configuration.
Correct — Environment variables are readable by anyone who can inspect the container. Images such as postgres read the secret from a file named in a
_FILE variable.11 questions · explanations appear as you answer
Building your own images
8 questions
01A Dockerfile ends with
CMD ["node", "app.js"], but the file in the image is server.js. What happens when you build the image and run it?Incorrect — Docker does not check what CMD refers to. The build only records it in the image configuration.
Correct — Node starts, fails to load
/app/app.js and exits with status 1. docker ps -a and docker logs show it.Incorrect — 127 would mean
node itself is missing. It exists, so it starts and then fails on the missing module.Incorrect — Node never gets as far as listening; it exits as soon as it cannot load the module.
02The image
lab-hello:2, already pushed to the team registry, turns out to contain .env with a real API token. You add .env to .dockerignore and push a rebuilt image. What else is needed?Incorrect — The old image and its layers stay in the registry and on every host that pulled them.
Incorrect — A later delete only hides the file. The layer that added it still holds it.
Correct — A secret that reached a layer is in the local store, the registry and every host that pulled the image. Rebuilding does not recall those copies. Also delete the old tag and manifest from the registry so no new host pulls it.
Incorrect — That clears the local build cache only. The copies in the registry and on other hosts are untouched.
03
docker image ls lab-hello shows DISK USAGE 249MB and CONTENT SIZE 64MB. Roughly how much does a push of this image to an empty registry transfer?Correct — CONTENT SIZE is the compressed size of the layers, close to what a push or pull transfers.
Incorrect — Registries store compressed layers. DISK USAGE is the unpacked size on this host.
Incorrect — The difference has no meaning of its own; no transfer sends it.
Incorrect — An empty registry has none of the layers, so the base layers are sent too.
04A Dockerfile has
RUN apt-get update && apt-get install -y curl near the top. Months later a rebuild still installs the same old curl, although newer packages exist. Why?Incorrect — There is no such lockfile. The step did not run at all; its cached result was reused.
Incorrect — The base image does not block apt. A fresh run of the step would fetch new package lists.
Incorrect — BuildKit never looks at what a RUN step would download.
Correct — Nothing below it changed, so BuildKit reused the old result. Use
--no-cache, and --pull for a newer base, when you want fresh packages.05A Python service's Dockerfile runs
COPY . . and then RUN pip install -r requirements.txt. Every code change reinstalls all packages. Which order keeps the install cached when only code changes?Incorrect — The install needs
requirements.txt, which is not in the image yet at that point.Correct — The install now depends only on
requirements.txt, so a code change reruns just the last copy.Incorrect — The first copy still changes with every edit, so every step after it runs again.
Incorrect —
--no-cache makes BuildKit rerun every step, which is the opposite of what you want.06
docker history lab-layers:1 shows <missing> in the IMAGE column for every row except the top one. Should you worry?Incorrect — The image runs and every row has a size. The column is about IDs, not content.
Incorrect — Removed layers would make the image unusable. These are present; their sizes are listed.
Correct —
<missing> is not an error. Only the top row is an image with its own ID.Incorrect — All the layers are stored locally.
<missing> says nothing about where they came from.07An image has
ENTRYPOINT ["echo", "greeting:"] and CMD ["hello", "world"]. docker run --rm --entrypoint echo myimg prints an empty line. Why not hello world?Incorrect —
--entrypoint takes the program only; its arguments go after the image name.Incorrect — echo prints whatever arguments it receives. It received none.
Incorrect — Nothing is appended to a replaced entrypoint. The new program runs alone.
Correct — Only arguments you pass after the image name reach the new entrypoint;
new args would have printed.08Your start script writes a config file and then starts the server. Why should its last line be
exec my-server "$@" and not just my-server "$@"?Correct — Without exec the shell usually stays PID 1 (it depends on the shell), and a shell without a handler ignores the stop signal, so the server never sees it. exec makes the server PID 1 with every shell.
Incorrect —
"$@" expands the same way with or without exec. The difference is which process ends up as PID 1.Incorrect — Docker checks nothing about the programs a container will run at build time.
Incorrect — exec runs nothing in the background. The server takes over the script's process.
8 questions · explanations appear as you answer
Data, networking & Compose
11 questions
01A container writes reports into a bind-mounted
./out directory. On the host, ls -ln out shows the new files owned by 0 0, and your account cannot edit them. Why?Incorrect — Files keep the numeric IDs of the writing process. In the lab, a container run with
--user "$(id -u):$(id -g)" produced a file owned by 1000.Correct — Run the container with
--user "$(id -u):$(id -g)", or compare docker exec <name> id with ls -ln when permissions go wrong.Incorrect — Nothing is copied. A bind mount is the host directory itself, written directly.
Incorrect — The host shows numbers, not a fallback. A process running as 1000 produces files owned by 1000.
02A batch container writes large temporary files to
/scratch, and they should never land in its writable layer or in any volume. Which option fits, and what is its catch?Incorrect — That creates a named volume, which stays until you remove it explicitly.
Incorrect — A bind mount writes into a host directory. The files land on disk and stay there.
Correct — A tmpfs is memory-backed and empty on each start. Under memory pressure the kernel can still swap it to disk.
Incorrect — Read-only refuses writes with
Read-only file system; it does not redirect them anywhere.03After
docker rm -f on every container that used it, docker volume ls still lists lab-notes. Why?Correct —
docker volume rm (or docker volume prune) removes a volume; removing containers never does.Incorrect — There is no timer. The volume stays until you remove it.
Incorrect —
docker rm -f removes the containers; it only kills them first if they are running.Incorrect — Volumes are separate from images.
lab-notes was created with docker volume create.04
docker run -d --network host -p 8082:80 myweb prints WARNING: Published ports are discarded when using host network mode. What does that mean for the server inside?Incorrect —
none leaves only a loopback interface, which makes it less reachable, not more.Incorrect — In host mode no port is mapped at all, random or otherwise.
Incorrect — With host networking there is no separate network stack; the container uses the host's interfaces.
Correct — The container's ports are already the host's ports, so
-p has nothing to do. Port clashes with other host programs become possible.05On one laptop, a container cannot reach a company server at
172.18.4.20 while the VPN is connected, although the laptop itself can. Other machines are fine. What is the usual cause?Incorrect — The host already reaches the server. The trouble is that the same address range exists on both sides.
Correct — Docker takes subnets from its default pools. Give it other ranges with
default-address-pools.Incorrect — No name is involved; the address is used directly.
Incorrect — Publishing is for traffic into containers. The company server is not a container.
06A container on the user-defined network
lab-net has nameserver 127.0.0.11 in its /etc/resolv.conf. How does it resolve a public name such as a package mirror?Incorrect — Containers on such networks resolve public names fine. Names that are not containers are passed on.
Incorrect — Nothing is copied. Queries go to 127.0.0.11 and are answered or forwarded as they come.
Correct — Container names are answered locally; everything else goes to the resolver listed in the generated file.
Incorrect — 127.0.0.11 is a loopback address inside the container, served by the local engine.
07On a new server without a
.env file, docker compose up -d stops with required variable POSTGRES_PASSWORD is missing a value: set POSTGRES_PASSWORD in .env. What has Compose created so far?Incorrect — Interpolation fails while the file is read, before any resource exists.
Incorrect — Compose never got that far. A blank value is what plain
${POSTGRES_PASSWORD} would have given.Incorrect — The text after
:? is the error message, never a value.Correct —
${POSTGRES_PASSWORD:?...} marks the variable as required, and Compose exits with the message from the file.08A project's
.env file contains DEBUG=true, but compose.yaml never mentions DEBUG. The app in the container reports that DEBUG is unset. Why?Correct — Reference it in the file, or list it under
environment: or env_file:, and it reaches the service.Incorrect — Compose accepts unquoted values and even strips quotes. The variable was never referenced.
Incorrect — An unset shell variable overrides nothing. Compose looks in the shell first, then in
.env.Incorrect — There is no per-service rule.
.env reaches no container unless the file references it.09A team's Postgres health check is
pg_isready -U app -d app, with no -h. On a fresh volume, db turns healthy, yet web still fails once with ECONNREFUSED. What explains it?Incorrect — The start period changes when failures count, not what the check tests.
Correct — The temporary init server has TCP off, so a socket check can pass early. Check over TCP with
-h 127.0.0.1.Incorrect — Docker runs the health check command inside the container.
Incorrect — Compose waits for the healthy state, which needs a passing check. The check itself was too weak.
10A teammate runs
docker push acme/api:1.0, expecting it to go to the company registry at registry.example.com. Where does Docker send it?Incorrect —
config.json records logins per registry. It does not change where a name without a registry part points.Incorrect — A local registry has to be named in the reference, as in
localhost:5000/lab-notes:1.0.Correct — A first component without a dot, colon or
localhost is a namespace on Docker Hub. Tag it registry.example.com/acme/api:1.0.Incorrect — The base image plays no part. The name alone decides where a push goes.
11You are logged in to the company registry, and
docker push ends with denied rather than no basic auth credentials. What does that point to?Correct — Usually a wrong namespace, or an account without push rights on it.
Incorrect — Missing credentials give
no basic auth credentials. denied means you were recognised and the action was refused.Incorrect — The pull limit answers
429 Too Many Requests, and this is a push to another registry.Incorrect — The message is an authorization answer; size has nothing to do with it.
11 questions · explanations appear as you answer