Dockerfile Examples Explained

This page presents two Dockerfile examples: a comprehensive single-stage Dockerfile demonstrating various features, and a multi-stage build optimized for production.

Comprehensive Dockerfile Example

This Dockerfile illustrates many common instructions and best practices for building a container image.

# syntax=docker/dockerfile:1
# Using the latest syntax parser for Dockerfile

# Start with a base image
FROM ubuntu:22.04

# Add metadata to the image using LABEL
LABEL maintainer="Your Name <your.email@example.com>"
LABEL version="1.0"
LABEL description="Example Dockerfile with comprehensive features"

# Set environment variables (available during build and runtime)
ENV APP_HOME=/app \
    NODE_ENV=production \
    DEBIAN_FRONTEND=noninteractive # Prevents prompts during apt installs

# Set build arguments that can be overridden at build time (docker build --build-arg ...)
ARG VERSION=1.0
ARG BUILD_DATE

# Create a non-root user for security best practices
ARG UID=1000
ARG GID=1000
RUN groupadd --gid $GID appuser && \
    useradd --uid $UID --gid $GID --shell /bin/bash --create-home appuser

# Set the working directory for subsequent commands (RUN, CMD, ENTRYPOINT, COPY, ADD)
WORKDIR $APP_HOME

# Install system dependencies with apt cache mount (requires BuildKit)
# This prevents re-downloading packages on subsequent builds if sources don't change
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
    apt-get update && apt-get install -y --no-install-recommends \
    curl \
    nodejs \
    npm \
    python3 \
    python3-pip \
    && apt-get clean # Clean up apt lists to reduce image size

# Copy dependency definition files first to leverage build cache
# If these files haven't changed, subsequent RUN steps won't be invalidated
COPY package*.json ./
COPY requirements.txt ./

# Install Node.js dependencies with npm cache mount (requires BuildKit)
RUN --mount=type=cache,target=/root/.npm \
    npm install --production

# Install Python dependencies with pip cache mount (requires BuildKit)
RUN --mount=type=cache,target=/root/.cache/pip \
    pip3 install --no-cache-dir -r requirements.txt

# Copy the rest of the application code
COPY . .

# Set correct permissions for the application directory
RUN chown -R appuser:appuser $APP_HOME

# Switch to the non-root user for running the application
USER appuser

# Example build step (if needed) using cache mount (requires BuildKit)
RUN --mount=type=cache,target=/app/node_modules/.cache \
    npm run build

# Expose ports the container will listen on at runtime
EXPOSE 8080 8081

# Define volumes for persistent or shared data
VOLUME ["/app/data", "/app/logs"]

# Health check to verify container is functioning correctly
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

# Set the entrypoint script (runs first when container starts)
ENTRYPOINT ["./docker-entrypoint.sh"] # Typically used for setup before the main command

# Default command to run if no command is provided to 'docker run'
CMD ["npm", "start"]

Key Features Explained:

  • # syntax=docker/dockerfile:1: Specifies the parser version, enabling newer features like cache mounts.
  • LABEL: Adds metadata like maintainer, version, description to the image.
  • ENV: Sets environment variables available during build and runtime. DEBIAN_FRONTEND=noninteractive is common for avoiding prompts during package installations.
  • ARG: Defines build-time arguments that can be passed during the docker build command (e.g., --build-arg VERSION=1.1). Also used here to parameterize user/group IDs.
  • User Management: Creates a dedicated non-root user (appuser) for enhanced security.
  • WORKDIR: Sets the default directory for subsequent instructions like RUN, COPY, CMD, ENTRYPOINT.
  • RUN --mount=type=cache...: (Requires BuildKit) Installs system and application dependencies using cache mounts. This significantly speeds up subsequent builds by reusing cached package manager directories (apt, npm, pip) without embedding them in the image layers.
  • Caching Strategy (COPY): Copies dependency definition files (package.json, requirements.txt) separately before running install commands. This leverages Docker's build cache effectively – if these files don't change, the potentially long installation steps are skipped.
  • COPY . .: Copies the application source code into the working directory. Placed after dependency installation for better caching.
  • Permissions (chown): Sets ownership of the application files to the non-root user.
  • USER: Switches the context to run subsequent commands (including ENTRYPOINT/CMD) as the specified non-root user.
  • EXPOSE: Documents which ports the application inside the container listens on. Does not actually publish the ports.
  • VOLUME: Creates mount points for external volumes, typically used for persistent data or logs. Data written here won't be part of the container's writable layer.
  • HEALTHCHECK: Defines a command Docker runs periodically to check if the container is healthy. Used by orchestrators (like Swarm, Kubernetes, or Compose with depends_on) to manage container state.
  • ENTRYPOINT: Configures the main executable that runs when the container starts. Often a wrapper script for setup tasks. Arguments to docker run are passed as arguments to the entrypoint.
  • CMD: Provides default arguments to the ENTRYPOINT or the default command to run if no ENTRYPOINT is specified. Can be overridden when running the container.

Multi-Stage Build Example

This example demonstrates a multi-stage build, a technique used to create smaller, more secure production images by separating build-time dependencies from the final runtime environment.

# syntax=docker/dockerfile:1

# --- Build Stage ---
FROM node:18-alpine AS builder # Name this stage 'builder'
WORKDIR /build

# Copy dependency files
COPY package*.json ./

# Install ALL dependencies (including devDependencies) with cache mount
RUN --mount=type=cache,target=/root/.npm \
    npm install

# Copy source code
COPY . .

# Build the application (e.g., transpile code, bundle assets)
# Use cache mount for build artifacts if applicable
RUN --mount=type=cache,target=/build/node_modules/.cache \
    npm run build # This creates artifacts in a '/build/dist' directory

# --- Production Stage ---
FROM node:18-alpine AS production # Start a new, clean stage named 'production'
WORKDIR /app

# Copy only the built artifacts from the 'builder' stage
COPY --from=builder /build/dist ./dist

# Copy package files needed for production dependencies
COPY --from=builder /build/package*.json ./

# Install ONLY production dependencies with cache
RUN --mount=type=cache,target=/root/.npm \
    npm install --production

# Switch to the built-in non-root 'node' user provided by the base image
USER node

# Expose the application port (optional but good practice)
EXPOSE 8080

# Define the command to run the application
CMD ["node", "dist/server.js"]

Multi-Stage Build Concepts:

  • Multiple FROM Instructions: Each FROM instruction starts a new build stage.
  • Naming Stages (AS builder): You can name stages using AS <stage_name>. This makes it easier to refer to them later.
  • Build Stage (builder): This stage uses a base image suitable for building (e.g., Node.js with build tools). It installs all dependencies (including development ones), copies source code, and runs build commands (like npm run build).
  • Production Stage (production): This stage starts from a fresh, often minimal, base image suitable for runtime (e.g., node:18-alpine).
  • COPY --from=<stage_name>: This crucial instruction copies artifacts (like compiled code, assets) from a previous stage (builder in this case) into the current stage.
  • Reduced Size & Security: The final image (production stage) only contains the runtime dependencies and the built application artifacts. Build tools, development dependencies, and source code are discarded, leading to significantly smaller and more secure production images.
  • Optimized Dependency Installation: Only production dependencies are installed in the final stage, further reducing size.