Je kunt Docker maandenlang in productie draaien zonder zichtbare problemen. Containers starten, applicaties reageren, niets breekt. Maar één blootgestelde poort of één verkeerd geconfigureerde permissie kan een aanvaller een voet tussen de deur geven die hij nauwelijks hoefde te forceren. De meeste beveiligingsfouten in Docker zien er niet uit als fouten, totdat er iets misgaat.
Dit artikel behandelt de specifieke configuraties die containeromgevingen kwetsbaar maken, wat een aanvaller daarmee kan bereiken, en sluit af met een checklist die je vandaag nog op je eigen setup kunt toepassen.
Waarom Docker-beveiliging lastiger is dan het lijkt
Containers voelen geïsoleerd aan. Je start er een, hij draait in zijn eigen procesruimte, en vanuit de container bestaat de buurcontainer niet. Je krijgt wel isolatie, maar die is slechts gedeeltelijk. Containers delen de kernel van de host, wat betekent dat een proces in een container onder bepaalde omstandigheden volledig toegang kan krijgen tot het hostsysteem.
Docker wordt standaard geconfigureerd voor het gemak van ontwikkelaars, niet voor productiebeveiliging. Root-toegang is ingeschakeld. Alle poorten zijn te binden aan alle interfaces. Geen runtime-monitoring. De meeste ontwikkelaars laten die instellingen staan, deployen de container en gaan verder. Dat is prima om snel aan de slag te gaan, maar het is geen volwaardige beveiligingsconfiguratie.
Volgens Red Hat's 2024 State of Kubernetes Security report: 67% van de organisaties heeft het deployen van applicaties vertraagd of uitgesteld vanwege beveiligingsproblemen met containers of Kubernetes. Die wrijving komt meestal niet van aanvallen. Het komt doordat teams ontdekken dat hun containeropzet extra beveiliging nodig had die er nooit in was gebouwd.
We zien vaak containers in productie draaien met exact dezelfde configuratie als op de lokale machine van een ontwikkelaar. Daar stapelen Docker-beveiligingsfouten zich stilletjes op, zonder zichtbare symptomen, totdat er een audit plaatsvindt of iets uitvalt.
De fouten die deze kwetsbaarheden veroorzaken zijn specifiek, voorspelbaar en grotendeels te voorkomen, te beginnen op configuratieniveau.
Veelvoorkomende Docker-configuratiefouten
De meeste containerbeveiligingsincidenten beginnen niet met een zero-day exploit. Ze beginnen met een configuratie die op dag één is ingesteld, zonder goed na te denken over netwerkblootstelling of het bereik van rechten.
Standaard Docker-instellingen zijn gemaakt om te werken. Het verschil tussen functioneel en veilig is precies waar beveiligingsrisico's in Docker-containers zich opstapelen, met name in zelfgehoste omgevingen die eenmaal worden uitgerold en daarna nooit meer worden herzien.
We zien dit patroon vaak: containers op servers met een publiek IP-adres, met poortbindingen, gebruikersinstellingen en netwerkconfiguraties die nog exact hetzelfde zijn als bij de eerste uitrol.
Containers draaien als root
Als je een Docker-container start zonder een gebruiker op te geven, draait hij als root. Dat betekent dat elk proces in de container, inclusief je applicatie, root-rechten heeft binnen de namespace van de container.

Root in een container is niet hetzelfde als root op de host, maar de scheiding is niet absoluut. Privilege-escalatie-exploits gericht op de runtime, zoals de goed gedocumenteerde runc CVE-2019-5736 en vergelijkbare runtime-kwetsbaarheden, vereisen vaak een root-containerproces om te slagen.
Containers zonder root-rechten verwijderen de vereiste rootproces waar die exploits op steunen, wat het aanvalsoppervlak voor die klasse kwetsbaarheden aanzienlijk verkleint - al sluiten ze het risico op container-escape niet volledig uit.
Een USER-instructie toevoegen aan je Dockerfile lost dit op. Sommige officiële images worden geleverd met een niet-bevoorrechte gebruiker die je via een USER-instructie kunt activeren, maar veel images gebruiken standaard root zonder kant-en-klare applicatiegebruiker. In die gevallen maak je de gebruiker aan in de Dockerfile voordat je ernaar overschakelt. Voor de meeste zelfgehoste omgevingen elimineert deze ene aanpassing een hele categorie escalatierisico's.
Te veel poorten blootstellen aan het publieke internet
Wanneer je een poort publiceert met Docker, schrijft Docker zijn eigen iptables-regels rechtstreeks. Die regels worden uitgevoerd vóór de firewallregels op hostniveau. Dit is een bekend gedrag dat door de community is gerapporteerd en gedocumenteerd in de handleiding voor pakketfiltering van Docker, geen misconfiguratie, en het betekent dat UFW en vergelijkbare tools niet blokkeren wat Docker al heeft geopend.

Docker schrijft rechtstreeks naar iptables en omzeilt daarmee de standaardinstellingen van UFW en firewalld op veel Linux-hosts. Dit betekent dat een poort gebonden aan 0.0.0.0 publiekelijk bereikbaar kan zijn, zelfs wanneer je firewall correct geconfigureerd lijkt. Cloud-beveiligingsgroepen en DOCKER-USER-ketenregels kunnen dat verkeer nog steeds blokkeren, dus de daadwerkelijke blootstelling hangt af van je specifieke netwerkinrichting.
Bind services waar mogelijk aan 127.0.0.1, routeer publiekelijk verkeer via een reverse proxy en publiceer alleen wat écht externe toegang vereist. Een reverse proxy is de meest betrouwbare manier om te bepalen wat van buitenaf de host bereikbaar is.
Netwerkinsolatie tussen containers negeren
Elke container op dat netwerk kan elke andere container daarop zonder beperkingen bereiken. De standaard bridge past geen verkeersfiltering toe tussen containers die deze delen, en de meeste omgevingen veranderen die configuratie nooit.

Als één container wordt gecompromitteerd, wordt die open communicatie een pad voor laterale beweging. Een frontend-container kan een database, een interne API of alles wat op hetzelfde standaard bridge-netwerk zit bereiken, zelfs wanneer die toegang nooit de bedoeling was.
Door de gebruiker gedefinieerde netwerken geven je expliciete controle over welke containers met elkaar kunnen communiceren, maar één enkel aangepast netwerk dat door al je services wordt gedeeld staat vrij verkeer tussen containers nog steeds toe. Echte isolatie vereist dat services die niet met elkaar mogen communiceren op aparte netwerken worden geplaatst. De standaard bridge uitschakelen is het startpunt, niet de eindstreep.
De Docker-socket over het hoofd zien
De Docker-socket op /var/run/docker.sock is de besturingsinterface voor de volledige Docker-engine. Deze in een container mounten geeft die container directe API-toegang tot de daemon die op de host draait.

Met die toegang kan een container nieuwe containers starten, hostmappen mounten, draaiende containers inspecteren en aanpassen, en feitelijk de hostmachine overnemen. Het aanvalsoppervlak staat gelijk aan root op de host, en daarom verdient elk hulpmiddel dat socket-toegang vereist een zorgvuldige evaluatie.
Voor de meeste gebruikssituaties zijn er veiligere alternatieven: beperkte APIs of Docker-beheerhulpmiddelen die geen socket-toegang vereisen. Docker-in-Docker brengt zijn eigen beveiligings- en operationele afwegingen met zich mee en is geen eenvoudige vervanging.
Configuratiefouten zorgen voor de initiële blootstelling. De keuze van images en afhankelijkheden bepaalt hoe die blootstelling zich in de loop van de tijd opstapelt.
Fouten in images en secrets die de container overleven
Wanneer je een container stopt, stoppen de configuratiefouten daarin ook. Wanneer je opnieuw bouwt vanuit een image met een kwetsbaarheid of een hardgecodeerde credential, herstart het probleem met de container. Fouten op image-niveau worden niet gereset tussen deploys.
Ze reizen mee met de image naar elke omgeving die hem ophaalt, elk registry dat hem opslaat, en elk teamlid dat hem uitvoert. Die persistentie maakt image- en secretsbeheer tot een aparte risicocategorie, die los van configuratie moet worden geauditeerd.
Dit patroon zien we vaak: een image die zorgvuldig werd gekozen bij de start van het project en sindsdien nooit opnieuw gebouwd, terwijl hij langzaam afwijkt van de beveiligingsbasis die hij aanvankelijk vertegenwoordigde.
Onvertrouwde of verouderde images gebruiken
Publieke registries zijn voor iedereen toegankelijk. Kwaadaardige images zijn verspreid via Docker Hub met daarin crypto-miners en backdoors, ingebed in de layergeschiedenis en aanwezig na elke herstart van de container. Verificatie vóór het pullen is belangrijk, vooral voor images van niet-officiële of onbekende uitgevers.

