Multi-stage builds w Docker – jak tworzyć mniejsze i bezpieczniejsze obrazy kontenerów
Linux

Multi-stage builds w Docker – jak tworzyć mniejsze i bezpieczniejsze obrazy kontenerów

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 builds w Docker – jak tworzyć mniejsze i bezpieczniejsze obrazy kontenerów
Multi-stage builds w Docker – jak tworzyć mniejsze i bezpieczniejsze obrazy kontenerów

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.

Polecane wpisy
Instalacja Linuksa na serwerze
Instalacja Linuksa na serwerze

Instalacja Linuksa na serwerze Przygotowanie: Wybierz dystrybucję Linuksa: Istnieje wiele dystrybucji Linuksa, które można zainstalować na serwerze. Popularne dystrybucje to Czytaj dalej

Ubuntu poradnik
Ubuntu poradnik

Ubuntu to jedna z najpopularniejszych dystrybucji Linuxa, dostępna dla każdego. Jest łatwy w użyciu i oferuje szeroki zakres funkcji. Istnieje Czytaj dalej

Marek "Netbe" Lampart Inżynier informatyki Marek Lampart to doświadczony inżynier informatyki z ponad 25-letnim stażem w zawodzie. Specjalizuje się w systemach Windows i Linux, bezpieczeństwie IT, cyberbezpieczeństwie, administracji serwerami oraz diagnostyce i optymalizacji systemów. Na netbe.pl publikuje praktyczne poradniki, analizy i instrukcje krok po kroku, pomagając administratorom, specjalistom IT oraz zaawansowanym użytkownikom rozwiązywać realne problemy techniczne.