Beetje Coderen

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.

⏱ 70 minuten leestijd + oefenen

Illustratie: een netwerk van verbonden blokken met een hartslag-grafiek, gezonde containers

🎯 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_isready zegt pas "ok" als PostgreSQL verbindingen accepteert.
  • condition: service_healthy laat web wachten tot de database gezond is.
  • De webcheck gebruikt Python, want python:3.12-slim heeft geen curl.
  • start_period geeft 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 met docker compose exec db pg_isready -U teller -d teller.
  • curl: not found in 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

  1. Voeg beide healthchecks en condition: service_healthy toe. Start met docker compose up -d na down -v: krijg je nog een 500 bij de eerste curl?
  2. Voeg de netwerken toe en test het debug-voorbeeld.
  3. Maak compose.override.yml met een bind mount en verander de tekst in app.py zonder opnieuw te bouwen.
  4. Stop de database met docker compose stop db. Wat doet de webapp, en wat zegt docker 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_healthy lossen de opstartrace op.
  • Eigen netwerken en internal: true schermen de database af.
  • Named volumes voor data, bind mounts voor ontwikkelen, compose.override.yml voor lokale instellingen.
  • Compose herstart bij crashes, niet bij unhealthy.
  • Volgende les: images goed bouwen.

📝 Quiz: Netwerken, volumes en healthchecks in Compose

5 vragen · 70% nodig om te slagen

1. Wat doet condition: service_healthy?
2. Wat doet internal: true op een netwerk?
3. Waarom faalt ping db vanuit een container op alleen voorkant?
4. Herstart Compose een unhealthy container automatisch?
5. Wanneer gebruik je een bind mount?