Modul 7 — Virtualizace a kontejnery: Docker
31. Best practices pro psaní Dockerfile (velikost image, vrstvy, bezpečnost)
Praktická doporučení pro menší, rychlejší a bezpečnější Docker images — multi-stage build, cachování vrstev, nepoužívání rootu a další zásady.
Úvod a kontext
Funkční Dockerfile z lekce o základech Dockeru stačí k tomu, aby aplikace
běžela — ale "funguje" a "je dobře napsaný pro produkci" jsou dvě různé
věci. Špatně napsaný Dockerfile vede ke zbytečně velkým images (pomalé
stahování a nasazování), pomalým buildům (dlouhé čekání při každé změně
kódu) a zbytečným bezpečnostním rizikům (kontejner běžící jako root,
zbytečné nástroje uvnitř image usnadňující útočníkovi po případném
průniku). Tato lekce shrnuje osvědčená pravidla, která se v praxi
používají téměř univerzálně.
Teorie
Volba základního image
Menší základní image znamená menší výsledný image, rychlejší stahování a
menší plochu pro bezpečnostní zranitelnosti:
alpinevarianty (postavené na Alpine Linux) jsou řádově menší
(jednotky MB) než plné distribuce, ale používajímuslmístoglibc,
což může u některých binárních závislostí (zejména v Pythonu/Node.js s
nativními rozšířeními) způsobit kompatibilní problémy.slimvarianty (u oficiálních Python/Node.js images) jsou
kompromis — menší než plný image, ale pořádglibc-kompatibilní.distrolessimages (od Google) obsahují jen samotnou aplikaci a
její běhové závislosti, bez shellu, balíčkovacího systému nebo
jakýchkoliv dalších nástrojů — nejmenší plocha pro útok, ale hůř se v
nich ladí (nenísh, do kterého by šlo vlézt přesdocker exec).
Pořadí instrukcí a cachování vrstev
Docker cachuje výsledek každé vrstvy (instrukce) a při dalším buildu ji
znovu nepočítá, pokud se nezměnil vstup dané instrukce a instrukce
předtím. Z toho plyne klíčové pravidlo: řadit instrukce od
nejméně po nejčastěji se měnící. Špatný příklad:
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python", "app.py"]
Zde COPY . . zkopíruje celý zdrojový kód (včetně requirements.txt),
takže i drobná změna v aplikačním kódu zneplatní cache a vynutí si
kompletní přeinstalování všech závislostí při každém buildu — pomalé.
Správně:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
Teď se requirements.txt kopíruje a závislosti instalují zvlášť, dřív
než zbytek kódu — dokud se soubor se závislostmi nezmění, Docker použije
cachovanou vrstvu s nainstalovanými balíčky a přeskočí pip install
úplně, i když se změnil zbytek aplikace.
Multi-stage build
Multi-stage build řeší situaci, kdy sestavení aplikace potřebuje nástroje
(kompilátor, build systém), které ve finálním image vůbec nejsou potřeba
za běhu — jen zbytečně zvětšují image a rozšiřují plochu pro
zranitelnosti:
# stage 1: sestavení
FROM node:20 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# stage 2: finální image jen s výsledkem
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
První stage (build) obsahuje celé Node.js prostředí s nástroji
potřebnými pro sestavení frontendové aplikace. Druhá stage vychází z
mnohem menšího nginx:alpine a instrukcí COPY --from=build si z první
stage vezme jen výsledný zkompilovaný obsah (dist) — samotné Node.js,
zdrojové soubory ani build nástroje se do finálního image vůbec
nedostanou.
.dockerignore
Podobně jako .gitignore u Gitu, soubor .dockerignore vyloučí soubory
z tzv. build contextu (co je vůbec dostupné instrukcím COPY/ADD):
.git
node_modules
__pycache__
*.pyc
.env
Kromě zrychlení buildu (méně dat se posílá do Docker démona) je to i
bezpečnostní opatření — zabrání náhodnému zabalení souborů s tajnými
klíči (.env) do image.
Nespouštět kontejner jako root
Ve výchozím nastavení běží procesy uvnitř kontejneru jako root. Pokud
útočník najde zranitelnost v aplikaci a získá spuštění kódu uvnitř
kontejneru, root uvnitř kontejneru mu usnadňuje případný únik z izolace
kontejneru (container escape) na hostitelský systém. Doporučená praxe je
vytvořit a používat nepřivilegovaného uživatele:
FROM python:3.12-slim
RUN useradd --create-home appuser
WORKDIR /app
COPY --chown=appuser:appuser . .
USER appuser
CMD ["python", "app.py"]
Explicitní verze a nepoužívání latest
Tag latest je pohyblivý cíl — co dnes znamená latest, se zítra může
změnit na zcela jinou verzi, což vede k neopakovatelným buildům a
překvapením v produkci. Vždy uvádět konkrétní verzi (python:3.12.4-slim
místo python:latest), ideálně i konkrétní hash image pro nejvyšší
reprodukovatelnost v kritických prostředích.
Skenování zranitelností
Base images i nainstalované balíčky mohou obsahovat známé bezpečnostní
zranitelnosti. Nástroje jako docker scout, Trivy nebo Snyk umí image
oskenovat a nahlásit zranitelné závislosti ještě před nasazením do
produkce (podrobněji v modulu o DevSecOps).
Praktický příklad
Shrnutí všech výše uvedených zásad v jednom realistickém Dockerfile pro
Python aplikaci:
FROM python:3.12.4-slim AS build
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
FROM python:3.12.4-slim
RUN useradd --create-home appuser
WORKDIR /app
COPY --from=build /root/.local /home/appuser/.local
COPY --chown=appuser:appuser . .
ENV PATH=/home/appuser/.local/bin:$PATH
USER appuser
CMD ["python", "app.py"]
Odpovídající .dockerignore:
.git
__pycache__
*.pyc
.env
tests/
Kontrola velikosti výsledného image a porovnání s naivní jednostage
verzí:
docker build -t appka:optimalizovana .
docker images appka:optimalizovana
Oskenování image nástrojem Trivy (pokud je nainstalovaný) na známé
zranitelnosti:
trivy image appka:optimalizovana
Shrnutí
Menší a bezpečnější Docker images vznikají kombinací několika návyků:
volbou malého základního image, řazením instrukcí od nejméně po
nejčastěji se měnící kvůli efektivnímu cachování, použitím multi-stage
buildu k oddělení sestavovacích nástrojů od finálního běhového prostředí,
vyloučením zbytečných souborů přes .dockerignore, spouštěním aplikace
pod nepřivilegovaným uživatelem místo rootu a používáním explicitních
verzí místo pohyblivého tagu latest. Pravidelné skenování images na
známé zranitelnosti doplňuje tato opatření o kontrolu závislostí.
Kontrolní otázky
- Proč se
COPY requirements.txt .a instalace závislostí obvykle
umísťují předCOPY . ., které kopíruje zbytek aplikace? - K čemu slouží multi-stage build a jaký konkrétní problém řeší?
- Proč je nebezpečné spouštět procesy uvnitř kontejneru jako root, a jak
se tomu dá v Dockerfile předejít? - Upravte ukázkový Dockerfile z lekce tak, aby místo
python:3.12.4-slim
používalpython:3.12.4-alpine, a vysvětlete jedno riziko této změny.
Lekce na sebe nejsou zamčené — libovolnou lekci můžete otevřít i označit jako hotovou v jakémkoliv pořadí.