dockerfile: Docker
Een Dockerfile is een tekstbestand dat instructies bevat voor het bouwen van een Docker-image. Het is de basis voor het automatiseren van het creatieproces van containerafbeeldingen en maakt deel uit van de centrale werkwijze in Docker-gebaseerde workflows. Met een Dockerfile kan een ontwikkelaar of systeembeheerder precies definiëren welke software, bestanden, configuraties en instellingen in een container terechtkomen, zodat de resulterende image reproduceerbaar, draagbaar en consistent is, ongeacht de omgeving waarin deze wordt uitgevoerd.
Dockerfiles gebruiken een specifieke syntax met een reeks opdrachten, elk beginnend met een instructie zoals `FROM`, `RUN`, `COPY`, `CMD` of `EXPOSE`. Deze instructies worden sequentieel uitgevoerd tijdens het bouwproces, en elke instructie creëert een nieuwe laag in de uiteindelijke image. Deze laagstructuur is een fundamenteel kenmerk van Docker-images en zorgt voor efficiëntie in opslag en distributie. Omdat elke laag alleen wordt herbouwd als de bijbehorende instructie verandert, kan Docker bestaande lagen uit de cache hergebruiken, wat het bouwen van images aanzienlijk versnelt.
De eerste instructie in bijna elke Dockerfile is `FROM`. Deze bepaalt de basisimage waarmee het bouwproces begint. Dit kan een bestaande image zijn zoals `ubuntu`, `alpine`, `node` of `python`, afkomstig van een register zoals Docker Hub. De keuze van de basisimage heeft grote invloed op de grootte, veiligheid en functionaliteit van de uiteindelijke container. Bijvoorbeeld: een `FROM alpine` levert een zeer kleine image op, ideaal voor productieomgevingen waar schijfruimte en beveiliging belangrijk zijn, terwijl `FROM ubuntu` meer tools en bibliotheken standaard biedt, handig voor ontwikkelomgevingen.
Na de `FROM`-instructie volgen meestal opdrachten zoals `RUN`, `COPY` of `ADD`. De `RUN`-instructie voert commando’s uit in de containerlaag tijdens het bouwproces, zoals het installeren van pakketten met een pakketbeheerder (`apt-get install`, `yum install`, `apk add`). De `COPY`-instructie kopieert bestanden en mappen van de lokale machine naar de containerimage. `ADD` doet hetzelfde, maar heeft extra functionaliteit zoals het automatisch uitpakken van gecomprimeerde bestanden of het ophalen van bestanden via een URL. Het wordt echter aanbevolen om `COPY` te gebruiken als dat volstaat, omdat het gedrag duidelijker en voorspelbaarder is.
Een andere belangrijke instructie is `CMD`. Deze definieert het standaardcommando dat wordt uitgevoerd wanneer een container van de image wordt gestart. Het kan een applicatie starten, zoals een Node.js-server of een Python-script. `CMD` kan worden overschreven wanneer de container wordt gestart met `docker run`, wat flexibiliteit biedt. Daarnaast bestaat er de `ENTRYPOINT`-instructie, die gebruikt kan worden om een vaste uitvoerbaar bestand in te stellen, waarna `CMD` als standaardargument fungeert.
Andere instructies zijn `EXPOSE`, die aangeeft op welke poort de container luistert, en `ENV`, die omgevingsvariabelen definieert die beschikbaar zijn tijdens het bouwen en uitvoeren van de container. `WORKDIR` stelt de huidige werkmap in voor de daaropvolgende instructies, wat helpt bij het organiseren van bestanden en het uitvoeren van commando’s in de juiste context. `LABEL` wordt gebruikt om metadata toe te voegen aan de image, zoals versie, auteur of beschrijving.
Het bouwen van een image uit een Dockerfile gebeurt met het commando `docker build`. Dit commando neemt een bouwcontext — meestal de huidige map — en stuurt die naar de Docker-daemon. De daemon leest de Dockerfile en voert de instructies een voor een uit, waarbij elke stap een nieuwe laag creëert. Tijdens dit proces worden tussentijdse containers aangemaakt, commando’s uitgevoerd, en bestanden gekopieerd. Aan het einde van het proces wordt een nieuwe image gegenereerd, die kan worden gelabeld met een naam en tag via de `-t`-optie, bijvoorbeeld `docker build -t mijn-app:1.0 .`.
De laagstructuur van Docker-images is essentieel voor begrip van hoe Docker werkt. Elke laag is onveranderlijk (immutable), wat betekent dat ze niet kunnen worden gewijzigd nadat ze zijn gemaakt. Wanneer een nieuwe laag wordt toegevoegd, zoals bij het installeren van software of het kopiëren van bestanden, wordt een nieuwe laag gemaakt bovenop de vorige. Dit zorgt voor efficiëntie: als een stap niet verandert, kan Docker de bestaande gecachte laag hergebruiken. Dit is een belangrijke reden waarom Docker-images snel kunnen worden herbouwd.
Er zijn echter ook uitdagingen met Dockerfiles. Slechte praktijken kunnen leiden tot onnodig grote images, beveiligingsrisico’s of onherproduceerbare builds. Bijvoorbeeld: het installeren van tijdelijke pakketten zonder ze direct na gebruik te verwijderen (zoals `build-essential` of `wget`) verhoogt de imagegrootte en verhoogt het aanvalsoppervlak. Een oplossing is het combineren van `RUN`-stappen waarbij installatie en opruiming in één commando gebeuren, zodat tijdelijke bestanden niet in een aparte laag blijven hangen.
Ook het beheren van gevoelige gegevens is een punt van aandacht. Geheimen zoals API-keys of wachtwoorden mogen nooit rechtstreeks in een Dockerfile worden geschreven, omdat ze dan in de imagegeschiedenis terechtkomen en zichtbaar zijn via `docker history`. In plaats daarvan moeten geheimen worden doorgegeven via build-arguments (`--build-arg`), of beter nog: via veilige methoden zoals Docker Secrets of een externe secrets-manager.
Er bestaan alternatieven voor het bouwen van images zonder Dockerfile. Zo kan `docker import` worden gebruikt om een image te maken uit een tarball, of `docker commit` om een image te maken op basis van de huidige staat van een actieve container. Deze methoden zijn echter minder transparant en moeilijker te reproduceren dan het gebruik van een Dockerfile. `docker commit` is handig voor experimenten of noodoplossingen, maar wordt afgeraden voor productiegebruik omdat het geen duidelijk overzicht biedt van wat er in de image zit.
De rol van Dockerfiles in moderne ontwikkeling is onmiskenbaar. Ze vormen de brug tussen ontwikkeling en operaties (DevOps), en maken continu integratie (CI) en continu leveren (CD) mogelijk. Veel CI-systemen, zoals GitHub Actions of Bitbucket Pipelines, bouwen automatisch images op basis van Dockerfiles wanneer code wordt gepusht naar een repository. Dit sluit aan bij de afschaffing van Docker Hub Automated Builds, die op 1 april 2027 wordt stopgezet. Gebruikers worden aangeraden over te stappen op dergelijke CI-workflows, waarin Dockerfiles een centrale rol spelen.
Bij het schrijven van een Dockerfile zijn er richtlijnen om te volgen voor optimale resultaten. Gebruik bijvoorbeeld `.dockerignore` om onnodige bestanden uit de bouwcontext te houden, wat de build snelheid verhoogt. Minimaliseer het aantal `RUN`-instructies door commando’s te verbinden met `&&`, en verwijder tijdelijke bestanden in dezelfde laag. Gebruik specifieke tags in plaats van `latest` voor basisimages, zodat builds reproduceerbaar blijven. En overweeg het gebruik van multi-stage builds, waarbij meerdere `FROM`-instructies in één Dockerfile worden gebruikt om bijvoorbeeld een image te bouwen met compilatie-tools en een tweede, veel kleinere image te maken met alleen de gecompileerde applicatie.
Ook taalspecifieke overwegingen spelen een rol. Voor Node.js-applicaties wordt bijvoorbeeld vaak een patroon gevolgd waarbij eerst alleen `package.json` en `package-lock.json` worden gekopieerd, daarna `npm install` wordt uitgevoerd, en pas daarna de broncode. Dit zorgt ervoor dat de afhankelijkheden alleen opnieuw worden geïnstalleerd als de package-bestanden veranderen, niet bij elke kleine codeaanpassing.
Samenvattend is een Dockerfile een krachtig en essentieel instrument in de wereld van containers. Het stelt gebruikers in staat om consistente, automatisch gebouwde images te maken die draaien op elk systeem met Docker. Door de juiste structuur, veiligheidspraktijken en efficiëntieprincipes te volgen, kunnen Dockerfiles een robuuste basis vormen voor moderne applicatie-ontwikkeling en -deployments. Ze zijn niet alleen technisch, maar ook cultureel van belang: ze bevorderen herbruikbaarheid, transparantie en samenwerking in softwareprojecten.