Multi-stage builds w Docker – jak tworzyć mniejsze i bezpieczniejsze obrazy kontenerów
Jednym z najczęstszych problemów w świecie kontenerów jest tworzenie zbyt dużych obrazów.
Początkujący często budują obraz według prostego schematu:
Kod źródłowy
+
Narzędzia kompilacji
+
Biblioteki developerskie
+
Gotowa aplikacja
=
Obraz Docker
Problem polega na tym, że aplikacja produkcyjna zazwyczaj nie potrzebuje:
- kompilatora,
- kodu źródłowego,
- menedżerów pakietów,
- narzędzi debugowania,
- plików tymczasowych.
Tutaj pojawia się Multi-stage build – jedna z najważniejszych technik optymalizacji obrazów Docker.
Czym jest Multi-stage build?
Multi-stage build pozwala wykorzystać kilka etapów (FROM) w jednym Dockerfile.
Pierwszy etap służy do:
- kompilacji,
- przygotowania aplikacji,
- instalacji zależności.
Drugi etap zawiera tylko:
- gotowy wynik,
- minimalne środowisko uruchomieniowe.
Schemat:
id="4t3m1k"
Stage 1 - Builder
GCC
Node.js
Python
Source code
Dependencies
↓
Build
↓
Stage 2 - Runtime
Binary
Application
Minimal OS
Problem klasycznego Dockerfile
Przykład aplikacji Go.
Klasyczne podejście:
FROM golang:1.23
WORKDIR /app
COPY . .
RUN go build -o server
CMD ["./server"]
Obraz zawiera:
golang compiler
+
biblioteki
+
źródła
+
aplikacja
Rozmiar:
kilkaset MB.
A produkcja potrzebuje tylko:
server binary
Rozwiązanie – Multi-stage build
Lepszy Dockerfile:
FROM golang:1.23 AS builder
WORKDIR /app
COPY . .
RUN go build -o server
FROM alpine
COPY --from=builder /app/server /server
CMD ["/server"]
Co się dzieje?
Pierwszy etap:
golang image
↓
kompilacja
↓
server binary
Drugi etap:
alpine
+
server binary
Efekt:
Brak:
❌ kompilatora
❌ kodu źródłowego
❌ narzędzi developerskich
Jest:
✅ aplikacja
Jak działa COPY –from?
Kluczowa instrukcja:
COPY --from=builder
oznacza:
pobierz pliki z poprzedniego etapu budowania.
Przykład:
COPY --from=builder /app/dist /app
Docker nie kopiuje całego systemu.
Kopiuje tylko wskazane pliki.
Multi-stage build a bezpieczeństwo
To nie tylko optymalizacja rozmiaru.
To również redukcja powierzchni ataku.
Porównanie:
Zwykły obraz
id="h9u2ne"
Ubuntu
+
Compiler
+
Python tools
+
Git
+
Curl
+
Application
Atakujący po przejęciu kontenera ma:
- więcej narzędzi,
- więcej możliwości,
- więcej potencjalnych podatności.

