Servery a DevOps

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.

12. 10. 2026 · 4 min čtení

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 v docker 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
  • [ ] .dockerignore s .git, .env, node_modules
  • [ ] nejdřív závislosti, potom kód
  • [ ] apt-get v jednom RUN s úklidem
  • [ ] USER jiný 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.