← Terug naar tutorials

Aan de slag met Docker: beginnersgids

dockercontainersdevopsbeginnersinstallatietutorial

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

  1. Wat is Docker precies?
  2. Belangrijke begrippen: image, container, registry
  3. Installatie en controle
  4. Eerste container draaien
  5. Werken met images
  6. Containers beheren
  7. Poorten, netwerken en bereikbaarheid
  8. Volumes en data persistent maken
  9. Een eigen image bouwen met een Dockerfile
  10. Best practices voor Dockerfiles
  11. Docker Compose: meerdere services
  12. Logs, debugging en troubleshooting
  13. Security en rechten
  14. Opruimen en onderhoud
  15. 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:

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?


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


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?

  1. Docker zoekt lokaal naar de image hello-world.
  2. Als die niet bestaat, wordt hij gedownload uit de registry.
  3. Docker maakt een container aan op basis van die image.
  4. 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:

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

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:

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

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

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:

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:

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:

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:


Samenvatting

Je hebt geleerd:

Met deze basis kun je vrijwel elk project containeriseren: van simpele webservers tot complete ontwikkelomgevingen met meerdere services.