TT Lab
Get started
Learn Learning paths Courses

Building Images

Building a Dockerfile One Line at a Time

Continue in TT Lab

This lab runs on a real VM

This box is not a Pod but a virtual machine launched by KubeVirt. A separate Linux kernel runs in it, systemd actually manages services, and docker is a real Docker engine, not an imitation. A container started with docker run becomes a real process, and both docker exec and docker logs work as usual.

This lab used to run inside a Pod. That box had dropped every kernel capability, so the step that starts a container was blocked, and you learned by working around it and unpacking image archives by hand. The workaround is no longer needed.

There are two things to know.

Goal

You add Dockerfile instructions one at a time, check with inspect which part of the image each one changes, and finally create a file that passes the review checklist.

Why it matters

A Dockerfile looks like a configuration file, but it is actually a build script in which order carries meaning. Even with the same instruction, depending on its position the layers grow, the cache breaks, and secrets get baked in. The purpose of this lab is not to memorize the instructions but to build the habit of asking each time, "does this line create a layer or stamp a configuration?" That one distinction prevents most size problems and security problems.

Steps

At each step, build with a new tag so that the results of the earlier steps are kept.

  1. Create /root/build1 and write the first line of /root/build1/Dockerfile as FROM alpine:3.20.
  2. Build it as labhub/app:v1.
  3. Create /root/build1/app.sh so that it prints app-running, add WORKDIR /app and COPY app.sh /app/ to the Dockerfile, and build it as labhub/app:v2.
  4. Add CMD in JSON array form so that app.sh runs, and build it as labhub/app:v3. When you run it, app-running should appear.
  5. Add ENV APP_ENV=prod and ARG VERSION, and stamp the value received through ARG into LABEL app.version. Using --build-arg VERSION=2.1.0, build labhub/app:v4.
  6. Set ENTRYPOINT in array form and CMD ["default-arg"] as the default, so that running without arguments prints default-arg and passing override-arg prints override-arg, then build it as labhub/app:v5.
  7. Add LABEL org.opencontainers.image.title=labhub-app and EXPOSE 8080, and build it as labhub/app:v6.
  8. Polish the final /root/build1/Dockerfile so that it satisfies all five items below, and build it as labhub/app:v7.
    • FROM has a pinned tag and it is not latest
    • WORKDIR comes before the first COPY
    • ENTRYPOINT or CMD is a JSON array
    • ENV/ARG names do not contain TOKEN, SECRET, PASSWORD or API_KEY

Notes

Pin the base image

Create /root/build1 and write the first line of /root/build1/Dockerfile as FROM alpine:3.20.

Specify a tag in FROM. latest makes yesterday's build and today's build differ.

First build

Build it as labhub/app:v1.

For the build context, specify the directory that contains the Dockerfile. If you do not name it with -t, it is hard to find later.

Working directory and copying files

Create /root/build1/app.sh so that it prints app-running, add WORKDIR /app and COPY app.sh /app/ to the Dockerfile, and build it as labhub/app:v2.

WORKDIR is stamped into the image configuration (Config.WorkingDir), and COPY creates a layer. The order of the two instructions determines the meaning of relative paths.

Specify the default command

Add CMD in JSON array form so that app.sh runs, and build it as labhub/app:v3. When you run it, app-running should appear.

In the shell form it is wrapped in /bin/sh -c and the shell becomes PID 1. You saw why the array form is needed in the previous course.

The difference between build arguments and environment variables

Add ENV APP_ENV=prod and ARG VERSION, and stamp the value received through ARG into LABEL app.version. Using --build-arg VERSION=2.1.0, build labhub/app:v4.

ARG lives only while building, and ENV remains in the image. To keep a value received as a build argument in the image, stamp it with LABEL.

Combining ENTRYPOINT and CMD

Set ENTRYPOINT in array form and CMD ["default-arg"] as the default, so that running without arguments prints default-arg and passing override-arg prints override-arg, then build it as labhub/app:v5.

ENTRYPOINT is fixed, and CMD is replaced by the arguments given at run time. Write both in array form.

Standard label and port declaration

Add LABEL org.opencontainers.image.title=labhub-app and EXPOSE 8080, and build it as labhub/app:v6.

OCI standard label keys start with org.opencontainers.image. EXPOSE does not open a port; it is an instruction that documents it.

Pass the review checklist

Polish the final /root/build1/Dockerfile so that it satisfies all five items below, and build it as labhub/app:v7.

Polish the Dockerfile you have so far to fit the five items. Grading looks at both the file contents and the build result.