Een apart probleem is veroudering. Een officiële image die je zes maanden geleden hebt gepulld en sindsdien nooit opnieuw hebt gebouwd, heeft steeds meer ongepatchtde Docker-kwetsbaarheden opgestapeld bij elke CVE die tegen zijn packages is gepubliceerd. De image is niet kapot. Hij is gewoon niet meer actueel.
Sonatype's 2024 State of the Software Supply Chain report toont aan dat in 95% van de gevallen waarin een kwetsbaar component wordt gebruikt, er al een gecorrigeerde versie beschikbaar is, en dat 80% van de applicatie-afhankelijkheden langer dan een jaar niet worden bijgewerkt. Dit patroon is ook relevant voor Docker base images, omdat die dezelfde open-source packages gebruiken.
Gebruik officiële images van geverifieerde uitgevers en pin specifieke versietags in plaats van "latest" te gebruiken. Stel een vaste rebuild-planning in om je images actueel te houden.
Secrets hardcoden in Dockerfiles en Compose-bestanden
Credentials die worden ingevoerd via een Dockerfile ENV- of ARG-instructie, hardgecodeerd in een Compose environment-blok, meegegeven als buildargument, of opgeslagen in een .env-bestand dat in versiebeheer staat, verdwijnen niet wanneer je de container stopt. Ze blijven in de layergeschiedenis van de image of in bronbeheer, toegankelijk voor iedereen die daar bij kan.

Dit is een van de meest over het hoofd geziene Docker-beveiligingsfouten, omdat hij tijdens de ontwikkeling geen zichtbare problemen veroorzaakt. Een API-sleutel in een ENV-instructie werkt correct. Maar hij staat ook in je repository, ingebakken in je image, en wordt meegedistribueerd overal waar die image naartoe gaat.
Moderne Docker Compose ondersteunt een native secrets-mechanisme dat credentials bij runtime beschikbaar stelt zonder ze in de image in te bakken. Docker's secrets API en externe secrets managers volgen hetzelfde principe. Dit zijn de opties die credentials volledig buiten build-artefacten en gecommitte bestanden houden.
Runtime-omgevingsvariabelen zijn een verbetering ten opzichte van hardgecodeerde credentials, maar ze zijn nog steeds zichtbaar via Docker inspect-uitvoer, logs en crashdumps. Het is een stap vooruit ten opzichte van ingebakken secrets, maar geen definitieve oplossing.
Container images niet regelmatig bijwerken
Maandenlang dezelfde image draaien is een veelvoorkomende gewoonte. Elke dag die verstrijkt nadat een nieuwe kwetsbaarheid is gepubliceerd maar voordat je een rebuild uitvoert, vergroot het blootstellingsvenster van je containers, zonder dat er iets zichtbaar verandert.
Stel een vaste rebuild-planning in. Automatiseer dat proces waar mogelijk en voer periodiek een kwetsbaarheidsscanner uit op je huidige images. Het doel is niet perfectie. Het gaat erom de tijd tussen het uitbrengen van een patch en het uitrollen ervan zo kort mogelijk te houden.
Toegangscontrole en monitoring raken bij snelle deployments snel op de achtergrond. Het zijn ook de categorieën waar incidenten het langst onopgemerkt blijven.
Hiaten in toegangscontrole en zichtbaarheid
Nadat een container draait met een solide configuratie en actuele images, blijven er twee soorten fouten over. Beide zijn van nature onzichtbaar: een zwak toegangscontroleprobleem merk je pas op als iemand er gebruik van maakt, en een monitoringhiaat valt pas op als je activiteit moet onderzoeken die nooit is gelogd.
Hetzelfde Red Hat 2024-onderzoek toont aan dat 42% van de teams onvoldoende capaciteit had om containersecurity en gerelateerde bedreigingen aan te pakken.
Monitoringhiaten komen doorgaans aan het licht tijdens incidentonderzoeken, niet ervoor. Tegen de tijd dat zichtbaarheid een prioriteit wordt, is het vaak al een reactie op iets in plaats van een preventie ervan.
Zwakke authenticatie en blootgestelde beheerdashboards
Een container management dashboard op een publiek IP zonder authenticatie vereist geen geavanceerde aanvaller. Ze hoeven alleen het adres te kennen. Dat is een lagere drempel dan de meeste teams beseffen.

Zelfgehoste monitoring- en beheertoepassingen worden doorgaans geleverd met een webinterface die toegankelijk is op alle netwerkinterfaces. Die op een publiek IP laten staan zonder authenticatie is het container-equivalent van een adminpaneel openlaten.
Authenticatie, een reverse proxy en plaatsing in een privénetwerk zijn het minimum. Toegangscontrole is een configuratiestap die je toevoegt aan elke beheerinterface, niet iets dat standaard ingeschakeld is.
Hetzelfde geldt voor Docker CLI en GUI-beheer; toegang op adminniveau tot de daemon brengt hetzelfde risico met zich mee, ongeacht de interface.
Niet monitoren wat je containers doen
Als een container gecompromitteerd is, laat de activiteit van de aanvaller sporen na: veranderend procesgedrag, ongebruikelijke netwerkverbindingen en onverwachte bestandswijzigingen. Zonder log-verzameling bestaan die sporen niet in een vorm waar je op kunt handelen.
Gecentraliseerde log-verzameling, container audit logging en runtime-monitoringtools geven je de data om afwijkende activiteit te detecteren voordat het escaleert. Het doel is niet elke regel analyseren. Het is de data beschikbaar hebben wanneer je een onderzoek moet starten.
Container-omgevingen die stilletjes in productie draaien zonder log-pipeline en zonder meldingen zijn niet onderhoudsarm. Ze worden niet geïnspecteerd. Dat zijn twee verschillende operationele situaties.
Waarom de infrastructuuromgeving ook belangrijk is
Containerbeveiliging begint bij configuratie, maar configuratie draait bovenop infrastructuur. Een host met verkeerd geconfigureerd netwerk, gedeelde resources of geen filtering op netwerkniveau creëert omstandigheden die elke container daarboven beïnvloeden. De containerinstellingen goed krijgen en de serverconfiguratie goed krijgen zijn twee afzonderlijke taken.
Veel Docker-beveiligingslekken worden versterkt door omstandigheden die de containers zelf erven:
- Een server met gedeeld gebruik zonder hardware-isolatie tussen tenants
- Een host-kernel die niet gepatcht wordt bijgehouden
- Een host zonder ingebouwde filtering op netwerkniveau
Dit vervangt de bovenstaande configuratiestappen niet, want goede container-hardening is belangrijk ongeacht de infrastructuurlaag. Beginnen op geïsoleerde infrastructuur elimineert één aandachtspunt.
Bij Cloudzy bieden we twee opties, afhankelijk van wat je setup vereist:
- Linux VPS: een schone omgeving om Docker zelf te installeren en de hardening-stappen uit dit artikel toe te passen
- Portainer VPS: een one-click optie met Portainer voorgeïnstalleerd; de server start op en je zit direct in het dashboard
Beide opties draaien op dezelfde infrastructuur: KVM-virtualisatie, AMD Ryzen 9 CPUs met een boostklok tot 5,7 GHz, DDR5-geheugen, NVMe SSD-opslag, netwerk tot 40 Gbps en gratis DDoS-beveiliging via BuyVM-filtering, verspreid over 12 wereldwijde locaties met een uptime-SLA van 99,95%.
Voor een uitgebreide blik op het draaien van Portainer op een VPS behandelen we dat in een apart artikel.
Een praktische beveiligingschecklist voor Docker-deployments
De bovenstaande Docker-beveiligingsfouten komen meestal voort uit enkelvoudige configuratiebeslissingen die eenmalig zijn genomen en nooit opnieuw bekeken. Deze checklist langs een bestaande setup lopen brengt die lacunes aan het licht. Het werkt als een audit, niet als een installatiehandleiding.
Deze Docker beveiligingsrichtlijnen beschrijven hoe je Docker-containers beschermt tegen de meest voorkomende configuratiefouten die hierboven zijn beschreven.
Snelle referentie: alle 9 fouten
| Fout | Categorie | Snelle oplossing |
| Draait als root | Configuratie | Toevoegen USER instructie aan je Dockerfile |
| Poorten gebonden aan 0.0.0.0 | Configuratie | Bind aan 127.0.0.1 en route via een reverse proxy |
| Geen netwerkilsolatie | Configuratie | Verdeel services over afzonderlijke gebruikergedefinieerde netwerken op basis van toegangsbehoeften. |
| Docker socket gekoppeld | Configuratie | Verwijder de koppeling; gebruik scoped APIs of alternatieven |
| Niet-vertrouwde of verouderde images | Afbeelding | Gebruik officiële images met vastgezette versietags |
| Hardcoded geheimen | Afbeelding | Zet credentials over naar runtime-omgevingsvariabelen of een secrets manager |
| Geen schema voor het herbouwen van images | Afbeelding | Stel een maandelijks herbouwschema in; automatiseer waar mogelijk |
| Niet-geverifieerde dashboards | Toegang | Voeg authenticatie toe en verplaats beheerdersinterfaces naar privénetwerken |
| Geen logverzameling voor containers | Toegang | Stel gecentraliseerde logging en runtime-monitoring in |
We raden aan dit eerst op bestaande omgevingen uit te voeren, want daar zijn de kwetsbaarheden het meest waarschijnlijk al aanwezig.
Containers draaien als niet-root: Controleer je Dockerfiles op een USER-instructie. Als die ontbreekt, draait de container als root.
Poortbindingen beperkt tot localhost of via proxy: Voer docker ps uit en bekijk de poortbindingen. Een vermelding als 0.0.0.0:PORT is publiek bereikbaar op hosts waar geen upstream security group, externe firewall of DOCKER-USER-ketenregel het blokkeert.
Aangepaste bridge-netwerken in gebruik: Containers op de standaard bridge van Docker kunnen elkaar vrij bereiken. Containers op dezelfde gebruikergedefinieerde bridge kunnen nog steeds met elkaar communiceren, dus verdeel services over afzonderlijke netwerken op basis van vertrouwensgrenzen voor echte isolatie.
Docker socket niet gekoppeld in containers: Controleer Compose-bestanden en run-argumenten. Als /var/run/docker.sock als volume voorkomt, bevestig dan dat dit vereist en bewust is.
Basisimages van geverifieerde uitgevers met vastgezette versies: Een FROM ubuntu:latest haalt een ongespecificeerde, mogelijk verouderde versie op. Zet vast op een specifieke release.
Geen geheimen in Dockerfiles, Compose-bestanden of build-argumenten: Afbeeldingslaaggeschiedenis bewaart inloggegevens ook na het verwijderen van de container. Gebruik Compose secrets, Swarm secrets, build secret mounts of een externe secrets manager. Runtime-omgevingsvariabelen zijn beter dan hardgecodeerde waarden, maar zijn nog steeds zichtbaar in inspect-uitvoer en logs.
Schema voor het opnieuw bouwen van images vastgelegd: Oude images stapelen kwetsbaarheden op. Een maandelijks rebuild-schema houdt het risico voor de meeste setups beheersbaar.
Beheerinterfaces beveiligd met authenticatie: Elk dashboard op een publiek IP-adres zonder authenticatie is een open toegangspunt. Plaatsing op een privénetwerk verdient de voorkeur waar mogelijk.
Containerlogs worden verzameld: Zonder een log-pipeline is incidentdetectie afhankelijk van zichtbare systeemimpact. Dat is een laat signaal om op te reageren.
Conclusie
De standaardconfiguratie van Docker is gebouwd voor gemak, niet voor beveiliging. De meeste fouten die in dit artikel worden behandeld, zijn terug te herleiden naar instellingen die na de initiële implementatie nooit zijn aangepast, niet naar geavanceerde aanvallen.
De oplossingen zijn grotendeels eenmalige configuratiebeslissingen: een USER-instructie, een wijziging van de poortbinding, een eigen netwerk, een rebuild-schema. Voor de meeste setups is geen nieuwe tooling nodig.
De containerconfiguratie goed instellen is de eerste stap. De infrastructuur waarop die draait, is de tweede. Beide zijn belangrijk en geen van beide vervangt de ander.