Writing Dockerfiles
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
- Order by change frequency — Put rarely-changing instructions first for better caching
- Copy dependency files before code — Keeps the dependency install layer cached
- Use
.dockerignore— Exclude unnecessary files from the build context - Pin versions — Use
FROM python:3.12-slimnotFROM python - Minimize layers — Combine
RUNcommands - 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