Les 2 · Module 1 · Compose goed onder de knie
Netwerken, volumes en healthchecks in Compose
Maak de teller-app robuust in Compose: healthchecks met depends_on condition, eigen netwerken die de database afschermen, restart-beleid en het verschil tussen volumes en bind mounts.
🎯 Na deze les kun je
- een healthcheck voor PostgreSQL en de webapp schrijven
- depends_on met condition: service_healthy gebruiken
- eigen netwerken gebruiken om de database af te schermen
- named volumes, bind mounts en restart-beleid bewust kiezen
In les 1 noteerde je dat de webapp soms start voordat PostgreSQL klaar is. In deze les los je dat op met healthchecks, en scherm je de database af met eigen netwerken. Dat zijn precies de ideeën die je in Kubernetes terugziet als readiness probes en netwerkbeleid.
Healthchecks
services:
db:
image: postgres:16
environment:
POSTGRES_USER: teller
POSTGRES_PASSWORD: geheim
POSTGRES_DB: teller
volumes:
- dbdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U teller -d teller"]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s
restart: unless-stopped
networks: [achterkant]
web:
build: .
image: teller:1.0
ports: ["8080:8080"]
environment:
DATABASE_URL: postgres://teller:geheim@db:5432/teller
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/healthz')"]
interval: 10s
timeout: 3s
retries: 3
restart: unless-stopped
networks: [voorkant, achterkant]
networks:
voorkant:
achterkant:
internal: true
volumes:
dbdata:
pg_isreadyzegt pas "ok" als PostgreSQL verbindingen accepteert.condition: service_healthylaatwebwachten tot de database gezond is.- De webcheck gebruikt Python, want
python:3.12-slimheeft geencurl. start_periodgeeft de container opstarttijd waarin mislukte checks nog niet tellen.
docker compose up -d --build
docker compose ps # STATUS: Up 20 seconds (healthy)
Netwerken
Met internal: true heeft het netwerk achterkant geen verbinding naar buiten, en zit alleen wat je eraan koppelt erin. De database hangt
alleen aan achterkant; de webapp aan beide. Een container die je later toevoegt aan alleen voorkant kan de database niet bereiken.
Voorspel eerst: je voegt een service debug: { image: alpine, command: sleep 1d, networks: [voorkant] } toe en doet
docker compose exec debug ping -c1 db. Wat gebeurt er?
Uitkomst
ping: bad address 'db'. De naam db wordt alleen opgelost binnen netwerken waar db aan hangt. debug zit alleen op voorkant, dus
bestaat db voor hem niet. Dit is "netwerksegmentatie": niet alles mag met alles praten. In Kubernetes doe je dit met NetworkPolicies (les 18).
Volumes en bind mounts
| Soort | Voorbeeld | Gebruik |
|---|---|---|
| Named volume | dbdata:/var/lib/postgresql/data |
data van databases; Docker beheert de plek |
| Bind mount | ./app.py:/app/app.py |
ontwikkelen: code van je laptop direct in de container |
| tmpfs | tmpfs: /tmp |
tijdelijke bestanden in het geheugen |
Voor ontwikkelen kun je een compose.override.yml maken die alleen lokaal geldt:
services:
web:
volumes: ["./app.py:/app/app.py"]
command: ["flask", "--app", "app", "run", "--host", "0.0.0.0", "--port", "8080", "--debug"]
Compose leest compose.override.yml automatisch naast compose.yml.
Restart-beleid
restart: unless-stopped start een gecrashte container opnieuw, behalve als jij hem stopte. Let op: Compose herstart bij een crash,
niet bij een unhealthy status. Kubernetes doet dat wel, met een liveness probe (les 14).
Typische fouten
dependency failed to start: container teller-db-1 is unhealthy: de healthcheck faalt altijd. Test het commando metdocker compose exec db pg_isready -U teller -d teller.curl: not foundin de healthcheck: slanke images hebben geen curl. Gebruik een taal die er wel is, of installeer het.- Webapp niet bereikbaar na
internal: true: je zette het netwerk met de poort op internal. Alleen het achterkant-netwerk is internal.
Oefenen
- Voeg beide healthchecks en
condition: service_healthytoe. Start metdocker compose up -dnadown -v: krijg je nog een 500 bij de eerste curl? - Voeg de netwerken toe en test het
debug-voorbeeld. - Maak
compose.override.ymlmet een bind mount en verander de tekst inapp.pyzonder opnieuw te bouwen. - Stop de database met
docker compose stop db. Wat doet de webapp, en wat zegtdocker compose ps?
Modelantwoord oefening 1
Nee: web start pas als db healthy is. docker compose up toont Container teller-db-1 Waiting en daarna Healthy.
Modelantwoord oefening 2
ping: bad address 'db' voor debug. Vanuit web werkt docker compose exec web python -c "import socket; print(socket.gethostbyname('db'))" wel.
Modelantwoord oefening 3
Met de Flask-ontwikkelserver en --debug herlaadt de app bij elke wijziging: curl localhost:8080 toont direct je nieuwe tekst. Gebruik dit
alleen lokaal; in productie draait gunicorn uit de image.
Modelantwoord oefening 4
curl localhost:8080 geeft een 500 (geen database), maar /healthz blijft ok: de webcheck controleert alleen of de app draait. ps toont web dus
nog als healthy. Dat onderscheid (leeft hij? kan hij werken?) is precies liveness tegenover readiness in les 14.
Lokaal oefenen
Geen live lab. Bekijk de details van een healthcheck met docker inspect --format '{{json .State.Health}}' teller-db-1.
Samenvatting
- Healthchecks plus
condition: service_healthylossen de opstartrace op. - Eigen netwerken en
internal: trueschermen de database af. - Named volumes voor data, bind mounts voor ontwikkelen,
compose.override.ymlvoor lokale instellingen. - Compose herstart bij crashes, niet bij unhealthy.
- Volgende les: images goed bouwen.