Kahibaro
Discord Login Register

6.4 Building Images with `docker build`

Understanding the `docker build` Process

Before you can run your own application in a container, you need to turn your Dockerfile into an image. This is exactly what the docker build command does. It reads the instructions in your Dockerfile, executes them step by step, and produces a new image that you can tag and run later.

Every docker build has two main ingredients. The first is the Dockerfile itself, which describes how to build the image. The second is the build context, which is the set of files that docker build sends to the Docker daemon. Usually, the build context is a directory on your machine, and everything inside it can potentially be used by the Dockerfile, for example with COPY or ADD.

It is important to understand that the Docker daemon receives a snapshot of the build context when the build starts. Files that are not in the build context are not available during the build, and changes you make to the files after the build starts are not seen until the next build.

During a build, the Docker daemon only sees the files in the build context that you passed to docker build, and only as they were at the start of the build.

Basic `docker build` Syntax

The simplest form of the command has three parts: optional flags, the build context path, and sometimes the Dockerfile location. A very common shape is:
docker build -t my-image:latest .

Here, . means "use the current directory as the build context" and, by default, Docker looks for a file called Dockerfile inside that directory. The -t flag gives the resulting image a name and optional tag so that you can reference it later when you run or push it.

You can also specify a different Dockerfile name or location with the -f option. For example, if you keep multiple Dockerfiles side by side you might run:
docker build -f Dockerfile.dev -t my-app:dev .

In this case, the context is still the current directory, but Docker reads instructions from Dockerfile.dev.

The build context path at the end of docker build is not the location of the Dockerfile. It defines which files are sent to the Docker daemon and must contain any files you want to COPY or ADD during the build.

Build Context and `.dockerignore`

Because the entire context is sent to the Docker daemon, it affects build performance. Large contexts, for example including build artifacts, logs, or dependency caches you do not need in the image, slow down the build and can expose unnecessary files inside the Docker build environment.

To control this, Docker supports a special file named .dockerignore in the root of the build context. This file works similarly to .gitignore. Each pattern in .dockerignore describes files and directories that should not be included in the build context. Once ignored, these files cannot be copied by COPY or ADD within the Dockerfile.

Typical entries in .dockerignore include version control directories, temporary files, local build outputs, and editor settings. Keeping the context small makes builds faster and can also reduce accidental leakage of secrets that might live in your working directory.

Files excluded by .dockerignore are not sent to the Docker daemon at all. You cannot use COPY or ADD to bring them into the image.

Tagging Images During Build

The -t flag, or --tag, assigns a human friendly name and optional tag to your image as soon as it is built. Tags have the form name:tag. If you omit the tag part, Docker silently uses latest.

For example, you can build multiple variants of the same application like this:
docker build -t my-app:1.0 .
docker build -t my-app:test .

Both commands produce images named my-app, but with different tags. This is extremely useful when you want to test experimental changes without overwriting a stable version, or when you need to push specific versions to a registry.

You are not limited to a single tag per build. You can repeat -t multiple times. For instance:
docker build -t my-app:1.0 -t my-app:stable .

This produces a single image object that can be addressed by either tag.

If you do not specify -t, the image is only identified by an auto generated ID. Always tag images you intend to reuse or share.

Controlling the Dockerfile Location

Although Docker looks for a file literally called Dockerfile in the context directory by default, you often need more flexibility. With the -f flag you can point to any file containing Dockerfile instructions.

There are two common patterns. The first is to keep the Dockerfile next to the application source code but under a different name, such as Dockerfile.prod. The second is to keep Dockerfiles in a separate directory yet still send the application code as context.

For example, to use a Dockerfile stored in docker/Dockerfile.web while sending the app/ directory as context, you might run:
docker build -f docker/Dockerfile.web -t my-web:latest app/

Here, app/ must contain all the files that the Dockerfile expects to copy, even though the Dockerfile itself lives in another directory.

The -f option controls only which file Docker reads for instructions. The final positional argument always controls which directory is used as the build context.

Build Output and Layers

