Skip to main content

Dockerfile


Chapter objectives​

  • Understand the Dockerfile syntax
  • Master the main instructions
  • Optimize your images
  • Apply best practices

1 - What is a Dockerfile?​

Definition​

A Dockerfile is a text file containing the instructions to automatically build a Docker image.

# Exemple simple
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Build process​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Dockerfile β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ docker build
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Docker Engine β”‚
β”‚ β”‚
β”‚ FROM β†’ RUN β†’ COPY β†’ ... β†’ CMD β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β–Ό β–Ό β–Ό β–Ό β”‚
β”‚ Layer Layer Layer Layer β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Image Docker β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2 - Basic instructions​

FROM - Base image​

# Image officielle
FROM ubuntu:22.04

# Image Alpine (légère)
FROM node:18-alpine

# Image scratch (vide)
FROM scratch

# Multi-stage : nommer l'Γ©tape
FROM golang:1.21 AS builder

WORKDIR - Working directory​

# DΓ©finir le rΓ©pertoire de travail
WORKDIR /app

# Tous les chemins relatifs seront basΓ©s sur /app
COPY . . # Copie dans /app
RUN ls # ExΓ©cutΓ© dans /app

COPY and ADD​

# COPY - Copier des fichiers
COPY source destination
COPY package.json /app/
COPY . /app/
COPY --chown=user:group files /app/

# ADD - Copier avec fonctionnalitΓ©s supplΓ©mentaires
ADD archive.tar.gz /app/ # Extrait automatiquement
ADD https://example.com/file /app/ # TΓ©lΓ©charge depuis URL
Best practice

Prefer COPY over ADD unless you need automatic extraction.

RUN - Run commands​

# Format shell
RUN apt-get update && apt-get install -y nginx

# Format exec (recommandΓ©)
RUN ["apt-get", "update"]

