Writing a Dockerfile
A Dockerfile is the "recipe" for building an image — each instruction creates a layer. A clear, cache-friendly Dockerfile greatly improves build speed and maintainability.
Minimal example
A typical Dockerfile for a ROS 2 workspace:
# 1. Base image: pin a tag for reproducibilityFROM ros:humble# 2. Environment variables (avoid interactive prompts)ENV DEBIAN_FRONTEND=noninteractiveENV ROS_DOMAIN_ID=1# 3. Install dependenciesRUN apt-get update && \ apt-get install -y --no-install-recommends python3-pip && \ rm -rf /var/lib/apt/lists/*# 4. Copy codeWORKDIR /workspaceCOPY . /workspace# 5. Container start commandCMD ["ros2", "run", "demo_nodes_cpp", "talker"]Build and run:
docker build -t my-ros-app:1.0 .docker run -d --name ros-node my-ros-app:1.0Common instructions
| Instruction | Purpose |
|---|---|
FROM <img> | Base image (must be the first line) |
WORKDIR <path> | Set working directory (created if missing) |
COPY <src> <dst> | Copy files into the image |
RUN <cmd> | Execute a command at build time (new layer) |
ENV <k>=<v> | Set an environment variable |
EXPOSE <port> | Declare an exposed port (documentation only) |
CMD [...] | Default command at container start (overridable by run) |
ENTRYPOINT [...] | Container entry point (pairs with CMD) |
💡
CMDand the command at the end ofdocker runare mutually exclusive — the latter overrides the former.ENTRYPOINTis best when you want the image to behave like a single executable.
Build cache and layer order
Docker builds top-to-bottom and skips cached layers. Any instruction before COPY is reused as long as it is unchanged, so put volatile content (your code) later:
# ✅ Install deps first (rarely change), copy code after (often change)RUN apt-get update && apt-get install -y python3-pipCOPY . /workspaceIf COPY comes before RUN apt-get, every code change invalidates all subsequent layers' cache, slowing builds.
.dockerignore
Like .gitignore, it prevents irrelevant local files (e.g. build/, node_modules, .git) from entering the build context:
build/.git*.logWhy it matters
The build context is sent entirely to the Docker daemon. Without .dockerignore, a large node_modules noticeably slows docker build.
For image-size optimization, see Image Slimming.