Docker veranderde hoe we software bouwen, distribueren en draaien. In plaats van "het werkt op mijn machine" garandeert Docker dat je applicatie overal hetzelfde draait - op je laptop, op de machine van een collega, in CI/CD en in productie. In deze gids gaan we van nul tot het deployen van een echte applicatie.
Wat is Docker?
Docker is een platform dat je applicatie en al zijn afhankelijkheden verpakt in een gestandaardiseerde eenheid genaamd een container. Een container is een geisoleerd, lichtgewicht proces dat de kernel van het host-besturingssysteem deelt maar een eigen bestandssysteem, netwerk en procesruimte heeft.
Containers vs Virtuele Machines
| Aspect | Containers | Virtuele Machines |
|---|---|---|
| Opstart | Seconden | Minuten |
| Grootte | MBs | GBs |
| OS | Deelt host-kernel | Volledig gast-OS |
| Isolatie | Procesniveau | Hardwareniveau |
| Prestaties | Bijna native | Overhead van hypervisor |
| Dichtheid | Honderden per host | Tientallen per host |
Docker Installeren
# macOS brew install --cask docker # Ubuntu/Debian curl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER # Verify installation docker --version docker run hello-world
Kernconcepten
Images
Een image is een alleen-lezen sjabloon met instructies voor het aanmaken van een container. Beschouw het als een momentopname van je applicatie en zijn omgeving.
# Pull an image from Docker Hub docker pull node:20-alpine # List local images docker images # Remove an image docker rmi node:20-alpine
Containers
Een container is een draaiende instantie van een image. Je kunt containers aanmaken, starten, stoppen en verwijderen.
# Run a container docker run -d --name my-app -p 3000:3000 node:20-alpine # List running containers docker ps # List all containers (including stopped) docker ps -a # Stop a container docker stop my-app # Remove a container docker rm my-app # View logs docker logs my-app # Execute a command inside a running container docker exec -it my-app sh
Een Dockerfile Schrijven
Een Dockerfile is een tekstbestand met instructies om een image te bouwen. Elke instructie maakt een laag aan.
Basis Dockerfile voor een Node.js App
# Use an official Node.js runtime as base image FROM node:20-alpine # Set working directory WORKDIR /app # Copy package files first (better caching) COPY package.json package-lock.json ./ # Install dependencies RUN npm ci --only=production # Copy application code COPY . . # Expose the port the app runs on EXPOSE 3000 # Command to run the application CMD ["node", "server.js"]
Bouwen en Draaien
# Build the image docker build -t my-node-app . # Run the container docker run -d -p 3000:3000 my-node-app # Visit http://localhost:3000
Multi-Stage Builds
Multi-stage builds houden je productie-images klein door de build-omgeving te scheiden van de runtime.
# Stage 1: Build FROM node:20-alpine AS builder WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY . . RUN npm run build # Stage 2: Production FROM node:20-alpine AS runner WORKDIR /app COPY /app/dist ./dist COPY /app/node_modules ./node_modules COPY /app/package.json ./ EXPOSE 3000 CMD ["node", "dist/server.js"]
Dit produceert een image met alleen de gecompileerde uitvoer en productie-afhankelijkheden - geen broncode, geen ontwikkelingsafhankelijkheden, geen build-tools.
Next.js Multi-Stage Voorbeeld
FROM node:20-alpine AS deps WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci FROM node:20-alpine AS builder WORKDIR /app COPY /app/node_modules ./node_modules COPY . . RUN npm run build FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production COPY /app/public ./public COPY /app/.next/standalone ./ COPY /app/.next/static ./.next/static EXPOSE 3000 CMD ["node", "server.js"]
Volumes: Persistente Data
Standaard gaat data in een container verloren wanneer de container wordt verwijderd. Volumes lossen dit op.
# Create a named volume docker volume create my-data # Run with a volume docker run -d -v my-data:/app/data my-app # Bind mount (map host directory to container) docker run -d -v $(pwd)/data:/app/data my-app # List volumes docker volume ls
Netwerken
Docker maakt geisoleerde netwerken aan zodat containers kunnen communiceren.
# Create a custom network docker network create my-network # Run containers on the same network docker run -d --name api --network my-network my-api docker run -d --name db --network my-network postgres:16 # Containers can reach each other by name # From "api" container: postgres://db:5432
Docker Compose
Docker Compose laat je multi-container applicaties definieren en draaien met een enkel YAML-bestand.
docker-compose.yml
services: api: build: ./api ports: - "3000:3000" environment: - DATABASE_URL=postgres://user:pass@db:5432/mydb depends_on: - db db: image: postgres:16-alpine environment: - POSTGRES_USER=user - POSTGRES_PASSWORD=pass - POSTGRES_DB=mydb volumes: - pgdata:/var/lib/postgresql/data ports: - "5432:5432" redis: image: redis:7-alpine ports: - "6379:6379" volumes: pgdata:
Commando's
# Start all services docker compose up -d # View logs docker compose logs -f # Stop all services docker compose down # Rebuild and restart docker compose up -d --build # Scale a service docker compose up -d --scale api=3
.dockerignore
Net als .gitignore voorkomt dit bestand dat onnodige bestanden naar de image worden gekopieerd.
node_modules .git .env *.md .next dist coverage
Best Practices voor Productie
1. Gebruik Kleine Basis-Images
# Bad: 1GB+ FROM node:20 # Good: ~180MB FROM node:20-alpine
2. Draai Niet als Root
FROM node:20-alpine RUN addgroup -S app && adduser -S app -G app USER app WORKDIR /home/app COPY . .
3. Gebruik Specifieke Image Tags
# Bad: can change unexpectedly FROM node:latest # Good: pinned version FROM node:20.11-alpine3.19
4. Benut de Build Cache
Orden je Dockerfile-instructies van minst naar meest frequent gewijzigd:
FROM node:20-alpine WORKDIR /app # These change rarely - cached COPY package.json package-lock.json ./ RUN npm ci --only=production # This changes often - not cached COPY . .
5. Health Checks
HEALTHCHECK \ CMD wget -qO- http://localhost:3000/health || exit 1
6. Gebruik Omgevingsvariabelen
ENV NODE_ENV=production ENV PORT=3000
Spiekbriefje voor Veelgebruikte Docker Commando's
# Images docker build -t name:tag . # Build image docker images # List images docker rmi image_name # Remove image docker image prune # Remove unused images # Containers docker run -d -p 3000:3000 image # Run detached docker ps # List running docker stop container_name # Stop docker rm container_name # Remove docker logs -f container_name # Follow logs docker exec -it container sh # Shell into container # Compose docker compose up -d # Start services docker compose down # Stop services docker compose logs -f # Follow all logs docker compose ps # List services # Cleanup docker system prune -a # Remove everything unused
Van Docker naar Kubernetes
Docker beheert individuele containers. Wanneer je honderden containers over meerdere servers moet orchestreren, heb je Kubernetes nodig. Docker en Kubernetes vullen elkaar aan:
- Docker: bouwt en draait containers
- Kubernetes: orchestreert containers op schaal (planning, schaling, zelfherstel)
Als je geinteresseerd bent in de volgende stap, bekijk dan mijn artikel over Introductie tot Kubernetes.
Conclusie
Docker is een fundamentele vaardigheid voor moderne ontwikkelaars. Het elimineert omgevingsinconsistenties, vereenvoudigt deployment en vormt de basis voor containerorchestratie met Kubernetes. Begin met een eenvoudig Dockerfile, stap over naar Docker Compose voor multi-service apps en adopteer multi-stage builds en beveiligingsbest practices naarmate je groeit.
De beste manier om Docker te leren is een project te containeriseren waar je al aan werkt. Begin vandaag.