# Commandes multiples (optimisΓ©)
RUN apt-get update && \
apt-get install -y \
nginx \
curl \
vim && \
rm -rf /var/lib/apt/lists/*

3 - Execution instructions​

CMD - Default command​

# Format exec (recommandΓ©)
CMD ["node", "server.js"]

# Format shell
CMD node server.js

# Paramètres pour ENTRYPOINT
CMD ["--help"]

ENTRYPOINT - Entry point​

# DΓ©finit l'exΓ©cutable principal
ENTRYPOINT ["node"]
CMD ["server.js"]

# docker run mon-image β†’ node server.js
# docker run mon-image app.js β†’ node app.js

CMD vs ENTRYPOINT difference​

AspectCMDENTRYPOINT
OverridableYes, easilyNo (except --entrypoint)
UsageDefault parametersMain executable
CombinationCan be arguments for ENTRYPOINTReceives CMD as arguments
# Exemple combinΓ©
ENTRYPOINT ["python"]
CMD ["app.py"]

# docker run mon-image β†’ python app.py
# docker run mon-image test.py β†’ python test.py

4 - Configuration​

ENV - Environment variables​

# DΓ©finir des variables
ENV NODE_ENV=production
ENV APP_HOME=/app \
APP_PORT=3000

# Utiliser les variables
WORKDIR $APP_HOME
EXPOSE $APP_PORT

ARG - Build arguments​

# Argument avec valeur par dΓ©faut
ARG NODE_VERSION=18

# Utilisation
FROM node:${NODE_VERSION}-alpine

# Argument obligatoire
ARG API_KEY
# Passer des arguments au build
docker build --build-arg NODE_VERSION=20 .
docker build --build-arg API_KEY=secret .

EXPOSE - Document the ports​

# Documenter les ports exposΓ©s
EXPOSE 80
EXPOSE 443
EXPOSE 3000/tcp
EXPOSE 5000/udp
Important

EXPOSE is informational. You always need -p at runtime to publish the ports.


5 - Advanced instructions​

USER - Change user​

# CrΓ©er et utiliser un utilisateur non-root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

# Revenir Γ  root si nΓ©cessaire
USER root

VOLUME - Mount points​

# DΓ©clarer un volume
VOLUME /data
VOLUME ["/data", "/logs"]

HEALTHCHECK - Health check​

# VΓ©rifier que l'application rΓ©pond
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1

# DΓ©sactiver le healthcheck
HEALTHCHECK NONE

LABEL - Metadata​

LABEL maintainer="[email protected]"
LABEL version="1.0"
LABEL description="Mon application web"

# Format OCI
LABEL org.opencontainers.image.source="https://github.com/user/repo"

6 - Multi-stage builds​

Concept​

Multi-stage builds make it possible to create lightweight images by separating the build from the execution.

# Γ‰tape 1: Build
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Γ‰tape 2: Production
FROM node:18-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/index.js"]

Go example​

# Build stage
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main .

# Production stage
FROM scratch
COPY --from=builder /app/main /main
ENTRYPOINT ["/main"]

Benefits​

Without multi-stageWith multi-stage
1.2 GB image50 MB image
Build tools includedOnly the executable
Large attack surfaceMinimal surface

7 - Build an image​

The docker build command​

# Build simple
docker build .

# Avec tag
docker build -t mon-app:v1 .

# Depuis un Dockerfile spΓ©cifique
docker build -f Dockerfile.prod -t mon-app:prod .

# Avec arguments
docker build --build-arg VERSION=1.0 -t mon-app:v1 .

# Sans cache
docker build --no-cache -t mon-app:v1 .

# Afficher la sortie des RUN
docker build --progress=plain -t mon-app:v1 .

The .dockerignore file​

# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
Dockerfile*
docker-compose*
README.md
.DS_Store
*.log
coverage
.nyc_output

8 - Complete examples​

Node.js application​

FROM node:18-alpine

# CrΓ©er un utilisateur non-root
RUN addgroup -S nodejs && adduser -S nodejs -G nodejs

WORKDIR /app

# Copier les fichiers de dΓ©pendances
COPY package*.json ./

# Installer les dΓ©pendances
RUN npm ci --only=production

# Copier le code source
COPY --chown=nodejs:nodejs . .

# Changer d'utilisateur
USER nodejs

# Exposer le port
EXPOSE 3000

# Healthcheck
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

# Commande de dΓ©marrage
CMD ["node", "server.js"]

Python application​

FROM python:3.11-slim

# Variables d'environnement
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1

WORKDIR /app

# Installer les dépendances système
RUN apt-get update && \
apt-get install -y --no-install-recommends gcc && \
rm -rf /var/lib/apt/lists/*

# Installer les dΓ©pendances Python
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copier le code
COPY . .

# Utilisateur non-root
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser

EXPOSE 8000

CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]

Java application (multi-stage)​

# Build stage
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests

# Runtime stage
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
RUN addgroup -S spring && adduser -S spring -G spring
USER spring
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

9 - Dockerfile best practices​

Order of instructions​

# ❌ Mauvais - Cache invalidé à chaque changement de code
FROM node:18
COPY . .
RUN npm install

# βœ… Bon - DΓ©pendances en cache
FROM node:18
COPY package*.json ./
RUN npm install
COPY . .

Minimize layers​

# ❌ Plusieurs couches
RUN apt-get update
RUN apt-get install -y nginx
RUN apt-get clean

# βœ… Une seule couche
RUN apt-get update && \
apt-get install -y nginx && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

Use lightweight images​

# ❌ Image lourde (1 Go+)
FROM ubuntu:22.04

# βœ… Image lΓ©gΓ¨re (~5 Mo)
FROM alpine:3.18

# βœ… Version slim (~100 Mo)
FROM python:3.11-slim

Security​

# βœ… Ne pas exΓ©cuter en root
USER appuser

# βœ… Copier uniquement le nΓ©cessaire
COPY --chown=appuser:appuser app.py /app/

# βœ… Utiliser des versions fixes
FROM node:18.19.0-alpine3.18

Summary​

InstructionDescription
FROMBase image
WORKDIRWorking directory
COPYCopy files
RUNRun a command
ENVEnvironment variable
ARGBuild argument
EXPOSEDocument a port
CMDDefault command
ENTRYPOINTEntry point
USERChange user
Key points
  • Order the instructions to optimize the cache
  • Use multi-stage builds for lightweight images
  • Do not run as root
  • Create a .dockerignore file

Hands-on exercises​

  1. Create a Dockerfile for a "Hello World" application in Python
  2. Optimize a Dockerfile by reordering the instructions
  3. Create a multi-stage build for a Go application
  4. Add a healthcheck to your image

← Docker Containers | Docker Volumes β†’