Hoofdstuk 1

Wat is Docker eigenlijk?

Voordat je iets beheert, is het handig om te begrijpen waarom Docker bestaat en wat het oplost.

Het klassieke probleem

Je hebt vast wel eens gehoord: "Bij mij werkt het wel!". Dat is precies het probleem dat Docker oplost. Software heeft afhankelijkheden: de juiste versie van Node.js, de juiste database-driver, de juiste configuratiewaarden. Als die versies net iets anders zijn op jouw laptop versus op de server, crasht de applicatie.

🚒 De Analogy: Vrachtcontainers

Vroeger werden goederen los verscheept en moest elke haven alles anders inladen β€” chaos en vertraging. Toen kwamen standaard vrachtcontainers: alles past overal, elk schip laadt ze hetzelfde. Docker doet exact hetzelfde voor software. Een Docker-container bevat je applicatie plus alles wat die nodig heeft. Je zet hem op Ontwikkelaar-A, Server B, Server C β€” hij werkt overal precies hetzelfde.

Wat is het verschil met een virtuele machine (VM)?

Kenmerk Virtuele Machine (VM) Docker Container
Heeft eigen OS βœ… Volledig eigen kernel ❌ Deelt de kernel van de host
Opstartsnelheid Minuten Seconden (of minder)
Geheugengebruik Hoog (GBs per VM) Laag (MBs per container)
Isolatie Maximaal Goed (gedeelde kernel)
Portabiliteit Beperkt (GBs groot) Uitstekend (MBs groot)
ℹ️

Docker containers zijn geen mini-computers. Ze draaien direct op de Linux-kernel van jouw server, maar hebben hun eigen afgeschermde bestandssysteem, processenlijst en netwerk.


Hoofdstuk 2

De kernconcepten

Docker werkt met een paar bouwblokken die je moet kennen. Eenmaal begrepen, snap je de rest vanzelf.

πŸ“„

Dockerfile

Een recept. Een tekstbestand met stap-voor-stap instructies over hoe een Image gebouwd moet worden. Bijvoorbeeld: "Begin met Node.js 20, kopieer de bestanden, installeer dependencies, start de server."

πŸ“¦

Image

Het gebakken recept. Una Image is een onveranderlijk snapshot van je applicatie, klaar om te draaien. Je kunt een Image vergelijken met een ISO-bestand of een install-DVD. Je kunt er meerdere containers van draaien.

πŸƒ

Container

Een draaiende instantie van een Image. Start je een Image op, dan wordt het een Container. Je kunt tientallen containers van dezelfde image draaien tegelijk β€” elk in zijn eigen afgeschermde omgeving.

πŸ—„οΈ

Volume

Persistente opslag buiten de container. Als een container stopt of verwijderd wordt, verdwijnt alles erin β€” tenzij je een Volume gebruikt. Volumes overleven herstarts en container-verwijderingen.

🌐

Netwerk

Het interne telefoonnet tussen containers. Containers in hetzelfde Docker-netwerk kunnen elkaar bellen via de servicenaam (bv. db), zonder dat je IP-adressen hoeft te weten.

πŸ“‹

Docker Compose

Een orkestratietool. Met één YAML-bestand (docker-compose.yml) definieer je meerdere containers tegelijk, inclusief hun netwerken, volumes en afhankelijkheden. Starten gaat met één commando.

πŸ• Analogy: Een pizzeria

De Dockerfile is het pizzarecept. De Image is de bereide pizza in de doos, klaar voor bezorging. De Container is de pizza op tafel die iemand eet. De Volume is de koelkast β€” ook na de maaltijd blijft de inhoud bestaan. Het Netwerk is het bezorgsysteem dat alles verbindt.


Hoofdstuk 3

Hoe werkt het samen?

Zo ziet de architectuur eruit van de coin-trader op deze server β€” van buitenaf tot diep in de container.

