✦ For everyone, free.

Practical knowledge for real and everyday life

Home

20.1.2 First Image Lesson

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

Building your first Docker image means writing a Dockerfile, running docker build, and understanding what the resulting image contains and how it was assembled. This lesson walks through the complete process from an empty directory to a runnable container, explaining every step.

What a Dockerfile Is

A Dockerfile is a plain text file with no extension. It contains a sequence of instructions that Docker executes in order, each one building on the previous, to produce a final image. Every instruction that modifies the filesystem creates a new layer in the image.

When you run docker build, Docker reads the Dockerfile, executes the instructions in sequence, and saves the resulting image to local storage. That image can then be run with docker run, pushed to a registry, or used as the base for another image.

A Minimal Dockerfile

Create a directory for the project and write a Dockerfile inside it:

mkdir my-first-image
cd my-first-image

Create a file named Dockerfile with the following content:

FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl
CMD ["curl", "--version"]

This Dockerfile has three instructions:

  • FROM ubuntu:22.04 — specifies the base image. Every Dockerfile starts with FROM. The base image provides the starting filesystem. Here, the Ubuntu 22.04 image is used.
  • RUN apt-get update && apt-get install -y curl — executes a shell command inside the container during the build. This installs the curl tool into the image.
  • CMD ["curl", "--version"] — defines the default command to run when a container is started from this image. This is not executed at build time; it runs when someone does docker run.

Building the Image

From the directory containing the Dockerfile:

docker build -t my-curl-image .
  • -t my-curl-image — tags the image with the name my-curl-image. Without a tag, Docker assigns a random ID that is hard to reference later.
  • . — the build context. Docker sends the contents of this directory to the build daemon. The Dockerfile is expected to be at the root of the build context.

Docker prints the build progress:

[+] Building 12.3s (6/6) FINISHED
 => [internal] load build definition from Dockerfile
 => [internal] load metadata for docker.io/library/ubuntu:22.04
 => [1/2] FROM ubuntu:22.04
 => [2/2] RUN apt-get update && apt-get install -y curl
 => exporting to image
 => => naming to docker.io/library/my-curl-image:latest

Each step corresponds to a Dockerfile instruction. The numbers indicate the layer being built.

Verifying the Image Exists

docker images
REPOSITORY       TAG       IMAGE ID       CREATED         SIZE
my-curl-image    latest    a1b2c3d4e5f6   1 minute ago    152MB
ubuntu           22.04     789abc012def   3 weeks ago     77.8MB

The new image appears at the top. The ubuntu:22.04 base image is also listed because it was pulled to serve as the base.

Running a Container from the Image

docker run my-curl-image

This runs the CMD from the Dockerfile: curl --version. The output shows the curl version installed in the image, then the container exits.

To override the default command:

docker run my-curl-image curl https://example.com

Adding an Application File

Most real images include application files. Add a script to the image using COPY:

FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl
COPY fetch.sh /usr/local/bin/fetch.sh
RUN chmod +x /usr/local/bin/fetch.sh
CMD ["/usr/local/bin/fetch.sh"]

Create fetch.sh in the same directory as the Dockerfile:

#!/bin/bash
curl -s https://example.com | head -5

COPY fetch.sh /usr/local/bin/fetch.sh copies the file from the build context (the . directory on the host) into the image at the specified path.

Build and run:

docker build -t my-fetch-image .
docker run my-fetch-image

How Layers Work During Build

FROM ubuntu:22.04 (base layer) RUN apt-get install curl (layer 1) COPY fetch.sh (layer 2) RUN chmod +x (layer 3) Bottom Top

Each instruction that modifies the filesystem adds a layer. The layers are stacked from bottom to top. Docker caches each layer — if a layer's instruction and inputs have not changed since the last build, Docker reuses the cached layer instead of re-executing the step. This makes rebuilds fast when only later instructions change.

Layer Caching in Practice

If you modify fetch.sh and rebuild, Docker reuses the cached layers for FROM and RUN apt-get install, then re-executes only the COPY and RUN chmod steps, because those are the first steps affected by the change.

If you change the RUN apt-get install instruction, Docker invalidates that layer and all layers after it, rebuilding from that point forward.

Tagging with a Version

You can apply multiple tags to the same image:

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

Or tag an existing image:

docker tag my-fetch-image:1.0 my-fetch-image:stable

Inspecting the Image

docker image inspect my-fetch-image

This outputs a JSON object with the image's configuration: the layers, the environment variables, the entrypoint, the command, the OS and architecture, and the creation timestamp.

To see the layer history:

docker history my-fetch-image
IMAGE          CREATED        CREATED BY                                SIZE
a1b2c3d4e5f6   1 minute ago   /bin/sh -c chmod +x /usr/local/bin/...   0B
b2c3d4e5f6a1   1 minute ago   /bin/sh -c #(nop) COPY file:...           52B
c3d4e5f6a1b2   2 minutes ago  /bin/sh -c apt-get update && apt-get...   74.2MB
789abc012def   3 weeks ago    /bin/sh -c #(nop)  CMD ["bash"]           0B

Each row corresponds to a layer. The size column shows how much each layer adds to the image.

Removing the Image

docker rmi my-fetch-image

If containers based on the image are still running or stopped, Docker refuses to remove it. Remove containers first:

docker rm <container_id>
docker rmi my-fetch-image

The .dockerignore File

The build context (.) is sent entirely to the Docker daemon. If the directory contains large files or sensitive data that should not be included, create a .dockerignore file:

node_modules
*.log
.env
.git

Entries in .dockerignore are excluded from the build context before it is sent to the daemon, which reduces build time and prevents unintended files from entering the image.

Content in this section