DK

Dockerfile

Docker · 16 entries

FROM — Set Base Image

syntax
FROM <image>[:<tag>] [AS <stage_name>]
example
FROM node:20-alpine
FROM python:3.12-slim AS builder

Note Every Dockerfile must start with FROM (or ARG before FROM). Always pin a specific tag — FROM node:latest today might be Node 20, but tomorrow it could be 22, breaking your build silently.

RUN — Execute Build Commands

syntax
RUN <command>
RUN ["executable", "arg1", "arg2"]
example
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir -r requirements.txt

Note Each RUN creates a new image layer. Combine related commands with && to reduce layers and image size. Always clean up package manager caches in the same RUN statement, or the cache ends up in a previous layer and is never actually removed.

COPY — Copy Files into Image

syntax
COPY [--chown=<user>:<group>] <src>... <dest>
example
COPY package.json package-lock.json ./
COPY --chown=appuser:appuser . /app/

Note COPY only handles local files from the build context. Copy dependency manifests (package.json, requirements.txt) before the rest of the source code so that dependency-install layers are cached when only your application code changes.

ADD — Copy with Extras

syntax
ADD <src> <dest>
example
ADD app.tar.gz /app/
ADD https://example.com/config.json /app/config.json

Note ADD can auto-extract tar archives and fetch URLs. However, COPY is preferred for simple file copies because ADD's extra behavior can be surprising. Using ADD for URL downloads also defeats layer caching — use RUN curl instead for better control.

WORKDIR — Set Working Directory

syntax
WORKDIR /path/to/dir
example
WORKDIR /app
COPY . .
RUN npm ci

Note All subsequent RUN, CMD, COPY, and ENTRYPOINT instructions use this directory. WORKDIR creates the directory if it does not exist. Avoid using RUN cd /app — it only affects that single RUN layer.

CMD — Default Container Command

syntax
CMD ["executable", "arg1", "arg2"]
CMD command arg1 arg2
example
CMD ["node", "server.js"]
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]

Note There can only be one CMD. If you specify multiple, only the last one takes effect. CMD is overridden by any command you pass to docker run. Always prefer the exec form (JSON array) so the process runs as PID 1 and receives signals properly.

ENTRYPOINT — Fixed Container Executable

syntax
ENTRYPOINT ["executable", "arg1"]
ENTRYPOINT command arg1
example
ENTRYPOINT ["python", "manage.py"]
CMD ["runserver", "0.0.0.0:8000"]

Note ENTRYPOINT sets the main executable. CMD then provides default arguments that users can override. Together: ENTRYPOINT ["python", "manage.py"] + CMD ["runserver"] means the user can run docker run myapp migrate to swap the subcommand.

ENV — Set Environment Variables

syntax
ENV <key>=<value> [<key>=<value>...]
example
ENV NODE_ENV=production
ENV APP_PORT=3000 LOG_LEVEL=info

Note ENV values persist into the running container and into child images. If you only need a variable during build time (e.g., a version number), use ARG instead to avoid leaking it into the final image.

ARG — Build-Time Variables

syntax
ARG <name>[=<default_value>]
example
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-alpine

ARG BUILD_DATE
LABEL build-date=$BUILD_DATE

Note ARG values are only available during build (not at runtime). Pass them with docker build --build-arg NODE_VERSION=22. ARG before FROM is only in scope for the FROM line itself — redeclare it after FROM if needed later.

EXPOSE — Document Container Port

syntax
EXPOSE <port>[/<protocol>]
example
EXPOSE 3000
EXPOSE 8443/tcp

Note EXPOSE is documentation only — it does NOT publish the port. You still need -p at runtime. Think of it as a note to anyone reading the Dockerfile about which ports the application listens on.

VOLUME — Declare Mount Point

syntax
VOLUME ["/path/to/dir"]
example
VOLUME ["/var/lib/postgresql/data"]

Note Creates an anonymous volume at that path when no explicit mount is provided. This prevents data from growing the container's writable layer. However, anonymous volumes are hard to manage — prefer named volumes via docker run -v.

USER — Set Runtime User

syntax
USER <username>[:<group>]
example
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
USER appuser

Note All subsequent RUN, CMD, and ENTRYPOINT instructions run as this user. Running as root in production is a security risk — always create and switch to a non-root user for the final stage.

LABEL — Add Metadata

syntax
LABEL <key>=<value> [<key>=<value>...]
example
LABEL maintainer="team@example.com"
LABEL version="2.1" description="API backend service"

Note Labels are key-value metadata stored in the image. They show up in docker inspect and can be used as filters: docker images --filter label=version=2.1. Use the OCI standard keys (org.opencontainers.image.*) for interoperability.

HEALTHCHECK — Container Health Probe

syntax
HEALTHCHECK [options] CMD <command>
HEALTHCHECK NONE
example
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

Note Docker marks the container as healthy, unhealthy, or starting based on exit codes. Orchestrators use this to decide when to route traffic or restart containers. Install curl or wget in your image if you use HTTP checks.

Multi-Stage Builds

syntax
FROM <image> AS <stage>
...
FROM <image>
COPY --from=<stage> <src> <dest>
example
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]

Note The final image only contains the layers from the last FROM stage. Build tools, dev dependencies, and source code from earlier stages are discarded, dramatically reducing image size. A Go API might go from 1.2GB (with SDK) to 15MB (static binary on scratch).

.dockerignore — Exclude Build Context

syntax
# .dockerignore file
pattern
dir/
example
node_modules
.git
*.md
.env
dist
.DS_Store
coverage

Note Place this file next to your Dockerfile. Without it, Docker sends everything in the build context to the daemon, including node_modules (hundreds of MB), .git history, and possibly secrets in .env files. This is one of the highest-impact optimizations you can make.