7.2 Write a Runnable Dockerfile
Prerequisites: Images, Containers, and Run Boundaries
Estimated time: 20 minutes
Exercise environment: Docker Engine; access to the registry is required when using BusyBox for the first time
You are ready to continue when: you can explain build context, caching, start command, and non-root users, and build a minimal image
You can now run an existing image. Write a Dockerfile to make your own application files, dependencies, user, and startup command reviewable.
A Dockerfile describes how to build an image. It doesn't copy your entire computer, and it doesn't automatically include files outside of the build context.
A Runnable Minimum Project
Start by creating an isolated directory, keeping all build context inside it:
image_workdir=$(mktemp -d)
image_tag="workshop-ch07-message:${RANDOM}-${RANDOM}"
mkdir -p "$image_workdir/workshop-image"
cd "$image_workdir/workshop-image"Place three files in the directory:
workshop-image/
├── Dockerfile
├── .dockerignore
└── message.txtmessage.txt:
portable workshopDockerfile:
FROM busybox:latest
WORKDIR /app
COPY --chown=65532:65532 message.txt .
USER 65532:65532
ENTRYPOINT ["cat"]
CMD ["/app/message.txt"].dockerignore:
.git
*.log
.envcd "$image_workdir/workshop-image"
docker build -t "$image_tag" .
docker run --rm "$image_tag"
docker run --rm --entrypoint id "$image_tag" -uYou should first see portable workshop, followed by the numeric user 65532. The COPY --chown and numeric UID are relevant for Linux containers; Windows containers use a different user and permission model. The numeric UID does not need to have a corresponding user account in the image. If the application relies on the home directory or user database, explicitly create the user.
What Does Each Instruction Do
| Instruction | Stage | Function |
|---|---|---|
FROM | Build | Select a base image and start the build stage |
WORKDIR | Build and Run Configuration | Set up subsequent instructions and the default starting directory |
COPY | Build | Copy files from the build context; cannot read arbitrary host paths beyond the context |
RUN | Build | Execute build steps and form the result layer |
USER | Build and Run Configuration | Set the user for subsequent RUN, as well as the user and group for the default container process |
EXPOSE | Metadata | Describe the ports that the application listens on, without publishing the ports |
ENTRYPOINT | Run Configuration | Define the main executable |
CMD | Run Configuration | Define the default command, or provide default parameters for ENTRYPOINT |
Prefer the JSON exec form, such as CMD ["java", "-jar", "app.jar"]. It does not add an extra layer of shell, the parameter boundaries are clear, and the main process is easier to receive signals sent by docker stop. If a shell form is indeed needed, understand the impact of PID 1 and exec.
Build Context with .dockerignore
The final . in docker build -t name . specifies the build context, not merely the directory containing the Dockerfile. The source path for COPY is parsed from the context, and the Docker client may also need to send the context to the remote builder.
Use .dockerignore to exclude .git, logs, test artifacts, dependency directories, and .env. It can minimize context, reduce cache invalidations, and lower the chance of accidentally sending secrets into the builder. Don't treat it as a secret management system; credentials should use BuildKit secret mount, not ARG, ENV, or regular COPY.
Accurate Rules for Caching
latest is a movable tag, so repeated builds may use a different base image. Record and pin a reviewed digest when reproducing an exact environment. The builder checks whether each step can reuse its cache. COPY / ADD cache checks account for file contents and relevant metadata, but ignore modification time (mtime) itself; once a layer is invalidated, subsequent layers need to be rebuilt. Placing dependencies that change infrequently before sources that change frequently is usually beneficial, but "appearing unchanged" does not equate to a cache error.
Debugging steps:
docker build --progress=plain -t "$image_tag" .
docker image inspect "$image_tag"
docker history "$image_tag"First confirm the build context, labels, and actual running image. --no-cache can be used for diagnosis, but is not the preferred choice for daily cache troubleshooting; it also does not automatically pull updated base images.
Multi-Stage Skeleton for a Java Project
Build tools should be present during the build stage, and the run stage should only retain what is necessary for execution:
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /src
COPY pom.xml .
RUN --mount=type=cache,target=/root/.m2 mvn -B dependency:go-offline
COPY src ./src
RUN --mount=type=cache,target=/root/.m2 mvn -B package
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /src/target/app.jar /app/app.jar
USER 10001:10001
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]This is a partial skeleton: it needs BuildKit support for RUN --mount, a complete POM and source tree, and the actual artifact name. app.jar must also have a valid Main-Class and bundled or configured runtime dependencies. An ordinary mvn package does not guarantee that java -jar will work. Multi-stage builds can exclude Maven and source code, but the final size of the image depends on the base image, dependencies, and artifacts, and cannot be guaranteed to be a specific number in advance. When releasing, it is also important to choose a supported version and use an immutable digest or supply chain strategy to control changes to the base image.
Clean Up This Lesson Resources
docker image rm "$image_tag"
cd
ls -ld -- "$image_workdir"
rm -r -- "$image_workdir"Only delete the tags and temporary directories created in this lesson. Do not use docker system prune as the command to clean up the tutorial, as it may affect the cache of other projects and stopped containers. Details are specified in Dockerfile reference.
Once the output and UID match the expected values, the single-image build and run exercise is complete. Next, use Compose to define a common project boundary for a group of services.