dev@laptop:~/sessie$ cat docker-101.md

Docker
van image → registry

Eén uur. Mentaal model + handen op de bak.
Doel: snappen waarom een image draait, niet alleen dat je 'm kunt pushen.
De casus: lokaal bouwen en pushen lukt — totdat de CI/CD het overneemt en niets meer bouwt.
— gebruik / spatie om verder te gaan, F voor fullscreen.
dev@laptop:~/sessie$ why --are-we-here

De casus

Een team bouwt en pusht lokaal probleemloos images. Tóch loopt het vast zodra de CI/CD het overneemt: de pipeline weigert te bouwen.

"Een image kunnen pushen" is niet hetzelfde als "snappen hoe een container runtime werkt". Ad-hoc lokale flows werken prima — totdat je reproduceerbaarheid nodig hebt.

En dat moment is precies dit: CI / CD. De gap dook niet toevallig op tijdens de migratie — dáár breekt een wankel mentaal model. Dat repareren we hier.

dev@laptop:~/model$ whatis container

Een container is geen VM

Een VM draait een eigen kernel bovenop een hypervisor. Zwaar, traag te starten, volledige OS.

Een container deelt de host-kernel en draait gewoon native processen, geïsoleerd met namespaces + cgroups.

Geen bytecode. Geen interpreter ertussen. Het proces in de container draait rechtstreeks op de kernel van de host.

Gevolg: een image is gebonden aan een CPU-architectuur. amd64 ≠ arm64. Onthoud dat — slide 5.

dev@laptop:~/model$ whatis image vs container

Image ≠ container

IMAGE = recept

  • Stapel read-only filesystem-layers
  • + config-metadata: ENTRYPOINT/CMD, env, poorten, workdir
  • Bevroren. Wordt nooit aangepast.

CONTAINER = gerecht

  • Een draaiende instance van een image
  • + een dunne writable layer bovenop
  • Wijzigingen leven daar — en zijn weg bij rm.
Image = "hoe ziet het eruit + hoe start ik het". Container = "het draait nu, met een kladlaag erbovenop".
dev@laptop:~/model$ debunk bytecode-mythe

De twee misverstanden

exit 1

"Image = bytecode"

  • Er is geen VM die het interpreteert.
  • Een image is een filesystem + startinstructie, geen draaibare bytecode.
exit 1

"Een image draait overal"

  • Een image is architectuur-gebonden.
  • amd64-image op een arm64-host = crash of trage emulatie.
  • Oplossing: multi-arch manifest (één tag, meerdere platforms).

Dit is waarom een build die lokaal werkt soms in CI op een andere node faalt.

dev@laptop:~/model$ explain layers

Layers stapelen

Een union filesystem (overlayfs) presenteert losse read-only layers als één geheel.

Elke Dockerfile-instructie ≈ een nieuwe layer. Layers worden gecachet en hergebruikt.

Daarom: kopieer eerst je dependency-manifest en installeer, dan pas je broncode. Wijzig je code, dan blijft de install-layer uit cache.
writable layer (container)
COPY . . — broncode
RUN install deps
COPY manifest
FROM base-image

Onder → boven. Alleen de bovenste laag is schrijfbaar, en hoort bij de container — niet bij de image.

dev@laptop:~/basics$ docker --help | head

De lifecycle in commands

docker pull nginx:1.27 # image ophalen docker run -d --name web nginx:1.27 # container starten uit image docker ps # wat draait er docker exec -it web sh # shell ín de draaiende container docker logs web # stdout/stderr docker stop web && docker rm web # stoppen + opruimen

run = create + start. exec opent een proces in een container die al draait — niet hetzelfde als run.

dev@laptop:~/basics$ docker commit # demo — let op
demo · 1× laten zien, daarna nooit meer

Container → image, met de hand

docker run -it --name kladje ubuntu:24.04 bash root@a1b2c3:/# apt-get update && apt-get install -y cowsay root@a1b2c3:/# exit docker commit kladje mijn-cowsay:eigenbouw sha256:9f8e... # de writable layer is nu bevroren tot een image

