Dockerfile best practices: rychlé buildy, bezpečné a čitelné image
Jak psát Dockerfile správně – pořadí vrstev a cache, .dockerignore, ne-root uživatel, CMD vs ENTRYPOINT, HEALTHCHECK, připnuté verze a kontrola pomocí hadolint.
Dockerfile, který „nějak funguje“, napíše každý. Dockerfile, který se sestaví za pět sekund místo pěti minut, neběží jako root a neobsahuje vaše .env, vyžaduje pár návyků. Tady jsou ty nejdůležitější, s vysvětlením proč.
1. Pořadí instrukcí kvůli cache
Docker ukládá každou instrukci jako vrstvu a při dalším buildu ji použije znovu, pokud se nezměnila ona ani nic před ní. Proto dávejte věci, které se mění zřídka, nahoru.
Špatně – každá změna kódu znovu instaluje všechny závislosti:
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
Správně – závislosti se reinstalují jen při změně requirements.txt:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
Pro Node.js stejný princip: nejdřív package.json a package-lock.json, npm ci, pak zbytek kódu.
2. .dockerignore
Bez něj Docker posílá do buildu celou složku včetně node_modules, .git a – co je horší – .env. Vytvořte .dockerignore:
.git
.env
.env.*
node_modules
__pycache__
*.pyc
.venv
dist
Dockerfile
docker-compose*.yml
Build bude rychlejší a do image se nedostanou tajné údaje.
3. Připnuté verze base image
FROM node:20-bookworm-slim # ano
FROM node:latest # ne
latest se mění bez varování a build, který včera fungoval, dnes spadne. Pro maximální reprodukovatelnost lze připnout i digest: FROM python:3.12-slim@sha256:....
Volba base image:
| Varianta | Vlastnosti |
|---|---|
python:3.12 |
plné Debian prostředí, velké |
python:3.12-slim |
dobrý kompromis, doporučený výchozí bod |
python:3.12-alpine |
velmi malé, ale musl libc – některé Python balíčky se kompilují nebo chovají jinak |
4. Slučování RUN a úklid ve stejné vrstvě
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
apt-get update a install musí být v jedné instrukci – jinak se může použít zastaralý cache index. Mazání v samostatném RUN by velikost image nezmenšilo, protože soubory už jsou v předchozí vrstvě.
5. Neběžte jako root
Výchozí uživatel v kontejneru je root. Pokud útočník najde chybu v aplikaci, má v kontejneru plná práva.
FROM python:3.12-slim
RUN useradd --create-home --uid 1000 app
WORKDIR /app
COPY --chown=app:app requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY --chown=app:app . .
USER app
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]
Oficiální Node image už uživatele node obsahuje, stačí USER node. Ne-root uživatel nemůže poslouchat na portech pod 1024 – použijte 8000, 3000 apod.
6. CMD a ENTRYPOINT v exec formě
CMD ["node", "server.js"] # exec forma – správně
CMD node server.js # shell forma – proces běží pod /bin/sh
V shell formě je PID 1 shell, který nepředává signály. docker stop pak čeká 10 sekund a kontejner tvrdě zabije – aplikace se nestihne korektně ukončit.
Rozdíl mezi instrukcemi:
ENTRYPOINT– co se spouští vždy,CMD– výchozí argumenty, které lze přepsat vdocker run image <příkaz>.
Pokud aplikace sama neobsluhuje signály nebo spouští podprocesy, spusťte kontejner s docker run --init (nebo init: true v Compose).
7. Jeden proces na kontejner
Nespouštějte v jednom kontejneru aplikaci, databázi i cron. Každá služba zvlášť a propojit je přes Docker Compose. Snáz se škálují, aktualizují a čtou se logy.
8. Logy na stdout/stderr
Aplikace v kontejneru by neměla psát logy do souborů, ale na standardní výstup. Docker je pak zachytí (docker logs). Pro Python přidejte:
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
9. Žádné tajné údaje v image
ENV API_KEY=... nebo COPY .env . uloží tajemství do vrstvy image – kdokoli s image ho přečte přes docker history nebo rozbalením vrstev. Tajné údaje předávejte až při spuštění (-e, --env-file). Pokud je potřebujete při buildu (privátní balíček), použijte BuildKit secrets:
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
docker build --secret id=npmrc,src=$HOME/.npmrc -t app .
10. HEALTHCHECK
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -fsS http://localhost:8000/health || exit 1
docker ps pak ukáže stav healthy/unhealthy a Compose na něj může čekat. Nástroj (curl, wget) musí v image existovat.
11. Labely a metadata
LABEL org.opencontainers.image.source="https://github.com/firma/app"
GitHub Container Registry podle tohoto labelu propojí image s repozitářem.
Kontrola: hadolint
Linter pro Dockerfile odhalí většinu výše uvedených chyb:
docker run --rm -i hadolint/hadolint < Dockerfile
Zapojit ho můžete i do CI.
Shrnutí v checklistu
- [ ] konkrétní tag base image, ideálně
-slim - [ ]
.dockerignores.git,.env,node_modules - [ ] nejdřív závislosti, potom kód
- [ ]
apt-getv jednomRUNs úklidem - [ ]
USERjiný než root - [ ] exec forma
CMD - [ ] žádné tajné údaje v
ENV/COPY - [ ]
HEALTHCHECK
Pokud je výsledný image i tak velký, další krok je multi-stage build.