Writing Dockerfiles

2 minute read

Writing Dockerfiles

A Dockerfile defines how to build your container image. Here are the most common instructions you need to know for the CKAD.

Basic Dockerfile

# Use a specific base image
FROM python:3.12-slim

# Set the working directory
WORKDIR /app

# Copy dependency file first (for caching)
COPY requirements.txt .

# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY . .

# Expose the port your app listens on
EXPOSE 8080

# Set the default command
CMD ["python", "app.py"]

Key Dockerfile Instructions

Instruction Purpose Example
FROM Base image FROM node:18-alpine
WORKDIR Set working directory WORKDIR /app
COPY Copy files from host COPY package.json .
ADD Copy + extract archives ADD app.tar.gz /app
RUN Execute build commands RUN npm install
ENV Set environment variable ENV NODE_ENV=production
EXPOSE Document the port EXPOSE 3000
CMD Default run command CMD ["node", "server.js"]
ENTRYPOINT Fixed run command ENTRYPOINT ["python"]
USER Run as non-root user USER 1000
ARG Build-time variable ARG VERSION=1.0

COPY vs ADD

Both copy files into the image, but they behave differently:

Feature COPY ADD
Copy local files ✅ ✅
Extract tar archives ❌ ✅ (auto-extracts)
Download from URL ❌ ✅
Recommended ✅ (preferred) Only when you need extraction

Best practice: Use COPY unless you specifically need tar extraction or URL download.

RUN — Reducing Layers

Each RUN instruction creates a layer. Combine related commands to reduce layers:

# Bad — 3 layers
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*

# Good — 1 layer
RUN apt-get update && \
    apt-get install -y curl && \
    rm -rf /var/lib/apt/lists/*

ENV vs ARG

Feature ENV ARG
Available at Build time + runtime Build time only
Persists in image ✅ ❌
Overridable at build With --build-arg With --build-arg
Overridable at run With -e or --env ❌
# ARG — only during build
ARG APP_VERSION=1.0
RUN echo "Building version $APP_VERSION"

# ENV — available at runtime too
ENV APP_PORT=8080

CMD vs ENTRYPOINT

This is a common CKAD topic. Understanding the difference is essential.

  • CMD — Default command that can be overridden when running the container
  • ENTRYPOINT — Fixed command that always runs. Arguments are appended to it
# CMD example — can be overridden
FROM ubuntu
CMD ["echo", "hello"]
# docker run myimage          → prints "hello"
# docker run myimage echo bye → prints "bye" (CMD is replaced)

# ENTRYPOINT example — always runs
FROM ubuntu
ENTRYPOINT ["echo"]
CMD ["hello"]
# docker run myimage          → prints "hello"
# docker run myimage bye      → prints "bye" (appended to ENTRYPOINT)

Summary Table

Scenario ENTRYPOINT CMD Result
Only CMD — ["echo", "hi"] echo hi
Only ENTRYPOINT ["echo"] — echo
Both ["echo"] ["hello"] echo hello
Override at run ["echo"] ["hello"] docker run img bye → echo bye

Overriding in Kubernetes

In Kubernetes, you override these using command (replaces ENTRYPOINT) and args (replaces CMD):

apiVersion: v1
kind: Pod
metadata:
  name: custom-cmd
spec:
  containers:
  - name: app
    image: ubuntu
    command: ["echo"]       # Overrides ENTRYPOINT
    args: ["from kubernetes"] # Overrides CMD
Dockerfile Kubernetes Pod Spec Behavior
ENTRYPOINT command Sets the executable
CMD args Sets the arguments

Dockerfile Best Practices

  1. Order by change frequency — Put rarely-changing instructions first for better caching
  2. Copy dependency files before code — Keeps the dependency install layer cached
  3. Use .dockerignore — Exclude unnecessary files from the build context
  4. Pin versions — Use FROM python:3.12-slim not FROM python
  5. Minimize layers — Combine RUN commands
  6. Clean up in the same layer — Delete caches and temp files in the same RUN

Example .dockerignore:

node_modules
.git
.env
*.md
Dockerfile
docker-compose.yml

Updated: