Docker’s flexibility as a containerization tool offers developers robust options for building and running containers. However, with this flexibility comes a certain complexity, especially when it comes to understanding Dockerfile instructions. Among the most commonly misunderstood instructions are RUN, CMD, and ENTRYPOINT. Though they may seem similar, they serve distinct purposes in the lifecycle of a Docker image and container.
In this post, I’ll break down these three Dockerfile instructions, explain their differences, and highlight when to use each in your projects to get the most out of Docker.
The RUN Instruction
The RUN instruction in a Dockerfile is used during the image build process to execute commands that install software, modify files, or configure the image. Every time you use RUN, Docker creates a new layer in the image, which can influence both the final size of the image and its build time.
Example:
RUN apt update && apt -y install apache2
This command tells Docker to update the apt package manager and install the Apache web server in the container’s filesystem. It’s important to use RUN efficiently, combining multiple commands into a single RUN instruction where possible to minimize the number of layers and keep the image size small.
When to use RUN:
During the build phase of an image.
For installing packages or software required for the application.
For tasks like setting up environments or modifying files that are needed at build time.
The CMD Instruction
Unlike RUN, which operates during the image build process, the CMD instruction specifies the default command to run when the container starts. However, this default can be easily overridden by the user when running the container.
Example:
CMD ["apache2ctl", "-DFOREGROUND"]
In this example, Apache will start when the container runs, unless a different command is specified at runtime (e.g., by using docker run <image> /bin/bash). CMD is perfect for setting defaults like starting services, but it’s not enforced like ENTRYPOINT.
When to use CMD:
To define the default behavior of the container.
When you want to allow the command to be easily overridden at runtime.
The ENTRYPOINT Instruction
ENTRYPOINT defines the command that will always run when the container starts. It’s used when you want your container to function like a standalone executable. Unlike CMD, ENTRYPOINT is not easily overridden—although it can be replaced using the --entrypoint flag in docker run.
Example:
ENTRYPOINT ["my_script"]
In this case, my_script will always execute when the container starts. Any arguments passed to docker run will be appended to the ENTRYPOINT command. This makes ENTRYPOINT great for building containers that require a fixed, consistent behavior.
When to use ENTRYPOINT:
When the container is meant to run a specific executable.
If you want to ensure that the main process always runs, while allowing the user to pass additional arguments.
Combining CMD and ENTRYPOINT
You can combine CMD and ENTRYPOINT for more control over container behavior. In this setup, ENTRYPOINT defines the main command (the executable), and CMD provides default arguments, which can still be overridden.
Shell vs Exec Form
Docker commands can be specified in two forms: shell and exec.
Shell form: Looks like a regular shell command and runs inside a shell (e.g., /bin/sh -c).
Exec form: Uses JSON array syntax and runs the command directly without a shell.
Shell Form Example:
Use shell form when you need shell features like variable expansion, piping, or chaining commands.
Use exec form when you want the command to be executed directly, especially if signal handling is important (e.g., PID 1 issues in containers).
Key Takeaways
RUN is for executing commands during image build (e.g., installing software).
CMD is for setting default runtime commands that can be easily overridden.
ENTRYPOINT is for defining a mandatory command that always runs when the container starts.
Shell form is useful for complex shell operations, but exec form is preferred for direct command execution, better signal handling, and security.
By understanding the roles of each instruction and their forms, you can build Docker images that are more efficient, reliable, and easier to manage.