Dit werkt. Je voelt nu wat die writable layer is: je hebt 'm bevroren tot een nieuwe image-layer. Onthoud dit gevoel — en op de volgende slide leg ik uit waarom je dit nooit in CI moet doen.

dev@laptop:~/basics$ docker commit; echo $?

Waarom commit faalt in CI

exit 1

Wat er mis is

  • Niet reproduceerbaar: niemand weet wat je intypte.
  • Niet auditeerbaar: geen broncode, geen diff, geen review.
  • Niet in de repo: CI heeft niets om te bouwen.
Dít is precies waar de pipeline op breekt. Bestaat een image alleen omdat iemand 'm lokaal commit-te, dan vindt de CI geen Dockerfile — of bouwt iets anders dan er lokaal draaide.

"Boring & auditable" begint hier: een build die je in een ISO 27001-audit kunt uitleggen, niet "Henk heeft 'm ooit op z'n laptop gemaakt".

dev@laptop:~/build$ cat Dockerfile

Dockerfile = de juiste manier

FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . USER 1000 CMD ["python", "app.py"]
Declaratief · in versiebeheer · reproduceerbaar. Iedereen bouwt dezelfde image uit dezelfde regels.

En dit is exact wat CI bouwt — of het nu GitHub Actions of Forgejo Actions is. Geen Dockerfile in de repo = geen werkende pipeline.

dev@laptop:~/build$ build && tag && push

Bouwen → taggen → pushen → pullen

docker build -t mijn-app:0.1.0 . # bouw uit Dockerfile docker tag mijn-app:0.1.0 \ docker.io/gongoeloe/mijn-app:0.1.0 # registry-pad + versie docker login docker push docker.io/gongoeloe/mijn-app:0.1.0 docker pull docker.io/gongoeloe/mijn-app:0.1.0 # elders, of in CI

Een tag is registry / repository : versie. Geen versie opgeven → Docker vult :latest in. Daar gaan we het op de volgende slide over hebben.

dev@laptop:~/best-practices$ lint Dockerfile

Do's & don'ts

exit 0
  • Pin versies: :1.27 of digest, nooit :latest in prod.
  • Multi-stage build: bouw-tools in stage 1, alleen de binary in de runtime-image.
  • Non-root: USER 1000.
  • .dockerignore: geen .git, secrets of node_modules in de build-context.
exit 1
  • :latest in prod — je weet nooit wat draait.
  • Secrets in een layer. Een layer is voor altijd: ook al "verwijder" je 'm later, hij zit nog in de history. (→ SOPS/age, geen ENV PASSWORD=.)
  • Dikke base-image met half Ubuntu erin als je een statische binary hebt.
dev@laptop:~/tips$ docker volume ls

Tips & tricks

Named volumes ≠ bind mounts

  • Named volume: door Docker beheerd, voor data (db). Overleeft de container.
  • Bind mount: host-pad, voor je broncode tijdens dev.

compose & data

docker compose down # containers + netwerk weg # named volumes BLIJVEN docker compose down -v # -v gooit óók de volumes weg

Data is dus losgekoppeld van de container-lifecycle. Een down wist je database niet — pas -v doet dat.

dev@laptop:~/lab$ ./hands-on.sh # 30 min
live · samen doen

Aan de slag

Maak een account op hub.docker.com (of gebruik een zelf-gehoste registry).

Schrijf een mini Dockerfile (slide 10 als basis). Hou 'm klein.

docker build → tag → login → push → pull op een andere machine.

Bonus: doe de commit-flow van slide 8 één keer — en voel waarom je 'm daarna nooit meer wilt.

Loop je vast: error eerst lezen, dan vragen. docker logs en docker inspect zijn je vrienden.

dev@laptop:~/sessie$ recap && exit 0

De kern

Boring & auditable: een build die je in een audit kunt uitleggen. Niet "Henk's laptop".
Vragen?
docker-101 · dev-sessie nav · F fullscreen 01 / 15