✦ For everyone, free.

Practical knowledge for real and everyday life

Home

20.1.2.5 First Image Build Run

A focused guide to First Image Build Run, connecting core concepts with practical Docker and container operations.

Running your first docker build takes a Dockerfile and produces a runnable image. This process involves sending the build context to the Docker daemon, executing each Dockerfile instruction in sequence, caching the resulting layers, and naming the image so you can reference it later. Understanding what happens during each phase of a build run makes the output easier to interpret and errors easier to diagnose.

The Build Command

The minimal form of docker build is:

docker build .

The . specifies the build context — the directory Docker packages and sends to the build daemon. The Dockerfile is expected to be in that directory. Without -t, the image is built but assigned no name, only a content-addressed hash that is hard to reference.

The standard form for a first build:

docker build -t my-image .

-t my-image tags the image as my-image:latest. Docker adds :latest automatically when no explicit tag is provided.

Specifying a Dockerfile Location

By default, Docker looks for a file named Dockerfile in the build context directory. To use a differently named file or one in a different location:

docker build -t my-image -f Dockerfile.dev .
docker build -t my-image -f /path/to/Dockerfile .

The build context (.) and the Dockerfile path are independent. The Dockerfile can be anywhere; the context is what defines which files COPY can access.

Build Output Explained

A typical build run prints progress for each step:

[+] Building 14.2s (8/8) FINISHED
 => [internal] load build definition from Dockerfile                      0.0s
 => [internal] load .dockerignore                                         0.0s
 => [internal] load metadata for docker.io/library/node:20-alpine         1.3s
 => [1/4] FROM node:20-alpine@sha256:abc...                               0.0s
 => CACHED [2/4] WORKDIR /app                                             0.0s
 => [3/4] COPY package.json .                                             0.1s
 => [4/4] RUN npm install                                                11.8s
 => exporting to image                                                    0.9s
 => => exporting layers                                                   0.9s
 => => writing image sha256:def...                                        0.0s
 => => naming to docker.io/library/my-image:latest                       0.0s

Line by line:

  • load build definition — Docker reads the Dockerfile.
  • load .dockerignore — Docker reads the exclusion list before packaging the context.
  • load metadata — Docker fetches metadata for the base image from the registry (or local cache).
  • FROM — Docker resolves the base image. If it is already local, no download happens.
  • CACHED — This layer was already built and its inputs have not changed. Docker reuses the stored layer.
  • COPY / RUN steps — Layers being built fresh. The time shown is how long each step took.
  • exporting to image — Docker assembles the layer stack into the final image.
  • naming — Docker applies the -t tag.

Layer Caching

Docker caches every layer it builds. On subsequent builds, Docker checks whether a layer's instruction and inputs have changed. If nothing changed, the layer is marked CACHED and skipped. If something changed, that layer and all layers below it in the Dockerfile are rebuilt.

First build — no cache, all steps execute:

docker build -t my-image .
 => [1/4] FROM node:20-alpine        1.3s
 => [2/4] WORKDIR /app               0.1s
 => [3/4] COPY package.json .        0.1s
 => [4/4] RUN npm install           11.8s

Second build — nothing changed, all steps cached:

docker build -t my-image .
 => CACHED [1/4] FROM node:20-alpine    0.0s
 => CACHED [2/4] WORKDIR /app           0.0s
 => CACHED [3/4] COPY package.json .    0.0s
 => CACHED [4/4] RUN npm install        0.0s

Total build time is under one second because all work is cache hits.

Third build — only source files changed (not package.json):

docker build -t my-image .
 => CACHED [1/4] FROM node:20-alpine    0.0s
 => CACHED [2/4] WORKDIR /app           0.0s
 => CACHED [3/4] COPY package.json .    0.0s
 => CACHED [4/4] RUN npm install        0.0s
 => [5/5] COPY . .                      0.1s

The npm install layer remains cached because package.json did not change.

Forcing a Full Rebuild

To bypass the cache and rebuild all layers from scratch:

docker build --no-cache -t my-image .

This is useful when a RUN instruction fetches external content (like apt-get install) and you want to ensure the latest packages are included, even though the Dockerfile line itself has not changed.

Running the Built Image

After a successful build, run the image:

docker run my-image

This executes the CMD defined in the Dockerfile. To run it in the background with a published port:

docker run -d -p 3000:3000 my-image

To verify the container is running:

docker ps
CONTAINER ID   IMAGE      COMMAND             STATUS         PORTS
a1b2c3d4e5f6   my-image   "node server.js"    Up 3 seconds   0.0.0.0:3000->3000/tcp

Tagging During Build

You can apply multiple tags in a single build command:

docker build -t my-image:latest -t my-image:1.0 .

Both tags reference the same image. Tags are pointers, not copies — the image data is stored once.

To tag an existing image separately:

docker tag my-image:latest my-image:stable

Build Context Size and Timing

The first line of the build output shows how long Docker took to load the build context. A large build context (caused by including node_modules, .git, or other large directories) slows every build, even when all layers are cached. The context must be transferred to the build daemon before any cache check occurs.

Monitor context size by watching the first output line:

 => [internal] load build context     3.4s
 => => transferring context: 48.22MB  3.4s

A 48MB context transfer on every build indicates a missing .dockerignore entry. The goal is to keep context transfer under a few hundred kilobytes for typical application projects.

Diagnosing a Failed Build

If a step fails, Docker prints the error output from the failing command and stops:

 => [3/4] RUN npm install                                                 2.1s
------
 > [3/4] RUN npm install:
npm error code ENOENT
npm error syscall open
npm error path /app/package.json
------
ERROR: failed to solve: process "/bin/sh -c npm install" did not complete successfully: exit code: 1

The error message is the stdout/stderr of the failing command. In this example, npm install could not find package.json because the COPY package.json . instruction was missing or placed after the RUN instruction.

Fix the Dockerfile, then run docker build again. Docker replays only the changed and uncached layers.

Verifying the Image After Build

docker images my-image
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
my-image     latest    c1d2e3f4a5b6   15 seconds ago   142MB

To see the layers:

docker history my-image

To inspect the image configuration (CMD, ENV, exposed ports, entrypoint):

docker image inspect my-image

The Config section of the JSON output shows the instruction-defined settings. The RootFS.Layers array shows the SHA256 hashes of every layer in the image.