Netwerken in Docker Compose: services laten samenwerken in productie
Hoe configureer je Docker Compose networking correct voor productie? Custom networks, DNS resolution, isolatie en troubleshooting.
Jean-Pierre Broeders
Freelance DevOps Engineer
Netwerken in Docker Compose: services laten samenwerken in productie
Twee containers die niet met elkaar kunnen praten. Het klinkt als een simpel probleem, maar het komt verrassend vaak voor. Meestal omdat het standaard network van Docker Compose net even anders werkt dan verwacht.
Het default network
Docker Compose maakt automatisch een bridge network aan voor elke stack. Alle services in dezelfde docker-compose.yml belanden op dat network en kunnen elkaar bereiken via hun service naam. Dus als er een api en een postgres service bestaan, dan werkt postgres://postgres:5432 vanuit de api-container gewoon. Geen IP-adressen nodig.
Dat klinkt mooi. En voor development is het prima. Maar in productie zitten er een paar addertjes onder het gras.
# Simpele setup - alles op één network
services:
api:
image: myapp/api:latest
ports:
- "8080:8080"
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
redis:
image: redis:7-alpine
Het probleem: alle services kunnen met alle andere services praten. De Redis cache kan de database bereiken. De database kan de API aanspreken. Er is geen enkele isolatie.
Custom networks voor isolatie
De oplossing is simpel maar wordt vaak overgeslagen: splits het verkeer op in logische networks.
services:
nginx:
image: nginx:alpine
ports:
- "443:443"
networks:
- frontend
api:
image: myapp/api:latest
networks:
- frontend
- backend
worker:
image: myapp/worker:latest
networks:
- backend
postgres:
image: postgres:16
networks:
- backend
redis:
image: redis:7-alpine
networks:
- backend
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
Wat gebeurt hier? Nginx zit alleen op het frontend network en kan dus niet rechtstreeks bij de database. De API zit op beide networks — die vormt de brug. En het backend network heeft internal: true, wat betekent dat containers op dat network geen uitgaand internetverkeer hebben. Postgres kan nergens naartoe bellen. Precies wat je wilt.
DNS resolution en aliassen
Een handige feature die weinig wordt gebruikt: network aliases. Hiermee kan een service meerdere namen hebben, afhankelijk van het network.
services:
api:
image: myapp/api:latest
networks:
frontend:
aliases:
- app-backend
backend:
aliases:
- data-consumer
Vanuit nginx is de API bereikbaar als api én als app-backend. Vanuit postgres als api én als data-consumer. Klinkt als overkill, maar het wordt handig bij migraties. Een oude service verwacht hostname legacy-api? Voeg een alias toe en de oude configuratie blijft werken zonder aanpassingen.
Wanneer DNS resolution hapert
Docker's ingebouwde DNS werkt goed, maar er zijn valkuilen. De meest voorkomende: een container start sneller op dan de service waar hij van afhankelijk is.
services:
api:
image: myapp/api:latest
depends_on:
postgres:
condition: service_healthy
networks:
- backend
postgres:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
networks:
- backend
depends_on met een health check condition voorkomt dat de API start voordat Postgres daadwerkelijk verbindingen accepteert. Zonder die condition wacht Docker alleen tot de container draait — niet tot de service klaar is. Groot verschil.
Een andere DNS-gerelateerde valkuil: caching. Docker cached DNS responses niet agressief, maar applicaties soms wel. Java-applicaties zijn hier berucht om. De JVM cached DNS lookups standaard voor altijd (of heel lang). Als een container opnieuw start en een ander IP krijgt, blijft de Java-app het oude adres gebruiken.
De fix zit in de JVM configuratie:
-Dsun.net.inetaddr.ttl=30
-Dsun.net.inetaddr.negative.ttl=10
Of in java.security:
networkaddress.cache.ttl=30
networkaddress.cache.negative.ttl=10
Communicatie tussen Compose stacks
Soms draaien services in aparte docker-compose.yml bestanden. Een monitoring stack apart van de applicatie stack, bijvoorbeeld. Die moeten wel met elkaar kunnen praten.
De aanpak: een extern network dat beide stacks delen.
# In de applicatie stack
networks:
shared:
name: monitoring-bridge
external: true
backend:
driver: bridge
# In de monitoring stack
networks:
shared:
name: monitoring-bridge
driver: bridge
De monitoring stack maakt het network aan. De applicatie stack gebruikt het via external: true. Services op het gedeelde network vinden elkaar via DNS, ongeacht in welke Compose file ze staan.
Let op: het network moet bestaan voordat de applicatie stack start. Anders faalt docker compose up met een foutmelding over een onbekend network. Start dus altijd de stack die het network aanmaakt als eerste.
Troubleshooting
Wanneer containers niet met elkaar praten, is systematisch checken de snelste weg naar een oplossing.
# Bekijk welke networks bestaan
docker network ls
# Inspecteer een specifiek network
docker network inspect backend
# Check vanuit een container of DNS werkt
docker exec -it api nslookup postgres
# Test connectiviteit
docker exec -it api nc -zv postgres 5432
| Symptoom | Mogelijke oorzaak | Oplossing |
|---|---|---|
| Connection refused | Service luistert op localhost i.p.v. 0.0.0.0 | Bind op 0.0.0.0 in de applicatie config |
| Name resolution failed | Containers op verschillende networks | Zet ze op een gedeeld network |
| Intermitterende timeouts | DNS caching in de applicatie | TTL verlagen of DNS cache uitzetten |
| Kan externe services niet bereiken | Network staat op internal: true | Gebruik een apart network voor uitgaand verkeer |
Afsluitend
Networking in Docker Compose hoeft niet complex te zijn. Splits verkeer op met custom networks, gebruik internal: true waar het kan, en test connectiviteit voordat het in productie problemen oplevert. Het kost vijf minuten extra bij het opzetten, maar bespaart uren debugging op een vrijdagavond.
