Ga naar hoofdinhoud

Images bouwen en beheren

Structuur van een Dockerfile en best practices

Wat is een Dockerfile

Zoals eerder gezegd is een Dockerfile een tekstbestand (zonder extensie) met instructies voor het opbouwen van een Docker-image. Elke instructie in het tekstbestand kan een nieuwe laag creëeren in de image. Dit gebeurt meestal alleen voor uitvoerende instructies zoals RUN, COPY, ADD. Zie ook de officiële Dockerfile reference.

Een lijst van de belangrijkste Dockerfile-instructies:

  • FROM: Startpunt van elke Dockerfile; specificeert de basisimage waarop je image gebaseerd is, bijvoorbeeld FROM ubuntu:latest.
  • RUN: Voert een commando uit in de container om bijvoorbeeld software te installeren of configureren. Elke RUN-instructie voegt een laag toe aan de image.
  • COPY / ADD: Kopieert bestanden van de host naar de container. COPY wordt aanbevolen boven ADD (tenzij je een bestand wilt downloaden).
  • CMD: Definieert het standaardcommando dat wordt uitgevoerd wanneer de container start.
  • ENTRYPOINT: Stelt het hoofdproces van de container in, vergelijkbaar met CMD, maar iets minder flexibel. CMD kan worden gebruikt als argument voor ENTRYPOINT.
  • EXPOSE: Geeft aan welke poort door de container moet worden geopend (niet verplicht voor communicatie, maar als documentatie handig).
  • ENV: Stelt omgevingsvariabelen in die binnen de container beschikbaar zijn.
  • WORKDIR: Stelt de werkdirectory in voor daaropvolgende instructies.

Best practices

  • Gebruik van een lichte basisimage:
    • Start met een zo licht mogelijke basisimage (zoals alpine of slim-versies), om de uiteindelijke image kleiner te maken. Dit vermindert laadtijd en opslaggebruik.
  • Vermijd onnodige lagen:
    • Combineer RUN-instructies om het aantal lagen te verminderen. Gebruik && om meerdere commando's in één RUN uit te voeren:
       RUN apt-get update && apt-get install -y \
    python3 \
    python3-pip && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
    • Verwijder tijdelijke bestanden binnen dezelfde RUN-instructie (zoals cache of logbestanden) om de image schoon te houden. (laatste lijn hierboven)
  • Gebruik COPY boven ADD:
    • COPY is explicieter en biedt meer controle over welke bestanden worden gekopieerd. Gebruik ADD alleen als je bijvoorbeeld een bestand wilt downloaden of een archief wilt uitpakken.
  • Set WORKDIR Correct:
    • Gebruik WORKDIR in plaats van directory’s aan te maken en cd te gebruiken. Dit maakt het script schoner en overzichtelijker.
      WORKDIR /app
      COPY . /app
  • Gebruik ENV voor Configuratie:
    • ENV maakt je Dockerfile flexibeler en stelt je in staat om omgevingsvariabelen later eenvoudig te wijzigen. Hiermee kun je afhankelijkheden configureren zonder de Dockerfile zelf te moeten aanpassen.
  • Gebruik .dockerignore:
    • Voeg een .dockerignore-bestand toe (vergelijkbaar met .gitignore) om onnodige bestanden en mappen uit te sluiten, zoals node_modules, .git, of tmp. Dit houdt de image kleiner en bouwt sneller.
  • Bepaal de CMD en ENTRYPOINT Correct:
    • Gebruik CMD voor default waarden die overschreven kunnen worden, en ENTRYPOINT voor het hoofdproces.
    • Combineer ENTRYPOINT met CMD om een dynamische configuratie te bereiken:
      ENTRYPOINT ["python3", "app.py"]
      CMD ["--help"]
  • Minimaliseer het aantal packages:
    • Installeer alleen noodzakelijke dependencies om veiligheidsrisico’s en image-grootte te minimaliseren. Overweeg tools zoals multi-stage builds voor efficiëntere en schonere builds.

Voorbeeld Dockerfile Een Dockerfile met best practices voor een eenvoudige Python-applicatie:

# Kies een lichte basisimage
FROM python:3.9-slim

# Stel werkdirectory in
WORKDIR /app

# Kopieer alleen noodzakelijke bestanden
COPY requirements.txt requirements.txt

# Installeer afhankelijkheden
RUN pip install --no-cache-dir -r requirements.txt

# Kopieer de rest van de applicatie
COPY . .

# Exposeer een poort
EXPOSE 5000

# Start de applicatie
ENTRYPOINT ["python", "app.py"]

Dit voorbeeld demonstreert best practices zoals het gebruik van een lichte basisimage, minimaliseren van lagen en gebruik van WORKDIR en EXPOSE.