During a build, Docker shows each instruction from the Dockerfile and gives it a numeric step identifier. For instance, you might see "Step 3/7" followed by the instruction itself and some logs from the command that runs. Each instruction that changes the filesystem creates a new image layer.

If the build completes successfully, Docker reports the image ID and the tags you applied. If any instruction fails, the build stops at that step and the image is not produced. The logs from that failing step are essential clues for debugging issues like incorrect paths, missing dependencies, or network problems when downloading packages.

The text output also shows when Docker reuses a cached layer. In such cases, you see a message similar to "Using cache" instead of rerunning the command. This caching behavior is managed by Docker and depends on how you structure your Dockerfile and the contents of previous build steps.

Using Build Arguments

Sometimes you want to pass configuration values into the build without hardcoding them in the Dockerfile. For that purpose, Docker supports build arguments, also known as ARG. The Dockerfile declares which arguments exist, and docker build lets you specify their values at build time.

For example, inside the Dockerfile, you might have:
ARG APP_ENV
RUN echo "Environment is $APP_ENV"

Then, when you build, you can supply the value like this:
docker build --build-arg APP_ENV=production -t my-app:prod .

If you do not pass a value explicitly and the ARG has no default in the Dockerfile, it will be empty in the build environment. Build arguments only affect the build process and are not automatically preserved as environment variables at runtime. For handling runtime configuration you need different mechanisms that are addressed elsewhere in the course.

Build arguments are available only while the image is being built. They do not automatically become environment variables inside running containers.

Caching and Rebuilding Images

A key feature of docker build is its layer cache. When you rebuild an image, Docker compares each instruction in the Dockerfile, together with the data it depends on, against what it has built before. If it finds an exact match, it can reuse the previously built layer instead of executing the instruction again. This can dramatically speed up incremental builds, especially when downloading large dependencies or compiling code.

However, the cache works in a strict order. If one instruction changes, all subsequent instructions are rebuilt because their dependencies now differ. This is why the order of instructions in your Dockerfile matters for performance. Instructions that change frequently should usually appear later, so that earlier expensive steps can still be reused.

There is also a way to completely ignore the cache and force a full rebuild. The --no-cache flag tells Docker to execute each instruction again, regardless of any matching layers. For example:
docker build --no-cache -t my-app:clean .

This is useful when you suspect the cache is hiding a problem, or when external resources that do not appear to change in the Dockerfile should be refreshed, such as package repositories.

Docker rebuilds all instructions after the first change it detects. Place frequently changing instructions later in the Dockerfile if you want faster rebuilds.

Common `docker build` Options

Although docker build has many flags, a few are especially common in day to day usage beyond -t, -f, --build-arg, and --no-cache.

The --pull option asks Docker to always attempt to pull a newer version of the base image specified in the FROM instruction, even if a local copy exists. This is useful when you want to ensure you are building on top of the latest security updates or bug fixes in the base image:
docker build --pull -t my-app:latest .

The --progress option can control how much detail you see during the build. It can be set to values like auto, plain, or tty, which change how logs and steps are presented, particularly when running inside CI systems or limited terminals.

Another option is --target, which is specifically related to multi stage builds. It lets you stop the build at a specific stage defined in the Dockerfile. The details and uses for this are covered when multi stage builds are introduced, but it is important to know that docker build is the tool that triggers those stages.

When builds run inside automated systems like CI servers, you might also see flags related to output or platforms, such as --output or --platform. These are more advanced uses and depend heavily on the environment where your builds happen.

Verifying and Using the Built Image

After a successful run of docker build, the resulting image can be found with the general image listing command. Your chosen tag lets you recognize it immediately. From there, you can start containers from it, inspect it, or push it to a registry so others can pull and run it.

At this point, the docker build command has completed its role. It took a Dockerfile and a build context, executed a series of reproducible steps, and produced an image that packages your application together with everything it needs to run.

In practice you will run docker build repeatedly as you develop and refine your Dockerfiles. Most improvements in build speed and reliability will come from a combination of thoughtful Dockerfile structure and a careful understanding of how the build context, tagging, caching, and arguments work together during each build.

Views: 6

Comments

Please login to add a comment.

Don't have an account? Register now!