RUN versus CMD in a Dockerfile
Separate build steps from startup defaults, understand cache reuse, and keep image construction reproducible.
RUN executes a build step while Docker constructs an image. CMD records the default command or arguments to use when a container starts. Use RUN to prepare the filesystem and install dependencies; use CMD to describe the application's normal startup.
RUN prepares the image
FROM ubuntu:24.04
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /app/logs
The resulting filesystem changes become part of the image. Related package operations belong together so an old cached package-index step is not reused independently of installation. Removing temporary files in the same build step avoids retaining them in an earlier layer.
A RUN step does not necessarily execute on every build: the builder can reuse a cached result or omit stages that are not needed for the selected target. Combine instructions for a reason, such as consistent package installation or avoiding intermediate artifacts, rather than treating the lowest possible layer count as the sole optimization goal.
CMD supplies a runtime default
CMD ["python", "app.py"]
Without an entrypoint, docker run myapp:1.2 python debug.py replaces that command. With an exec-form entrypoint, the replacement becomes its arguments. Only the final CMD in a build stage determines that stage's CMD metadata.
| Property | RUN | CMD |
|---|---|---|
| Main purpose | Build-time preparation | Startup default |
| Execution | When the required step is not reused from cache | When the container starts using that command |
| Stored result | Applicable filesystem changes | Command metadata |
| Runtime override | Already-built changes remain | Runtime arguments can replace the default |
Put the two together
For a Python project with Flask and Gunicorn in a reviewed requirements.txt:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . ./
CMD ["gunicorn", "-w", "4", "app:app"]
This assumes app.py exports the intended WSGI application. Pin dependencies appropriately, exclude local secrets and build clutter with .dockerignore, and choose the worker count for the deployment. Copying the dependency file first can preserve its installation cache when only application code changes.
FAQ
Why use the JSON-array form?
It avoids implicitly placing a shell between Docker and the executable. That helps signal delivery, but the application still needs suitable shutdown handling. Shell expansion does not happen automatically inside an exec-form array.
What if a RUN command fails?
A failing build step normally stops the build. Shell pipelines and command separators can hide an earlier failure, so structure the command deliberately. docker build --progress=plain . can make the relevant output easier to inspect.
How does ENTRYPOINT fit?
It selects the executable while CMD can supply its defaults. See the companion CMD and ENTRYPOINT guide.
See Dockerfile instructions and build practices.
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan