RUN versus CMD in a Dockerfile

Separate build steps from startup defaults, understand cache reuse, and keep image construction reproducible.

Best practices
Illustration of server operations and log analysis

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.

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting