← Terug naar tutorials

Falende Docker-builds debuggen: Dockerfile-fouten lezen en oplossen

dockerdockerfiledebuggingbuild errorsci/cdcontainersdevopsbuild logscachingtroubleshooting

Falende Docker-builds debuggen: Dockerfile-fouten lezen en oplossen

Docker-builds falen zelden “zomaar”. Bijna altijd vertelt de foutmelding je precies wat er misgaat—alleen staat het verstopt tussen lagen, caching, multi-stage builds en soms cryptische output van package managers. In deze tutorial leer je systematisch Dockerfile-fouten lezen, reproduceren, isoleren en oplossen. We gaan diep in op de meest voorkomende oorzaken (syntax, context, permissies, netwerken, package managers, multi-stage, BuildKit, caching) en je krijgt concrete commando’s om elke stap te debuggen.

Alles in deze gids werkt met de moderne Docker build-engine (BuildKit). Waar relevant laat ik ook zien hoe je gedrag kunt vergelijken met “legacy” builds.


Inhoud

  1. Basis: hoe Docker een build uitvoert
  2. Eerste hulp: de build opnieuw draaien met maximale info
  3. Foutmeldingen leren lezen: laag, instructie, exit code
  4. Veelvoorkomende Dockerfile-fouten (met fixes)
  5. BuildKit superpowers: progress, secrets, mounts, targets
  6. Debuggen door een “shell in de build” te krijgen
  7. Caching en “het werkt op mijn machine”
  8. Checklist: van fout naar fix
  9. Praktische case studies

Basis: hoe Docker een build uitvoert

Een Docker-build is een reeks lagen (layers). Elke Dockerfile-instructie (FROM, RUN, COPY, etc.) creëert een stap. Docker:

  1. Leest de Dockerfile.
  2. Stuurt de build context (meestal de huidige map) naar de Docker daemon.
  3. Voert instructies uit in volgorde.
  4. Cachet resultaten per stap (tenzij je dat uitschakelt).

Belangrijke gevolgen voor debugging:


Eerste hulp: de build opnieuw draaien met maximale info

Begin altijd met een reproduceerbare build. Gebruik expliciete flags:

docker build --no-cache --progress=plain -t mijnapp:debug .

Als je BuildKit expliciet wilt aanzetten/uitzetten:

# BuildKit aan (meestal default)
DOCKER_BUILDKIT=1 docker build --progress=plain -t mijnapp:debug .

# Legacy builder (soms nuttig om gedrag te vergelijken)
DOCKER_BUILDKIT=0 docker build -t mijnapp:debug .

Bekijk ook welke Dockerfile gebruikt wordt:

docker build -f Dockerfile.dev --progress=plain -t mijnapp:debug .

En bouw tot een specifieke stage (bij multi-stage):

docker build --target builder --progress=plain -t mijnapp:builder .

Foutmeldingen leren lezen: laag, instructie, exit code

Docker toont meestal iets als:

Voorbeeld:

#7 0.231 /bin/sh: 1: yarn: not found
------
Dockerfile:12
--------------------
  10 |     COPY package.json yarn.lock ./
  11 |     RUN corepack enable
  12 | >>> RUN yarn install --frozen-lockfile
  13 |
--------------------
ERROR: failed to solve: process "/bin/sh -c yarn install --frozen-lockfile" did not complete successfully: exit code: 127

Interpretatie:

Je debugt dus niet “Docker”, maar het commando in die laag.

Snelle mapping van exit codes


Veelvoorkomende Dockerfile-fouten met fixes

Syntax- en parsefouten

Symptoom: build faalt meteen met “parse error” of “unknown instruction”.

Voorbeeld:

unknown instruction: RNU

Oorzaak: typefout (RNU i.p.v. RUN).

Andere klassiekers:

Correcte voorbeelden:

RUN apt-get update && apt-get install -y curl

Multi-line met backslashes:

RUN apt-get update && apt-get install -y \
    curl \
    ca-certificates \
 && rm -rf /var/lib/apt/lists/*

JSON-vorm (exec form) voor CMD:

CMD ["node", "server.js"]

Debugtip: laat Docker precies tonen waar de parse faalt:

docker build --progress=plain --no-cache .

COPY/ADD en build context problemen

Symptoom: COPY failed: file not found in build context or excluded by .dockerignore

Voorbeeld:

failed to compute cache key: failed to calculate checksum of ref ... "/app/package.json": not found

Oorzaken:

  1. Je build context is verkeerd (je bouwt vanuit de verkeerde map).
  2. .dockerignore sluit het bestand uit.
  3. Je pad in COPY klopt niet.

Controleer je build context:

Als je docker build . doet, is de context de huidige map. Controleer:

pwd
ls -la

Bekijk .dockerignore:

cat .dockerignore

Als je per ongeluk package*.json negeert, kan COPY package.json ... falen.

Correct gebruik van COPY:

WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .

Let op: COPY . . kopieert alles uit de context (behalve genegeerde files). Grote contexten maken builds traag en onvoorspelbaar.

Pro-tip: inspecteer wat er echt in de context zit door tijdelijk dit te doen:

RUN ls -la

Of specifieker:

RUN find . -maxdepth 2 -type f | sed -n '1,200p'

(Alleen zinvol nadat je de relevante COPY hebt gedaan.)


RUN faalt: exit code 1/127/100

Exit code 127: commando bestaat niet

Voorbeeld:

/bin/sh: 1: python: not found

Oplossingen:

Voor Debian/Ubuntu:

RUN apt-get update && apt-get install -y python3 python3-pip

Voor Alpine:

RUN apk add --no-cache python3 py3-pip

Exit code 100: apt-get problemen

Voorbeeld:

E: Unable to locate package ...

Oorzaken:

Correct patroon:

RUN apt-get update \
 && apt-get install -y --no-install-recommends curl ca-certificates \
 && rm -rf /var/lib/apt/lists/*

Belangrijk: combineer apt-get update en apt-get install in één RUN zodat de package index niet uit cache raakt.

Exit code 1: generiek

Dan moet je de output van het commando zelf lezen. Maak het commando explicieter:

RUN set -eux; \
    node --version; \
    npm --version; \
    npm ci

Permissies en gebruikers (root vs non-root)

Symptoom: permission denied bij RUN, COPY, of tijdens runtime.

Veel images draaien als root tijdens build, maar je kunt USER veranderen. Als je USER node zet en daarna apt-get install probeert, faalt dat.

Voorbeeld fout:

E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)

Fix: doe OS-installaties als root, switch daarna:

FROM node:20-bookworm

WORKDIR /app

# root (default) voor apt
RUN apt-get update && apt-get install -y --no-install-recommends tini \
 && rm -rf /var/lib/apt/lists/*

# pas daarna non-root
USER node

COPY --chown=node:node package.json package-lock.json ./
RUN npm ci

COPY --chown=node:node . .
CMD ["node", "server.js"]

Debugtip: print de huidige user en permissies:

RUN id && whoami && ls -la

Netwerk/DNS/Proxy issues

Symptoom: timeouts bij apt, npm, pip, curl, git clone.

Voorbeelden:

Check of het een algemene netwerkfout is:

Voeg tijdelijk toe:

RUN set -eux; \
    cat /etc/resolv.conf; \
    ping -c 1 8.8.8.8 || true; \
    ping -c 1 deb.debian.org || true; \
    curl -I https://deb.debian.org || true

ping is niet altijd aanwezig; je kunt iputils-ping installeren of alleen curl gebruiken.

Proxy instellen tijdens build:

docker build \
  --build-arg http_proxy=http://proxy:3128 \
  --build-arg https_proxy=http://proxy:3128 \
  --build-arg no_proxy=localhost,127.0.0.1 \
  --progress=plain \
  -t mijnapp:debug .

En in Dockerfile:

ARG http_proxy
ARG https_proxy
ARG no_proxy

Let op: zet secrets (tokens) niet als build-arg als je image publiek kan worden; gebruik BuildKit secrets (zie verderop).


Multi-stage build fouten

Multi-stage builds zijn geweldig, maar fouten worden soms verwarrend door COPY --from=....

Voorbeeld:

FROM node:20 AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html

Symptoom: COPY failed: stat /app/dist: file does not exist

Oorzaken:

Debug aanpak:

  1. Bouw alleen de builder stage:
docker build --target builder --progress=plain -t mijnapp:builder .
  1. Start een container om te inspecteren:
docker run --rm -it mijnapp:builder sh
# of bash, afhankelijk van image
ls -la /app
ls -la /app/dist
  1. Voeg tijdelijke checks toe:
RUN set -eux; npm run build; ls -la /app; ls -la /app/dist

Platform/architectuur issues (amd64 vs arm64)

Symptoom: “exec format error” of binaries die niet draaien.

Voorbeeld:

standard_init_linux.go:228: exec user process caused: exec format error

Oorzaken:

Check je platform:

docker version
docker info | grep -i architecture

Forceer platform bij build:

docker build --platform=linux/amd64 -t mijnapp:amd64 .

En bij run:

docker run --rm --platform=linux/amd64 mijnapp:amd64

Debugtip: in de container:

uname -m

Line endings en shell-gedrag

Symptoom: scripts werken lokaal, falen in container met ^M of “not found”.

Voorbeeld:

/bin/sh^M: bad interpreter: No such file or directory

Oorzaak: Windows CRLF line endings.

Fix:

Converteer met dos2unix:

RUN apt-get update && apt-get install -y dos2unix
COPY entrypoint.sh /entrypoint.sh
RUN dos2unix /entrypoint.sh && chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

Shell nuance: RUN gebruikt standaard /bin/sh -c. Bash-specifieke syntax faalt dan.

Als je bash nodig hebt:

RUN apt-get update && apt-get install -y bash
SHELL ["/bin/bash", "-lc"]
RUN [[ -f "somefile" ]] && echo "bestaat"

BuildKit superpowers: progress, secrets, mounts, targets

Volledige logs

Gebruik:

docker build --progress=plain -t mijnapp:debug .

Targeted builds

docker build --target test --progress=plain -t mijnapp:test .

Secrets (tokens) veilig gebruiken

Stel je moet een private npm registry token gebruiken. Niet doen:

ARG NPM_TOKEN
RUN npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN

Dit kan in image history belanden.

Met BuildKit secrets:

docker build \
  --secret id=npm_token,src=$HOME/.npm-token \
  --progress=plain \
  -t mijnapp:debug .

Dockerfile:

# syntax=docker/dockerfile:1.7
RUN --mount=type=secret,id=npm_token \
    set -eu; \
    TOKEN="$(cat /run/secrets/npm_token)"; \
    npm config set //registry.npmjs.org/:_authToken="$TOKEN"; \
    npm ci

Cache mounts voor package managers

Dit versnelt builds en maakt debug herhaalbaar:

# syntax=docker/dockerfile:1.7
RUN --mount=type=cache,target=/root/.npm \
    npm ci

Debuggen door een “shell in de build” te krijgen

Je kunt niet letterlijk “in” een mislukte laag springen, maar je kunt dichtbij komen met deze technieken.

1) Bouw tot een stage en run een shell

Als je multi-stage gebruikt, bouw de stage die faalt:

docker build --target builder --progress=plain -t mijnapp:builder .
docker run --rm -it mijnapp:builder sh

Van daaruit kun je commando’s handmatig uitvoeren:

cd /app
npm run build
ls -la

2) Maak een tijdelijke debug stage

Voeg onderaan je Dockerfile toe:

FROM builder AS debug
CMD ["sh"]

Bouw en start:

docker build --target debug -t mijnapp:debugshell .
docker run --rm -it mijnapp:debugshell

3) Splits een complexe RUN op (tijdelijk)

In plaats van:

RUN do_a && do_b && do_c

Tijdelijk:

RUN do_a
RUN do_b
RUN do_c

Zo zie je exact welke stap faalt. Daarna kun je weer combineren voor efficiënte layers.

4) Zet set -eux aan

Heel effectief bij shell scripts:

RUN set -eux; \
    echo "Stap 1"; \
    do_a; \
    echo "Stap 2"; \
    do_b

Caching en “het werkt op mijn machine”

Caching kan een build sneller maken, maar ook verwarrend:

Cache bewust uitschakelen

docker build --no-cache --progress=plain -t mijnapp:debug .

Cache bewust “busten” (zonder alles uit te zetten)

Gebruik een build-arg:

ARG CACHEBUST=1
RUN echo "cachebust=$CACHEBUST" && apt-get update

Build:

docker build --build-arg CACHEBUST=$(date +%s) -t mijnapp:debug .

Let op volgorde van COPY voor betere cache

Goed patroon (Node.js voorbeeld):

COPY package.json package-lock.json ./
RUN npm ci
COPY . .

Als je eerst COPY . . doet, dan breekt elke codewijziging de cache van npm ci.


Checklist: van fout naar fix

  1. Herhaalbaar maken
    • docker build --no-cache --progress=plain ...
  2. Vind de exacte Dockerfile-regel
    • Lees de >>> regel in de output.
  3. Classificeer het type fout
    • Parse? COPY? RUN? Netwerk? Permissie?
  4. Reproduceer in isolatie
    • --target stage bouwen
    • Tijdelijk RUN ls -la, id, cat /etc/os-release
  5. Maak commando’s expliciet
    • set -eux
    • Print versies: node -v, python3 --version, gcc --version
  6. Controleer context en .dockerignore
  7. Controleer platform
    • --platform=..., uname -m
  8. Verwijder tijdelijke debug code
    • Combineer RUN’s weer waar zinvol
    • Verwijder tokens/logging

Praktische case studies

Case 1: COPY failed: file not found door verkeerde context

Situatie: je projectstructuur:

repo/
  docker/
    Dockerfile
  app/
    package.json
    src/

Je draait:

docker build -f docker/Dockerfile -t mijnapp .

Maar je Dockerfile doet:

COPY package.json ./

Dat faalt, want package.json zit in app/.

Fix 1 (paden aanpassen):

COPY app/package.json ./

Fix 2 (context veranderen):

docker build -f ../docker/Dockerfile -t mijnapp ../app

Dan is de context app/ en werkt COPY package.json ./.


Case 2: apt-get install faalt sporadisch in CI

Symptoom: timeouts of “Hash Sum mismatch”.

Aanpak:

  1. Log DNS en repo:
RUN set -eux; cat /etc/resolv.conf; cat /etc/apt/sources.list
  1. Gebruik retries en noninteractive:
ENV DEBIAN_FRONTEND=noninteractive
RUN set -eux; \
    apt-get update; \
    apt-get install -y --no-install-recommends ca-certificates curl; \
    rm -rf /var/lib/apt/lists/*
  1. Als CI een proxy nodig heeft: build-args voor proxy meegeven (zie eerder).

Case 3: yarn: not found in Node 20 images

In recente Node images is Yarn niet altijd standaard beschikbaar zoals je verwacht. Oplossing: Corepack gebruiken of Yarn installeren.

Fix met Corepack:

FROM node:20-bookworm
WORKDIR /app

COPY package.json yarn.lock ./
RUN corepack enable
RUN yarn --version
RUN yarn install --frozen-lockfile

Als corepack enable faalt, controleer of het in die Node variant aanwezig is en print:

RUN node --version && corepack --version || true

Case 4: “works locally” maar faalt in container door bash-syntax

Je hebt:

RUN if [[ -f .env ]]; then echo ok; fi

[[ ... ]] is bash, maar /bin/sh (dash) snapt het niet.

Fix:

RUN if [ -f .env ]; then echo ok; fi

Of zet bash als shell:

RUN apt-get update && apt-get install -y bash
SHELL ["/bin/bash", "-lc"]
RUN if [[ -f .env ]]; then echo ok; fi

Extra debugcommando’s die bijna altijd helpen

Voeg tijdelijk toe rond de falende stap:

RUN set -eux; \
    cat /etc/os-release; \
    uname -a; \
    env | sort | sed -n '1,120p'

Bestanden inspecteren:

RUN set -eux; \
    pwd; \
    ls -la; \
    find . -maxdepth 2 -type f | sed -n '1,200p'

Disk space check (bij grote builds):

RUN df -h

Afronding

Falende Docker-builds debuggen is vooral: lees de foutmelding als een aanwijzing naar één specifieke laag, maak de build reproduceerbaar met --no-cache en --progress=plain, en reduceer complexiteit door te isoleren (targets/stages) en commando’s expliciet te maken (set -eux). Zodra je dit proces beheerst, worden zelfs “cryptische” errors meestal binnen minuten verklaarbaar—en oplosbaar.

Als je een concrete foutmelding + Dockerfile snippet plakt (inclusief de stap met >>>), kan ik de diagnose stap voor stap met je doorlopen en een minimale fix voorstellen.