---
title: "Dockerfile Syntax Cheatsheet"
slug: "dockerfile-syntax"
category: "cheatsheets"
tags: ["cheatsheets", "docker", "dockerfile", "containers", "devops", "reference"]
status: "stable"
last_updated: 2026-05-14
summary: "Dockerfile instruction reference: FROM, RUN, COPY, CMD, ENTRYPOINT, ARG, ENV, USER, WORKDIR, EXPOSE, HEALTHCHECK, multi-stage patterns."
related:
  [
    "[[cheatsheets/docker-commands]]",
    "[[cheatsheets/kubernetes-commands]]",
    "[[cheatsheets/nginx-config]]",
    "[[ops/github-pages]]",
    "[[coding/shell]]",
    "[[cheatsheets/bash-one-liners]]",
    "[[cheatsheets/index|Cheatsheets]]",
  ]
---

## Overview

A Dockerfile is a sequential list of instructions that Docker executes to produce a layer-cached image. Each instruction that creates filesystem changes adds a layer; minimize layers by chaining `RUN` commands and ordering instructions from least-changed to most-changed for maximum cache reuse. For the commands that build and run images, see [[cheatsheets/docker-commands]].

## Base and metadata instructions

Set the base image, author metadata, and the working context before any filesystem instructions.

| Instruction | Syntax                           | Notes                                                                                  |
| ----------- | -------------------------------- | -------------------------------------------------------------------------------------- |
| `FROM`      | `FROM <image>:<tag> [AS <name>]` | First instruction in every stage. Pin a digest for reproducibility.                    |
| `LABEL`     | `LABEL key="value"`              | OCI image metadata; queryable with `docker inspect`.                                   |
| `ARG`       | `ARG <name>[=<default>]`         | Build-time variable; not available at runtime. Declare before `FROM` to use in `FROM`. |
| `WORKDIR`   | `WORKDIR /app`                   | Set working directory; creates the path if it does not exist.                          |
| `USER`      | `USER <user>[:<group>]`          | Switch to a non-root user; applies to all subsequent `RUN`, `CMD`, `ENTRYPOINT`.       |

```dockerfile
FROM node:22-alpine AS base
LABEL org.opencontainers.image.source="https://github.com/org/repo"
ARG NODE_ENV=production
WORKDIR /app
USER node
```

Pin base images to a digest (`node:22-alpine@sha256:...`) in production to prevent silent upstream changes.

## Filesystem instructions

Copy files into the image and run commands to build the application.

| Instruction | Syntax                                   | Notes                                                                               |
| ----------- | ---------------------------------------- | ----------------------------------------------------------------------------------- |
| `COPY`      | `COPY [--chown=user:group] <src> <dest>` | Preferred over `ADD`; no hidden extraction or URL fetching.                         |
| `ADD`       | `ADD <src> <dest>`                       | Use only when you need automatic tar extraction; otherwise use `COPY`.              |
| `RUN`       | `RUN <command>`                          | Creates a layer; chain with `&&` to minimize layers. Use `RUN --mount` for secrets. |

```dockerfile
# Good: chain RUN commands to minimize layers; clean caches in same layer
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    && rm -rf /var/lib/apt/lists/*

# COPY dependency manifests first to cache the install layer
COPY package*.json ./
RUN npm ci --omit=dev

# COPY source after installs so code changes don't bust the npm install layer
COPY --chown=node:node . .
```

## Environment variables

`ENV` persists into the container at runtime. `ARG` is build-only.

| Instruction | Syntax            | Visible at runtime?                       |
| ----------- | ----------------- | ----------------------------------------- |
| `ENV`       | `ENV KEY=value`   | Yes; in `docker inspect` and process env. |
| `ARG`       | `ARG KEY=default` | No; only during build.                    |

```dockerfile
ARG APP_VERSION=0.0.0           # build-time only
ENV APP_VERSION=$APP_VERSION    # promote to runtime if needed
ENV NODE_ENV=production \
    PORT=8080
```

