Kahibaro
Discord Login Register

6.3 Common Instructions (`FROM`, `RUN`, `COPY`, `CMD`, `ENTRYPOINT`)

Overview

In a Dockerfile you describe how to build an image, step by step. While there are many possible instructions, a small group appears in almost every Dockerfile and shapes how the final image behaves. These are FROM, RUN, COPY, CMD, and ENTRYPOINT. Understanding the specific role of each of these instructions, and how they interact with each other, is essential to creating predictable and maintainable images.

This chapter focuses only on these core instructions and how to use them correctly. Details about overall Dockerfile structure, build commands, and best practices are covered in their own chapters.

The `FROM` Instruction

Every Dockerfile begins with a base image. The FROM instruction chooses that base and defines the starting point for the rest of the build. What you choose here controls what tools, libraries, and operating system are already present before any of your own instructions run.

A simple example is:

dockerfile
FROM python:3.12-slim

This tells Docker to start from the python:3.12-slim image, which already includes a Python runtime on top of a minimal Linux userland. All later instructions in the same Dockerfile will modify this base.

You can also refer to images by digest instead of by tag if you need a more stable reference, but the details of tags and digests are covered elsewhere.

It is possible to have multiple FROM instructions in one Dockerfile to create separate build stages, which you will use with multi stage builds. In that case each FROM starts a new stage, and instructions after it apply to that stage only. For now it is enough to understand that FROM always resets the context to a fresh base image.

Every valid Dockerfile must start with a FROM instruction, except in very special cases such as using the reserved FROM scratch. All other instructions operate on top of the image selected by FROM.

The `RUN` Instruction

Once a base image is selected, you usually need to install dependencies, set up directories, or perform other one time configuration during the image build. The RUN instruction executes a command inside the build environment and records the result as a new layer in the image.

For example:

dockerfile
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y curl

Each RUN line creates a new intermediate container, executes the command inside it, commits the result into the image, then discards the container. The file system changes made by each RUN persist into the next instruction.

There are two main forms of RUN:

  1. Shell form
dockerfile
RUN apt-get update && apt-get install -y curl

This form uses a shell inside the container environment. On Linux images this means /bin/sh -c "apt-get update && apt-get install -y curl". Because it goes through the shell, you can use shell features like &&, environment variable expansion, and pipes.

  1. Exec form
dockerfile
RUN ["apt-get", "install", "-y", "curl"]

This form bypasses the shell and executes the program directly. Arguments are written as a JSON array. This is useful when you want to be explicit about arguments and avoid shell interpretation.

With RUN it is common to combine related operations into a single instruction. For example, updating package indices and installing packages in one RUN helps avoid leaving behind stale caches if you remove them at the end of the same command. The detailed optimization strategies are discussed in other chapters, but you should already be aware that each RUN adds a layer to the image.

RUN instructions execute at build time, not when the container starts. They are for preparing the image, not for starting services when the container runs.

The `COPY` Instruction

Images are not very useful without your own code, configuration, or data. The COPY instruction takes files from the build context on your machine and adds them into the image file system.

A simple example looks like this:

dockerfile
FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json ./
COPY src ./src

Here, COPY moves files and directories from your project into /app inside the image. The paths on the left are relative to the build context, which is typically the directory you pass to docker build. The path on the right is the destination inside the image.

A few key behaviors of COPY are important:

It preserves file contents, and by default keeps basic metadata such as file permissions on Unix type systems.

If the source is a directory, COPY copies its contents, not the directory itself, unless you include a trailing slash in a specific way. For beginners, it is usually enough to test the result by inspecting the container file system later.

If the destination path does not exist inside the image, Docker creates it as needed.

You must be aware of what is included in the build context. Unwanted files, such as large logs or temporary directories, may be accidentally copied if they are not excluded with a .dockerignore file, but that topic is covered elsewhere.

There is also an ADD instruction that can copy files and perform some extra tasks such as automatically extracting local tar archives or fetching remote URLs. For most simple Dockerfiles, COPY is preferred, because its behavior is clearer and more predictable.

COPY always works from the build context on your machine into the image. It does not copy files from your running container back to your host, nor from other images.

The `CMD` Instruction

While FROM, RUN, and COPY define what goes into an image, CMD defines how the container should start by default. CMD provides the default command and arguments that docker run will execute, unless the user overrides them.

An example in shell form:

dockerfile
FROM node:22-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD npm start

