FROMnode:20-alpine
FROMpython: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.
base imagefrom instructionparent imagestarting image
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.
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.
copy filesadd files to imagecopy into containercopy source code
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.
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.
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.
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.
entrypointmain processentry pointfixed commandentrypoint vs cmd
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.
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.
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.
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.
volume instructiondeclare volumepersistent storagemount point
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.
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.
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.
FROM<image> AS <stage>...FROM<image>COPY--from=<stage><src><dest>
Example
FROMnode:20-alpine AS build
WORKDIR/app
COPY package*.json./RUNnpm ci
COPY..RUNnpm run build
FROMnode: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).
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.