Overige Tips

  • Gebruik Multi-Stage Builds: Hiermee kun je in de eerste fase je app bouwen (bijvoorbeeld met alle build tools), en in de tweede fase alleen de essentiële bestanden kopiëren naar een kleinere image.
  • Beveiliging: Vermijd hard-coded secrets of wachtwoorden in Dockerfiles. Gebruik bijvoorbeeld Docker Secrets voor gevoelige gegevens. Andere optie voor lokaal gebruik is een .env file.
  • Cachebeheer: Vermijd frequent wijzigen van bovenste lagen, zoals RUN apt-get update, om het gebruik van de Docker-cache te optimaliseren. Zie ook volgende sectie.

Caching van lagen

Hoe werkt caching?

Docker maakt gebruik van een laag-gebaseerd systeem, waarbij (bijna) elke instructie (RUN, COPY, enz.) een laag toevoegt aan de image. Bij elke nieuwe build probeert Docker eerdere lagen te hergebruiken om de build sneller te maken. Als Docker detecteert dat een laag eerder is gebouwd en niet is veranderd, dan gebruikt het de cached versie.

De cache wordt ongeldig als Docker een wijziging detecteert in een laag of in de bovenliggende lagen. Als één laag verandert, worden alle onderliggende lagen opnieuw gebouwd, wat extra tijd kan kosten.

Optimaliseer volgorde van instructies

Probeer instructies die zelden veranderen (zoals het installeren van systeempackages) bovenaan te zetten. Door eerst de dependencies te installeren voordat je de applicatiecode kopieert, worden eerdere lagen vaker gecached, wat build-tijd kan besparen.

FROM python:3.9-slim

# Installeer systeemafhankelijkheden, die minder vaak veranderen
RUN apt-get update && apt-get install -y build-essential

# Kopieer en installeer afhankelijkheden
COPY requirements.txt .
RUN pip install -r requirements.txt

# Kopieer de app-code
COPY . .

Beheren van images - tags, versies, opkuisen

Tags

Tags zijn labels die je aan een Docker-image kunt geven om versies of varianten van een image te identificeren. Ze worden gebruikt om een specifieke versie van een image op te halen, bijvoorbeeld voor productie of ontwikkeling.

# Bouwen en taggen van een image
docker build -t myapp:1.0 .

# Taggen met een andere versie
docker tag myapp:1.0 myapp:latest

Uitleg

  • myapp:1.0: Dit is een expliciete versie van de image.
  • myapp:latest: Dit is de meest recente versie van de image. Het gebruik van latest is handig voor het aanduiden van de nieuwste stabiele versie van een image. Meestal wordt een latest versie gebruikt voor productie omgevingen.

Versies

In een professionele DevOps pipeline is het belangrijk om elke build van je Docker-image een unieke versie te geven, zodat je kunt bijhouden welke versie van je applicatie in welke omgeving draait.

Er zijn verschillende opties om versiebeheer van Docker images te doen:

  • Op basis van een commit hash
    COMMIT_HASH=$(git rev-parse --short HEAD)
    docker build -t myapp:$COMMIT_HASH .
  • Met semantic versioning (MAJOR.MINOR.PATCH) waarbij:
    • MAJOR: Veranderingen die backwards incompatibel zijn (bijvoorbeeld nieuwe features die oude functionaliteit breken).
    • MINOR: Nieuwe features die backwards compatibel zijn.
    • PATCH: Bugfixes of verbeteringen die backwards compatibel zijn.
      docker build -t myapp:2.1.0 .

Opkuisen oude images

Docker-images kunnen snel veel schijfruimte in beslag nemen, vooral als je vaak nieuwe builds maakt. Het is cruciaal om oude, ongebruikte images op te ruimen om je omgeving schoon te houden en opslag te besparen.

Waarom is opschonen belangrijk:

  • Schijfruimte: Docker-images kunnen groot worden, vooral als je veel verschillende versies hebt. Het is belangrijk om oude images die niet meer nodig zijn te verwijderen.
  • Optimale prestatie van Docker omgeving: Overtollige lagen, volumes, en images kunnen de prestaties van je systeem beïnvloeden.

Verwijderen van ongebruikte images: Ongebruikte images worden ook wel eens "dangling" of "zwevende" images genoemd. Dit zijn images zonder tag die niet meer in gebruik zijn omdat er nieuwere versies beschikbaar zijn bijvoorbeeld.

docker image prune

Verwijderen van alle niet gebruikte images: Je kunt ook alle images verwijderen die niet in gebruik zijn door containers.

docker image prune -a

Verwijderen van specifieke images: Als je een specifieke versie of oude image wilt verwijderen.

docker image remove <image_id>
# OF
docker rmi <image_id>

Hands-on!

Doel:

  • Een Dockerfile maken / leren lezen
  • Een image builden van die file
  • Een container runnen met die image
  • In de container zien dat de omgeving anders is dan de host
  • Eventueel al eens pushen en pullen (EXTRA als docker registry ook behandeld)

Zie simple-node-server voorbeeld.