── JOUW BROWSER (192.168.2.x) ──────────────────────────
HTTP verzoek β†’ Apache (poort 80/443)
                  β”‚
                  β”œβ”€ /api β†’ proxy β†’ Server Container :3005
                  β”œβ”€ /docker-manager β†’ proxy β†’ Docker Manager :9090
                  β””─ /* β†’ Statische bestanden /var/www/html

── DOCKER INTERN NETWERK (coin-network) ──────────────────
coin-trader-server-1 β†’ communiceert met β†’ coin-trader-db-1
                             (via hostnaam "db":3306)

coin-trader-client-1 β†’ serveert frontend via Apache (poort 80 intern)

De levenscyclus van een container

Elke container doorloopt een eenvoudige levenscyclus:

Levenscyclus
  # 1. Image bouwen (eenmalig of na codewijziging)
  docker build -t mijn-app .

  # 2. Container starten (image β†’ draaiende container)
  docker run -d --name mijn-container mijn-app

  # 3. Container is actief β†’ verwerkt verzoeken
  docker ps               # status bekijken
  docker logs mijn-container

  # 4. Container stoppen (maar niet verwijderen)
  docker stop mijn-container

  # 5. Container verwijderen
  docker rm mijn-container

  # 6. Image verwijderen (ruimt schijfruimte op)
  docker rmi mijn-app

Hoofdstuk 4

EssentiΓ«le commando's

Je hoeft niet alles te kennen, maar deze commando's gebruik je dagelijks.

Containers beheren

bash – Containers
# Alle draaiende containers zien
docker ps

# Alle containers zien (ook gestopte)
docker ps -a

# Container starten / stoppen / herstarten
docker start mijn-container
docker stop mijn-container
docker restart mijn-container

# Container verwijderen (moet eerst gestopt zijn)
docker rm mijn-container

# Container verwijderen, ook als hij draait
docker rm -f mijn-container

# Logs bekijken (laatste 50 regels, live volgen)
docker logs --tail=50 --follow mijn-container

# Shell openen IN een draaiende container
docker exec -it mijn-container sh

Images beheren

bash – Images
# Alle lokale images zien
docker images

# Image downloaden van Docker Hub
docker pull mariadb:10.11

# Image bouwen vanuit een Dockerfile
docker build -t mijn-app-naam .

# Image exporteren naar een bestand (voor migratie!)
docker save -o mijn-app.tar mijn-app-naam

# Image importeren op een andere server
docker load -i mijn-app.tar

# Ongebruikte images, containers en netwerken opruimen
docker system prune -f

Systeem informatie

bash – Systeem
# Overzicht van schijfgebruik
docker system df

# Gedetailleerde info over Docker installatie
docker info

# Live CPU/geheugen gebruik per container
docker stats

Hoofdstuk 5

Netwerken begrijpen

Netwerken is vaak de bron van verwarring voor beginners. Eenmaal begrepen, is het eigenlijk simpel.

Twee werelden: intern en extern

Docker heeft twee "werelden" qua netwerk:

Wereld Wie bereikt het Hoe
Intern Docker netwerk Alleen containers onderling Via servicenaam (bv. db)
Extern (host poorten) Jij, de browser, het internet Via poort-mapping (bv. 8080:80)

Poort-mapping uitgelegd

Als je in docker-compose.yml schrijft "8080:80", dan zeg je: "Verzoeken die binnenkomen op de host via poort 8080 worden doorgestuurd naar poort 80 binnenin de container."

🏒 Analogy: een kantoorgebouw

De host-server is een kantoorgebouw met een begane grond (poorten). Elke container is een kantoor op een verdieping. De receptie (poort-mapping) stuurt bezoekers van de voordeur (host poort 8080) door naar het juiste kantoor (container intern poort 80). Zonder doorschakeling is het kantoor onbereikbaar van buitenaf, maar de kantoren onderling kunnen elkaar wel intern bereiken via de gangen (Docker netwerk).

bash – Netwerken bekijken
# Alle Docker netwerken
docker network ls

# Details van het coin-trader netwerk
docker network inspect coin-trader_coin-network

# IP-adressen van draaiende containers
docker inspect --format '{{.Name}} -> {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
  coin-trader-db-1 coin-trader-server-1 coin-trader-client-1
⚠️

Let op: Interne IP-adressen (172.18.x.x) veranderen elke keer als je docker compose down && docker compose up doet. Gebruik altijd de servicenaam (zoals db) voor communicatie tussen containers β€” nooit een hardcoded IP.


Hoofdstuk 6

Data opslaan met Volumes

Dit is een van de meest onderschatte concepten β€” en de bron van kostbare dataloss als je het verkeerd doet.

Waarom volumes zo belangrijk zijn

Een container is van nature vluchtig. Alles wat erin wordt opgeslagen verdwijnt zodra de container wordt verwijderd of opnieuw gebouwd. Dat is soms handig (reproduceerbaar), maar voor een database is dat rampzalig.

🚨

Kritieke valkuil: Als je een MariaDB-container verwijdert zonder volume, ben je alle databasedata kwijt. Altijd een volume definiΓ«ren voor databases!

In onze docker-compose.yml staat dit geregeld:

docker-compose.yml – Volume configuratie
  db:
    image: mariadb:10.11
    volumes:
      - db_data:/var/lib/mysql   # ← data gaat naar het volume 'db_data'

volumes:
  db_data:                              # ← Docker beheert dit volume

Soorten volumes

Type Hoe Gebruik
Named Volume db_data:/var/lib/mysql Database data β€” Docker beheert de locatie
Bind Mount /host/pad:/container/pad Ontwikkeling β€” realtimecode synchronisatie
Anoniem Geen naam opgegeven Tijdelijk β€” vermijden!
bash – Volumes bekijken en backuppen
# Alle volumes tonen
docker volume ls

# Volume backuppen naar een tar-bestand
docker run --rm \
  -v coin-trader_db_data:/data \
  -v /tmp:/backup \
  alpine tar czf /backup/db_backup.tar.gz /data

# Volume herstellen vanuit backup
docker run --rm \
  -v coin-trader_db_data:/data \
  -v /tmp:/backup \
  alpine tar xzf /backup/db_backup.tar.gz -C /

Hoofdstuk 7

Docker Compose begrijpen

Met Docker Compose beheer je meerdere containers als één project β€” het hart van onze coin-trader setup.

Anatomie van een docker-compose.yml

docker-compose.yml – Uitleg per sectie
services:              # ← Hier definieer je elke container

  db:                  # ← Servicenaam (ook hostnaam op intern netwerk)
    image: mariadb:10.11  # ← Welke image te gebruiken
    restart: always    # ← Automatisch herstarten bij crash
    env_file:          # ← Laad omgevingsvariabelen uit bestand
      - ./server/.env
    volumes:
      - db_data:/var/lib/mysql  # ← Data opslaan buiten de container
    ports:
      - "3307:3306"     # ← host_poort:container_poort
    networks:
      - coin-network   # ← Intern netwerk

  server:
    build:             # ← Bouw image via Dockerfile (in plaats van pull)
      context: ./server
      dockerfile: Dockerfile
    depends_on:        # ← Start pas NA de db container
      - db
    # ... (overige velden)

networks:
  coin-network:        # ← Definieer het interne netwerk
    driver: bridge

volumes:
  db_data:             # ← Definieer het named volume

Meerdere omgevingen (test vs. productie)

Met Docker Compose kun je eenvoudig meerdere omgevingen naast elkaar draaien met override-bestanden:

bash – Omgevingen draaien
# Testomgeving (ander project-label, andere poorten)
sudo docker compose -p coin-test -f docker-compose.yml -f docker-compose.test.yml up -d

# Productieomgeving
sudo docker compose -p coin-prod -f docker-compose.yml -f docker-compose.prod.yml up -d

# Status per project
sudo docker compose -p coin-test ps
sudo docker compose -p coin-prod ps

Hoofdstuk 8

Migreren naar een andere server

Dit is waar veel mensen de mist ingaan. Hier is het complete, veilige stappenplan.

πŸ’‘

De kracht van Docker is dat de applicatiecode reproductief is β€” je hoeft alleen de code + Dockerfiles + .env mee te nemen. Maar de databasedata zit in een volume en moet apart geback-upt worden!

Stap-voor-stap migratieplan

Wat je NOOIT mag vergeten bij migratie

🚨

De .env bestanden! Deze staan niet in de Git repository (terecht, want er zitten wachtwoorden in). Kopieer ze altijd handmatig mee: server/.env en de root .env.

⚠️

Poortconflicten op de nieuwe server. Controleer wat er al draait: ss -tlnp | grep -E '3306|3005|80'. Pas poorten in docker-compose.yml aan indien nodig.

⚠️

Firewall regels. Op de nieuwe server moeten de benodigde poorten open staan: sudo ufw allow 80 && sudo ufw allow 443.


Hoofdstuk 9

Veelgemaakte fouten & valkuilen

Leer van de fouten die iedereen maakt β€” inclusief wat we zelf zijn tegengekomen tijdens de installatie op deze server.

πŸͺ€ Valkuil 1: Data opslaan IN de container

Je schrijft bestanden rechtstreeks in de container (bv. uploads, logs, database). Als de container wordt verwijderd of herbouwd: alles weg.

βœ… Fix: Gebruik altijd een named volume of bind mount voor data die bewaard moet blijven.

πŸͺ€ Valkuil 2: Hardcoded IP-adressen gebruiken

Je gebruikt 172.18.0.4 als database-host in je applicatie. Na een docker compose down/up krijgen containers andere IP-adressen en werkt niets meer.

βœ… Fix: Gebruik altijd de servicenaam als hostnaam: DB_HOST=db.

πŸͺ€ Valkuil 3: .env bestanden vergeten bij migratie

Je kopieert de broncode maar vergeet de .env bestanden. De containers starten, maar de applicatie geeft cryptische fouten over ontbrekende configuratie.

βœ… Fix: Maak een checklist van alle configuratiebestanden die NIET in Git zitten en kopieer die altijd als eerste.

πŸͺ€ Valkuil 4: Poortconflicten

Je start een container met poort 3306, maar de host MariaDB gebruikt die al. De container crasht bij opstarten met "address already in use".

βœ… Fix: Controleer altijd bezette poorten met ss -tlnp en gebruik afwijkende host-poorten (bv. 3307).

πŸͺ€ Valkuil 5: docker-compose v1 vs v2

docker-compose (oud, Python, v1.29) is incompatibel met Docker Engine 28+. Je krijgt cryptische errors zoals "ContainerConfig KeyError".

βœ… Fix: Gebruik altijd docker compose (zonder koppelteken) β€” dat is de moderne v2 plugin.

πŸͺ€ Valkuil 6: Te groot bouwen (alles in één container)

Beginners stoppen database, webserver en applicatie in één container. Dat werkt, maar is niet de Docker-filosofie en maakt schalen, herstarten en backuppen lastig.

βœ… Fix: Één verantwoordelijkheid per container. Zoals bij coin-trader: db, server, client als aparte containers.

πŸͺ€ Valkuil 7: Schijf volloopt door oude images

Elke rebuild maakt nieuwe image layers. Na verloop van tijd staat de schijf vol met verlaten images (dangling images).

βœ… Fix: Periodiek opruimen met sudo docker system prune -f. Controleer schijfgebruik met sudo docker system df.

Snel naslagwerk

Docker Cheatsheet

De meest gebruikte commando's op één plek.

bash – Complete cheatsheet
──── CONTAINERS ────────────────────────────────────────────
docker ps                            # Draaiende containers
docker ps -a                         # Alle containers
docker start|stop|restart <naam>    # Starten/stoppen/herstarten
docker rm -f <naam>                 # Forceer verwijderen
docker logs --tail=100 <naam>       # Logs bekijken
docker exec -it <naam> sh           # Shell in container
docker stats                         # CPU/geheugen live

──── IMAGES ────────────────────────────────────────────────
docker images                        # Alle images
docker build -t naam .               # Bouwen vanuit Dockerfile
docker pull naam:tag                 # Downloaden van Docker Hub
docker rmi naam                      # Image verwijderen
docker save -o bestand.tar naam      # Exporteren
docker load -i bestand.tar           # Importeren

──── VOLUMES ───────────────────────────────────────────────
docker volume ls                     # Alle volumes
docker volume inspect <naam>        # Details
docker volume rm <naam>             # Verwijderen (pas op!)

──── COMPOSE (gebruik ALTIJD zonder koppelteken) ───────────
docker compose up -d                 # Alles starten in achtergrond
docker compose up -d --build         # Starten + images herbouwen
docker compose down                  # Alles stoppen + verwijderen
docker compose ps                    # Status
docker compose logs <service>       # Logs van een service
docker compose restart <service>    # Herstarten

──── SYSTEEM ───────────────────────────────────────────────
docker system df                     # Schijfgebruik overview
docker system prune -f               # Ruim ongebruikte zaken op
docker info                          # Systeem info

──── MIGRATIE VOLGORDE ─────────────────────────────────────
# 1. Database dumpen
docker exec -i db mysqldump -u user -p'pass' dbname > backup.sql
# 2. Bestanden kopiΓ«ren (scp of git)
# 3. .env bestanden kopiΓ«ren (!)
# 4. Docker installeren op nieuwe server
# 5. docker compose up -d --build
# 6. Database importeren
docker exec -i db mysql -u user -p'pass' dbname < backup.sql