If you build this image as myapp and run docker run myapp, Docker will execute npm start when the container starts.

In shell form, Docker wraps the command with a shell. On Linux images this becomes /bin/sh -c "npm start". As with RUN, this allows shell features but also means parsing is done by the shell.

The exec form is often preferred:

dockerfile
CMD ["npm", "start"]

Here Docker executes the program directly without a shell. Arguments are listed as JSON array elements, which avoids issues with quoting, spaces, and shell expansion.

You can specify only one CMD instruction in a given build stage. If you write multiple CMD lines, only the last one is used. Everything before it is discarded in terms of the final image configuration.

Users can override CMD at runtime. For example, if the image has:

dockerfile
CMD ["npm", "start"]

and you run:

bash
docker run myapp node script.js

then node script.js replaces the default CMD. This makes CMD a good place to specify a sensible default that is still flexible for users who need a different behavior.

CMD sets defaults for the container startup command. It is not executed during the build, and it is not guaranteed to run if the user explicitly specifies another command when starting the container.

The `ENTRYPOINT` Instruction

Where CMD provides default arguments or a default command that can be replaced, ENTRYPOINT defines the main executable for the container. It is the program that should always run when the container starts. CMD can then be used to supply default arguments to this entrypoint.

A simple example of using ENTRYPOINT alone:

dockerfile
FROM alpine:3.20
ENTRYPOINT ["echo", "Hello"]

Running docker run myimage will run echo Hello inside the container. If you run docker run myimage world, the extra world becomes an argument to echo, so the result is Hello world. This happens because the default docker behavior, when both ENTRYPOINT and additional command line arguments are present, is to append those arguments to the entrypoint command.

ENTRYPOINT also supports shell and exec forms. As with CMD, the exec form is usually preferred to avoid going through a shell process:

dockerfile
ENTRYPOINT ["python", "app.py"]

This creates a container that always runs python app.py on start, regardless of what you pass in, unless you actively override the entrypoint with a flag. You can override the entrypoint using docker run --entrypoint, but that is a more advanced use.

You can combine ENTRYPOINT and CMD to create flexible images. For example:

dockerfile
FROM ubuntu:22.04
ENTRYPOINT ["ping"]
CMD ["localhost"]

Running docker run mypinger results in the command:

text
ping localhost

If you run:

bash
docker run mypinger example.com

the CMD value is replaced with the new argument. Docker executes:

text
ping example.com

In this pattern ENTRYPOINT defines the stable executable, and CMD defines default parameters that users can easily override.

Only the last ENTRYPOINT instruction in a given build stage has effect. If multiple ENTRYPOINT lines are present, earlier ones are ignored in the final image.

ENTRYPOINT defines the main command that should always run when a container starts. CMD can supply default arguments to ENTRYPOINT, but if you provide a command at docker run without changing the entrypoint, Docker treats it as arguments to the existing ENTRYPOINT, not as a replacement.

How `CMD` and `ENTRYPOINT` Work Together

Understanding the interaction between CMD and ENTRYPOINT helps you design containers that behave intuitively. There are three common patterns.

First, CMD alone. In this case the container has a default command that is easy to replace. This is typical for simple application images where the user may often override the command.

Second, ENTRYPOINT alone. Here the container behaves like a fixed tool. It always runs the same executable, and any extra arguments at runtime are appended. This is common when you want an image to act like a single well defined command line tool.

Third, ENTRYPOINT plus CMD. In this pattern ENTRYPOINT specifies the base program, and CMD provides default options. Users can then change only the arguments by providing a new command at docker run, but the main executable remains the same. This is useful for utilities like ping or backup tools, or for applications that accept configurable options but always use the same binary.

When you design a Dockerfile, choose which resource should be stable and which should be flexible. Use ENTRYPOINT for the stable part and CMD for the flexible defaults.

If you specify both ENTRYPOINT and CMD in exec form, Docker builds the final startup command by taking all elements of ENTRYPOINT and appending all elements of CMD, unless the user overrides CMD at runtime.

Summary

In a Dockerfile, each of the core instructions plays a specific role. FROM selects the base image, RUN prepares the environment during build, COPY brings your files into the image, CMD provides default startup behavior, and ENTRYPOINT defines the main executable for the container. By using them carefully and understanding their interactions, you can create images that are both predictable and convenient to use, while leaving room for configuration when the container runs.

Views: 13

Comments

Please login to add a comment.

Don't have an account? Register now!