Do not `ENV` secrets into images; they appear in `docker history` and `docker inspect`. Use runtime `--env-file` or a secrets manager.

## Entrypoint and CMD

These two instructions define what runs when the container starts.

| Instruction  | Syntax                      | Purpose                                                  |
| ------------ | --------------------------- | -------------------------------------------------------- |
| `CMD`        | `CMD ["node", "server.js"]` | Default arguments; overridden by `docker run` arguments. |
| `ENTRYPOINT` | `ENTRYPOINT ["node"]`       | Fixed executable; `docker run` arguments are appended.   |
| Combined     | `ENTRYPOINT` + `CMD`        | `CMD` provides defaults for `ENTRYPOINT` arguments.      |

```dockerfile
# CMD only: easily overridden
CMD ["npm", "start"]

# ENTRYPOINT + CMD: ENTRYPOINT is fixed; CMD is default argument
ENTRYPOINT ["python", "-m", "gunicorn"]
CMD ["app:app", "--bind", "0.0.0.0:8080"]

# Override CMD at runtime: docker run myimage app:app --workers 4
```

Always use the exec form (`["executable", "arg"]`), not the shell form (`CMD npm start`). The shell form wraps the command in `sh -c`, adding a shell process that does not forward signals.

## Network and health

Declare the port the application listens on and define a health probe.

| Instruction   | Syntax                                | Notes                                                                  |
| ------------- | ------------------------------------- | ---------------------------------------------------------------------- |
| `EXPOSE`      | `EXPOSE <port>[/tcp\|udp]`            | Documents the port; does not publish it. Publish with `docker run -p`. |
| `HEALTHCHECK` | `HEALTHCHECK [options] CMD <command>` | Sets the container health status used by orchestrators.                |

```dockerfile
EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
    CMD curl -fs http://localhost:8080/health || exit 1
```

Options for `HEALTHCHECK`: `--interval` (default 30s), `--timeout` (default 30s), `--start-period` (default 0s), `--retries` (default 3).

## Multi-stage build pattern

Multi-stage builds produce a small runtime image by discarding build-time dependencies.

```dockerfile
# Stage 1: install and build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: run tests in the build stage (target in CI)
FROM builder AS test
RUN npm test

# Stage 3: minimal runtime
FROM node:22-alpine AS runtime
WORKDIR /app
RUN addgroup -S app && adduser -S app -G app
USER app
COPY --from=builder --chown=app:app /app/dist ./dist
COPY --from=builder --chown=app:app /app/node_modules ./node_modules
EXPOSE 8080
HEALTHCHECK --interval=30s CMD wget -qO- http://localhost:8080/health || exit 1
CMD ["node", "dist/server.js"]
```

Build just the test stage in CI: `docker build --target test -t myapp:test .`

## Common gotchas

- `COPY . .` copies `.env`, `node_modules`, and secrets unless `.dockerignore` excludes them. Always maintain a `.dockerignore` alongside the Dockerfile.
- Each `ENV` instruction adds a layer. Combine related variables into a single `ENV` instruction.
- `RUN apt-get update` without `apt-get install` in the same `RUN` layer leaves a stale package list cached. Always chain them.
- Shell form `CMD npm start` runs as `/bin/sh -c "npm start"`, making `npm` PID 1. `SIGTERM` goes to the shell, not to node. The shell often does not forward it, causing a 10-second graceful-shutdown timeout. Use exec form.
- `USER node` before `COPY` means files are owned by the build user. Use `--chown=node:node` on `COPY` to set ownership in one layer.
- `ARG` values before `FROM` are not in scope inside the build stage unless re-declared. Declare `ARG` again after `FROM` if you need it.

## Related

- [[cheatsheets/docker-commands]]
- [[cheatsheets/kubernetes-commands]]
- [[cheatsheets/nginx-config]]
- [[ops/github-pages]]
- [[coding/shell]]
- [[cheatsheets/bash-one-liners]]
- [[cheatsheets/index|Cheatsheets]]
