Home / Alt manpages / dockerfile(5)

  • dockerfile(5)
  • File format
  • linux

Build a Reproducible Docker Image from a Dockerfile

In about 15 minutes, you will turn a small application directory into a tagged Docker image, run it, inspect the result, and understand which files and values entered the build. The examples use Docker 29.8.1, supplied here by docker-ce-cli. You need a working Docker engine, the docker command, and a shell. Building an image normally needs access to the Docker daemon; if your account is not allowed to use it, use your site's approved Docker access method or an authorised sudo command.

Checkpoint: choose a clean build directory

  1. Create a directory containing only the files intended for the image, then enter it.
mkdir docker-demo
cd docker-demo
cat > server.sh <<'EOF'
#!/bin/sh
printf 'hello from the image\n'
EOF
chmod 755 server.sh

The final argument to docker build is the build context. With ., the whole current directory is available to build instructions such as COPY. That makes the directory boundary an easy place to make a mistake: credentials, build artefacts and large dependency trees can be sent to the builder unless excluded.

Step 1: exclude accidental input

Create a .dockerignore before building. These patterns are examples; adjust them to the files your project actually uses.

cat > .dockerignore <<'EOF'
.git
.env
*.log
node_modules
EOF

A Dockerfile-specific ignore file takes precedence over the context's root .dockerignore. Docker still needs the Dockerfile and ignore file to perform the build, but an ignore rule is not a mechanism for copying those files into the image. Treat ignore patterns as a reviewable security boundary, not merely a speed optimisation.

Checkpoint

Run find . -maxdepth 2 -type f -print and confirm that no private key, token or production configuration is in the context. The command is read-only.

Step 2: write the Dockerfile

FROM starts a build stage. WORKDIR makes later paths predictable, COPY imports a file from the context, RUN executes during the build, and CMD supplies the default command when a container starts.

cat > Dockerfile <<'EOF'
FROM alpine:3.20
WORKDIR /app
COPY server.sh ./server.sh
RUN /bin/sh -n ./server.sh
CMD ["/app/server.sh"]
EOF

The exec form of RUN and CMD is a JSON array, so it requires double quotes. The RUN check is performed while building and creates a layer; CMD is not run until a container starts. This distinction prevents a common debugging error: changing CMD does not test the command at build time.

Pinning a base image tag is clearer than silently accepting an unexamined default, but a tag can still move. For a release build, record and review a digest, for example alpine:3.20@sha256:REPLACE_WITH_A_REVIEWED_DIGEST. Do not paste an unverified digest into production.

Step 3: build and tag the image

Run this as your ordinary account. Add sudo only when that is the established way your machine grants Docker daemon access.

docker build --tag docker-demo:local .

Successful output ends with an image export or naming line similar to:

Successfully tagged docker-demo:local

The exact progress format depends on the builder, so use the exit status as the first check. The context shown in the output should be small enough to match the files you intended to send.

If a build fails with a missing file error, check whether the source is outside the context or excluded by .dockerignore. If a command fails in RUN, reproduce that command in a temporary container based on the same base image rather than weakening the Dockerfile blindly.

Step 4: run and verify the result

Start a short-lived container. This changes Docker daemon state only while the container exists.

docker run --rm docker-demo:local

Expected output:

hello from the image

--rm removes the stopped container automatically. If you omitted it and need to clean up a stopped test container, list it with docker ps --all, then remove the specific ID with docker rm CONTAINER_ID. Check the ID before removing anything; docker rm is destructive for that container's writable layer.

Inspect the image's configuration and size:

docker image inspect docker-demo:local
docker image ls docker-demo:local

Look for the configured working directory, entry command and image ID in the first command. The second should show the local tag and a size. These checks do not prove that an application is healthy, but they confirm that the intended Dockerfile metadata reached the image.

Step 5: handle values and secrets deliberately

Use ARG for a build-time value and ENV for a value that should persist into containers. For example:

ARG APP_VERSION=dev
ENV APP_VERSION=$APP_VERSION

Build with docker build --build-arg APP_VERSION=1.2.3 --tag docker-demo:1.2.3 .. The ARG is not automatically present in the final container, while the ENV value is. An ENV with the same name overrides the argument from its point of definition.

Security boundary

Do not put passwords, private keys or API tokens in ARG, ENV, the Dockerfile, or a copied file. Build arguments can be exposed by image history and build provenance, and environment values remain in image configuration. Use the builder's secret-mount facility for a secret that must be available during a build, then verify that the resulting layers contain no secret.

Common traps and recovery

  • Wrong context: docker build -f path/to/Dockerfile . uses the current directory as context, not the Dockerfile's directory. Pass the intended context explicitly.
  • Shell versus exec form: RUN echo $VALUE is interpreted by a shell, while RUN ["echo", "$VALUE"] does not invoke one automatically. Use the form that matches the command's needs.
  • Unexpected cache: use docker build --no-cache --tag docker-demo:clean . when investigating stale build results. Add --pull as well when you deliberately want to check for a newer base image. This can download and rebuild substantially more, so do not use it for every local edit.
  • Too much input: refine .dockerignore and rebuild. Never solve a context problem by copying an entire home directory or repository into the image.

Done means

  • Dockerfile starts with a reviewed FROM and uses an explicit WORKDIR.
  • .dockerignore excludes credentials, VCS data and irrelevant large files.
  • docker build --tag docker-demo:local . exits successfully.
  • docker run --rm docker-demo:local prints the expected result.
  • You inspected the image and confirmed that no secret was supplied through the build context, ARG or ENV.