Multi-stage
id="6wq5aj"
Minimal image
+
Application
Mniej elementów:
- mniej CVE,
- mniej bibliotek,
- prostszy audyt.
Multi-stage build dla aplikacji Node.js
Typowy problem:
Node podczas budowania potrzebuje:
- npm,
- webpack,
- dev dependencies.
Ale aplikacja produkcyjna potrzebuje tylko:
node_modules production
+
build files
Przykład:
FROM node:22 AS builder
WORKDIR /app
COPY package*.json .
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
Efekt:
Pierwszy obraz:
Node.js
Webpack
Source code
Drugi:
Nginx
Static files
Multi-stage build dla aplikacji Python
Przykład:
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install \
--prefix=/install \
-r requirements.txt
FROM python:3.12-slim
COPY --from=builder /install /usr/local
COPY app.py .
CMD ["python","app.py"]
Zaleta:
Obraz runtime nie zawiera:
- cache pip,
- narzędzi build,
- plików tymczasowych.
Multi-stage build i obrazy distroless
Bardzo często łączy się:
Multi-stage
+
Distroless images
Przykład:
FROM gcr.io/distroless/base
COPY --from=builder /app/server /
CMD ["/server"]
Obraz może nie posiadać:
- shell,
- package manager,
- narzędzi systemowych.
Dla atakującego:
docker exec
↓
brak bash
↓
utrudniona analiza
Multi-stage build i BuildKit
Multi-stage builds najlepiej działają z BuildKit.
BuildKit zapewnia:
- lepszy cache,
- równoległe etapy,
- optymalizację zależności.
Przykład:
docker buildx build .
Named stages
Dobra praktyka:
Zamiast:
FROM golang
używać:
FROM golang AS builder
Dlaczego?
Czytelność:
COPY --from=builder
jest jasne.
Debug stage
Ciekawa technika.
Można mieć osobny etap do debugowania:
FROM alpine AS debug
RUN apk add curl vim
FROM alpine AS production
COPY --from=builder app /
Produkcja:
minimalna
Debug:
narzędzia diagnostyczne
Test stage
Multi-stage świetnie nadaje się do CI/CD.
Przykład:
FROM app AS test
RUN npm test
FROM app AS production
CMD ["npm","start"]
Pipeline:
Build
↓
Tests
↓
Production Image
Target builds
Można budować konkretny etap:
docker build \
--target test .
albo:
docker build \
--target production .
Multi-stage build i Docker Compose
Compose może wskazać konkretny target:
services:
app:
build:
context: .
target: production
Najczęstsze błędy
1. Kopiowanie całego katalogu
Źle:
COPY --from=builder / /
Problem:
kopiujesz cały system.
Lepiej:
COPY --from=builder /app/bin/server /
2. Przenoszenie sekretów
Błąd:
COPY .env /app
Sekrety zostają w warstwach.
Lepsze:
- BuildKit secrets,
- Vault,
- Docker secrets.
3. Zbyt duży obraz bazowy
Przykład:
FROM ubuntu
dla małej aplikacji.
Często lepiej:
FROM alpine
lub:
FROM distroless
4. Brak .dockerignore
Bez:
.dockerignore
do obrazu mogą trafić:
- repozytoria Git,
- logi,
- pliki lokalne,
- sekrety.
Przykład:
.git
.env
node_modules
*.log
Multi-stage build a Supply Chain Security
Nowoczesny proces:
Developer
↓
Git
↓
BuildKit
↓
Multi-stage build
↓
Security Scan
↓
Image Signing
↓
Registry
↓
Production
Każdy etap może zostać zaatakowany.
Dlatego ważne są:
- minimalne obrazy,
- reprodukowalne buildy,
- skanowanie,
- podpisywanie.
Porównanie
| Cecha | Jeden etap | Multi-stage |
|---|---|---|
| Rozmiar obrazu | duży | mały |
| Bezpieczeństwo | słabsze | lepsze |
| CVE | więcej | mniej |
| Build tools w produkcji | często tak | nie |
| CI/CD | gorsze | idealne |
Multi-stage build w praktycznej architekturze
Profesjonalne środowisko:
Developer
↓
Dockerfile
↓
BuildKit
↓
Multi-stage build
↓
Minimal Image
↓
Trivy / Scanner
↓
Registry
↓
Kubernetes/Docker
Podsumowanie
Multi-stage builds to jedna z podstawowych praktyk tworzenia profesjonalnych obrazów Docker.
Dzięki nim można:
✅ zmniejszyć rozmiar obrazów
✅ usunąć narzędzia developerskie z produkcji
✅ ograniczyć liczbę podatności
✅ poprawić bezpieczeństwo supply chain
✅ przyspieszyć wdrożenia
W połączeniu z:
- BuildKit,
- Rootless Docker,
- Docker Bench Security,
- Seccomp,
- AppArmor/SELinux,
- cgroups v2,
- OverlayFS,
tworzą fundament bezpiecznego środowiska kontenerowego klasy produkcyjnej.






