Aan de slag met Docker: beginnersgids
Docker is een platform waarmee je applicaties kunt verpakken, distribueren en draaien in zogeheten containers. Een container bevat alles wat je applicatie nodig heeft: code, runtime, libraries en configuratie. Het grote voordeel: je krijgt een voorspelbare en reproduceerbare omgeving die op je laptop, op een server en in de cloud hetzelfde werkt.
Deze gids is bedoeld voor beginners, maar gaat inhoudelijk de diepte in. Je leert niet alleen wat je moet typen, maar ook waarom het werkt, hoe je problemen herkent en hoe je een nette workflow opbouwt.
Inhoud
- Wat is Docker precies?
- Belangrijke begrippen: image, container, registry
- Installatie en controle
- Eerste container draaien
- Werken met images
- Containers beheren
- Poorten, netwerken en bereikbaarheid
- Volumes en data persistent maken
- Een eigen image bouwen met een Dockerfile
- Best practices voor Dockerfiles
- Docker Compose: meerdere services
- Logs, debugging en troubleshooting
- Security en rechten
- Opruimen en onderhoud
- Volgende stappen
Wat is Docker precies?
Containers versus virtuele machines
Een klassieke virtuele machine (VM) virtualiseert complete hardware: je draait een heel besturingssysteem binnen een ander besturingssysteem. Dat is krachtig, maar relatief zwaar: veel geheugen, trage opstart, en vaak dubbel werk (meerdere OS-installaties).
Docker-containers werken anders:
- Containers delen de kernel van het hostbesturingssysteem.
- Isolatie gebeurt via OS-mechanismen zoals namespaces en cgroups.
- Daardoor zijn containers lichtgewicht en starten ze vaak in seconden (of minder).
Belangrijk om te begrijpen: een container is geen mini-VM. Het is een proces (of set processen) dat geïsoleerd draait, met een eigen filesystem-laag, netwerkstack en procesruimte.
Waarom Docker gebruiken?
- Reproduceerbaarheid: “werkt op mijn machine” wordt “werkt overal”.
- Snelle deployments: images zijn portable en versieerbaar.
- Schaalbaarheid: meerdere containers naast elkaar, eenvoudig te dupliceren.
- Isolatie: dependencies van project A botsen niet met project B.
- CI/CD: pipelines bouwen en testen in dezelfde omgeving als productie.
Belangrijke begrippen: image, container, registry
Image
Een image is een read-only sjabloon: een momentopname van een filesystem plus metadata (zoals entrypoint en default command). Images bestaan uit lagen. Elke laag is een set wijzigingen t.o.v. de vorige laag. Dit maakt caching en snelle downloads mogelijk.
Container
Een container is een draaiende (of gestopte) instantie van een image, met een extra write layer erbovenop. Alles wat je in de container wijzigt zonder volume (bijvoorbeeld een bestand aanpassen) verdwijnt meestal zodra je de container verwijdert.
Registry
Een registry is een opslagplaats voor images. De bekendste is Docker Hub. Je kunt ook een private registry gebruiken.
Voorbeeld: nginx:alpine
nginxis de repository/naamalpineis de tag (vaak een versie of variant)
Installatie en controle
Docker installeren
Installeer Docker Desktop of Docker Engine afhankelijk van je systeem. Na installatie controleer je of Docker werkt.
Controleer versie:
docker version
Controleer of de daemon draait:
docker info
Als docker info faalt, draait de Docker-daemon niet of heb je geen rechten.
Eerste container draaien
We starten met een simpele testcontainer:
docker run hello-world
Wat gebeurt hier?
- Docker zoekt lokaal naar de image
hello-world. - Als die niet bestaat, wordt hij gedownload uit de registry.
- Docker maakt een container aan op basis van die image.
- De container voert een commando uit en stopt.
Interactief een Linux-shell starten
Start een tijdelijke container met een shell:
docker run -it --rm alpine sh
Uitleg:
-itmaakt het interactief (terminal + input).--rmverwijdert de container automatisch bij afsluiten.alpineis een kleine Linux-distributie.shis de shell.
In de container kun je bijvoorbeeld:
uname -a
ls -la
Stop de shell met exit.
Werken met images
Images zoeken en downloaden
Zoeken (optioneel):
docker search nginx
Download een image:
docker pull nginx:alpine
Bekijk lokale images:
docker images
Tags en versies
Gebruik bij voorkeur expliciete tags. latest is geen garantie voor stabiliteit.
Voorbeeld:
docker pull python:3.12-slim
docker pull node:20-alpine
Containers beheren
Een container starten (achtergrond)
Draai Nginx in de achtergrond:
docker run -d --name web nginx:alpine
-ddraait detached (achtergrond).--name webgeeft een herkenbare naam.
Bekijk actieve containers:
docker ps
Bekijk ook gestopte containers:
docker ps -a
Stoppen, starten, verwijderen
Stop:
docker stop web
Start opnieuw:
docker start web
Verwijder (container moet gestopt zijn):
docker rm web
Forceer verwijderen (ook als hij draait):
docker rm -f web
Commando uitvoeren in een draaiende container
Start opnieuw:
docker run -d --name web nginx:alpine
Open een shell in de container:
docker exec -it web sh
Bekijk processen:
ps aux
Verlaat met exit.
Poorten, netwerken en bereikbaarheid
Containers hebben een eigen netwerknamespace. Als je een webserver in een container draait, is die niet automatisch bereikbaar vanaf je host. Je moet poorten publiceren.
Poort publiceren
Draai Nginx en map poort 8080 op je host naar poort 80 in de container:
docker rm -f web 2>/dev/null || true
docker run -d --name web -p 8080:80 nginx:alpine
Test in je browser via http://localhost:8080 of via curl:
curl -i http://localhost:8080
Uitleg -p:
- Links is hostpoort (8080)
- Rechts is containerpoort (80)
Netwerken in Docker
Docker maakt standaard een bridge netwerk aan. Containers op hetzelfde netwerk kunnen elkaar via naam bereiken als je een user-defined netwerk gebruikt.
Maak een netwerk:
docker network create appnet
Start twee containers op dit netwerk:
docker run -d --name web --network appnet -p 8080:80 nginx:alpine
docker run -it --rm --name client --network appnet alpine sh
In client kun je testen:
apk add --no-cache curl
curl -I http://web
Hier werkt web als hostnaam omdat Docker DNS regelt binnen het netwerk.
Volumes en data persistent maken
Containers zijn bedoeld om vervangbaar te zijn. Data wil je meestal bewaren. Daarvoor gebruik je volumes of bind mounts.
Volume versus bind mount
- Volume: beheerd door Docker, ideaal voor databases en persistente data.
- Bind mount: koppelt een map van je host direct in de container, handig voor development.
Een volume maken en gebruiken
Maak een volume:
docker volume create pgdata
Start PostgreSQL met een volume:
docker run -d \
--name db \
-e POSTGRES_PASSWORD=geheim \
-v pgdata:/var/lib/postgresql/data \
-p 5432:5432 \
postgres:16-alpine
Controleer logs:
docker logs -f db
Stop en verwijder de container:
docker rm -f db
Start opnieuw met hetzelfde volume:
docker run -d \
--name db \
-e POSTGRES_PASSWORD=geheim \
-v pgdata:/var/lib/postgresql/data \
-p 5432:5432 \
postgres:16-alpine
Je data blijft behouden omdat het volume losstaat van de container.
Bind mount voor development
Stel je hebt een map site/ met een index.html. Je kunt die live serveren met Nginx:
mkdir -p site
printf '<h1>Hallo Docker</h1>\n' > site/index.html
docker rm -f web 2>/dev/null || true
docker run -d --name web -p 8080:80 -v "$(pwd)/site":/usr/share/nginx/html:ro nginx:alpine
:romaakt de mount read-only in de container.- Wijzig
site/index.htmlen refresh je browser: je ziet direct het resultaat.
Een eigen image bouwen met een Dockerfile
Een Dockerfile is een recept om een image te bouwen. We maken een simpele webapp in Python met Flask.
Projectstructuur
Maak een map:
mkdir -p flask-app
cd flask-app
Maak app.py:
cat > app.py <<'EOF'
from flask import Flask
import os
app = Flask(__name__)
@app.get("/")
def home():
return {
"bericht": "Hallo vanuit Docker",
"hostnaam": os.uname().nodename
}
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
EOF
Maak requirements.txt:
cat > requirements.txt <<'EOF'
flask==3.0.3
EOF
Dockerfile schrijven
Maak Dockerfile:
cat > Dockerfile <<'EOF'
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]
EOF
Uitleg per regel:
FROM python:3.12-slim: basisimage met Python.WORKDIR /app: zet werkmap; volgende commando’s draaien vanuit/app.COPY requirements.txt .: kopieert alleen dependenciesbestand.RUN pip install ...: installeert dependencies. Door eerstrequirements.txtte kopiëren kan Docker deze laag cachen zolang requirements niet veranderen.COPY app.py .: kopieert de appcode.EXPOSE 5000: documenteert dat de container luistert op 5000 (publiceert niet automatisch).CMD ...: standaard commando bijdocker run.
Image bouwen
Bouw het image:
docker build -t flask-demo:1.0 .
Bekijk images:
docker images | grep flask-demo
Container draaien
Start de container:
docker run -d --name flask -p 5000:5000 flask-demo:1.0
Test:
curl -s http://localhost:5000/ | cat
Logs bekijken:
docker logs -f flask
Stop en verwijder:
docker rm -f flask
Best practices voor Dockerfiles
1) Gebruik kleine basisimages waar passend
alpine is klein, maar niet altijd ideaal (andere libc, soms compatibiliteitsissues). slim is vaak een goede balans.
2) Minimaliseer lagen en houd caching slim
Door eerst requirements.txt te kopiëren en te installeren, hergebruikt Docker de cache wanneer alleen je code verandert.
3) Draai als niet-root waar mogelijk
Voor betere veiligheid maak je een gebruiker aan. Voorbeeld (conceptueel):
RUN useradd -m appuser
USER appuser
Let op: soms heb je extra rechten nodig voor poorten <1024 of package installs.
4) Gebruik .dockerignore
Zonder .dockerignore stuur je mogelijk onnodige bestanden naar de build context (zoals .git, node_modules).
Maak .dockerignore:
cat > .dockerignore <<'EOF'
.git
__pycache__
*.pyc
.env
EOF
5) Pin versies
Pin waar mogelijk versies van dependencies om onverwachte breuken te voorkomen.
Docker Compose: meerdere services
Docker Compose is handig als je meerdere containers samen wilt laten werken, bijvoorbeeld een webapp plus database.
We maken een compose.yml voor Flask + PostgreSQL.
Bestanden aanmaken
Zorg dat je in flask-app/ zit. Maak compose.yml:
cat > compose.yml <<'EOF'
services:
web:
build: .
container_name: flask_web
ports:
- "5000:5000"
environment:
- DATABASE_URL=postgresql://postgres:geheim@db:5432/postgres
depends_on:
- db
db:
image: postgres:16-alpine
container_name: flask_db
environment:
- POSTGRES_PASSWORD=geheim
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
pgdata:
EOF
Belangrijk:
depends_onzorgt voor startvolgorde, maar garandeert niet dat de database al klaar is. In echte projecten gebruik je healthchecks of retry-logica.dbis bereikbaar als hostnamedbbinnen Compose-netwerk.
Starten en stoppen
Start:
docker compose up -d --build
Bekijk status:
docker compose ps
Logs:
docker compose logs -f
Stop (containers blijven bestaan):
docker compose stop
Stop en verwijder containers/netwerk (volume blijft tenzij je -v gebruikt):
docker compose down
Alles weg inclusief volumes:
docker compose down -v
Logs, debugging en troubleshooting
Logs bekijken
Voor een losse container:
docker logs --tail 200 -f web
Met timestamps:
docker logs -t --tail 200 web
Inspecteren van configuratie
Bekijk details:
docker inspect web
Handig om te zien:
- IP-adres
- mounts/volumes
- environment variables
- command/entrypoint
Filter met een format (voorbeeld):
docker inspect -f '{{.NetworkSettings.IPAddress}}' web
In een container kijken
Shell openen:
docker exec -it web sh
Of bij Debian/Ubuntu images:
docker exec -it <naam> bash
Veelvoorkomende problemen
Poort al in gebruik
Foutmelding bij -p 8080:80: poort 8080 is bezet. Kies een andere hostpoort:
docker run -d -p 8081:80 nginx:alpine
Container stopt meteen
Bekijk logs:
docker logs <naam>
Vaak is het commando klaar of crasht de app. Een container blijft alleen draaien zolang het hoofdproces draait.
Geen netwerkverbinding tussen containers
Zorg dat ze op hetzelfde user-defined netwerk zitten of via Compose. Test DNS:
docker exec -it client nslookup web
(Installeer eventueel bind-tools in Alpine: apk add --no-cache bind-tools.)
Security en rechten
Draai zo min mogelijk met root
Root in een container is niet automatisch root op de host, maar het vergroot wel risico’s bij misconfiguraties. Gebruik USER in Dockerfile waar mogelijk.
Beperk capabilities en read-only filesystem
Voor extra hardening kun je opties gebruiken zoals:
docker run --read-only --cap-drop ALL --cap-add NET_BIND_SERVICE ...
Dit vereist dat je applicatie kan werken zonder schrijfbare rootfilesystem (gebruik volumes voor noodzakelijke schrijfpaden).
Geheimen en omgevingsvariabelen
Stop geen wachtwoorden in je Dockerfile. Gebruik environment variables of secret-mechanismen. In Compose kun je met .env werken, maar behandel dat bestand als geheim en zet het niet in publieke repositories.
Opruimen en onderhoud
Docker kan veel schijfruimte gebruiken door oude images, gestopte containers en ongebruikte volumes.
Verwijder gestopte containers
docker container prune
Verwijder ongebruikte images
docker image prune
Alles ongebruikt (voorzichtig):
docker system prune
Inclusief ongebruikte volumes (extra voorzichtig):
docker system prune --volumes
Schijfruimte bekijken
docker system df
Volgende stappen
Als je de basis beheerst, zijn dit logische vervolgstappen:
- Multi-stage builds voor kleinere images (vooral bij Go, Node, Java).
- Healthchecks en restart policies:
docker run --restart unless-stopped ... - Observability: metrics en tracing, bijvoorbeeld via een reverse proxy of een monitoringstack.
- Orchestratie: Kubernetes of Docker Swarm voor schaal en high availability.
- CI/CD: images bouwen, testen en pushen naar een registry.
Samenvatting
Je hebt geleerd:
- Wat Docker is en waarom containers anders zijn dan virtuele machines.
- Hoe je images downloadt, containers start/stop/verwijdert en commando’s uitvoert.
- Hoe poortmapping werkt en hoe containers elkaar via netwerken kunnen bereiken.
- Hoe je data persistent maakt met volumes en bind mounts.
- Hoe je een eigen image bouwt met een Dockerfile en een multi-service setup maakt met Docker Compose.
- Hoe je logs en inspect gebruikt om problemen te debuggen.
- Hoe je basis security en onderhoud toepast.
Met deze basis kun je vrijwel elk project containeriseren: van simpele webservers tot complete ontwikkelomgevingen met meerdere services.