A Dockerfile is a plain-text recipe that defines how to assemble a Docker image. It consists of a series of instructions executed sequentially by the docker build command. Rather than manually configuring containers, Dockerfiles enable reproducible, version-controlled, and automated image creation.
Below is a modernized Redis Dockerfile example—rewritten for clarity, security, and best practices (e.g., using a minimal base image, non-root user, multi-stage build, and explicit dependency menagement):
# Multi-stage build: compile Redis in builder stage
FROM golang:1.22-alpine AS builder
RUN apk add --no-cache build-base wget tar
WORKDIR /tmp
RUN wget -qO- https://download.redis.io/redis-stable.tar.gz | tar xz
WORKDIR /tmp/redis-stable
RUN make -j$(nproc) && make install
# Final runtime stage
FROM alpine:3.20
RUN addgroup -g 1001 -f redis && adduser -S redis -u 1001
COPY --from=builder /usr/local/bin/redis-* /usr/local/bin/
COPY --from=builder /tmp/redis-stable/redis.conf /etc/redis.conf
# Harden configuration
RUN sed -i 's/^bind .*/bind 127.0.0.1 ::1/' /etc/redis.conf && \
sed -i 's/^daemonize .*/daemonize no/' /etc/redis.conf && \
sed -i 's/^dir .*/dir \/data/' /etc/redis.conf && \
sed -i 's/^logfile .*/logfile \/dev\/stdout/' /etc/redis.conf
VOLUME ["/data"]
WORKDIR /data
EXPOSE 6379
USER redis
CMD ["redis-server", "/etc/redis.conf"]
This example demonstrates key improvements over legacy patterns: separation of build and runtime concerns, reduced attack surface via Alpine Linux, non-root execution, and safer default bindings.
Core Dockerfile Instructions
FROM: Declares the base image. Must be the first instruction. Supports tags and digests (e.g.,FROM ubuntu:22.04orFROM python@sha256:abc123...).ARG: Defines build-time variables (e.g.,ARG NODE_VERSION=20.12.0), passed via--build-arg. Not available at runtime unless copied into the image.RUN: Executes commands during build. Prefer shell form for simple logic (RUN apt-get update && apt-get install -y curl) or exec form for predictability (RUN ["sh", "-c", "echo hello"]). EachRUNcreates a new layer.COPYandADD:COPYis preferred for local files/directories (COPY ./src /app/src).ADDsupports remote URLs and auto-extraction of tar archives—but avoid it for security and transparency reasons.ENV: Sets persistent environment variables visible both during build and at runtime (ENV PATH="/app/bin:$PATH").EXPOSE: Documents which ports the container listens on. Does not publish them—use-por-Patdocker runtime to map host ports.VOLUME: Declares mount points for persistent data. Ensures the directory is stored outside the Union File System (e.g.,VOLUME ["/var/lib/mysql"]).WORKDIR: Sets the working directory for subsequent instructions (WORKDIR /appaffectsRUN,CMD,ENTRYPOINT, etc.). Creates the path if missing.USER: Switches to a non-root user before runtime (USER appuser:appgroup). Critical for security hardening.ENTRYPOINTandCMD: Define the container’s default executable and arguments.ENTRYPOINTconfigures the executable (often as a wrapper script);CMDsupplies its default arguments. They combine when both are present in exec form:ENTRYPOINT ["./entrypoint.sh"]+CMD ["--verbose"]→ runs./entrypoint.sh --verbose.ONBUILD: Triggers instructions only when the current image is used as a base for another build. Rarely used in modern workflows due to maintainability concerns.LABEL: Adds metadata (e.g.,LABEL org.opencontainers.image.source="https://github.com/example/app") for traceability and tooling integration.
Dockerfiles should follow the principle of least privilege, minimize layers, avoid hardcoded secrets, and prioritize reproducibility—using pinned base images and explicit dependency versions.