Docker to lunchbox z przegródkami
Makaron nie rozleje się na deser, bo każde danie ma swoją przegródkę. Tyle Docker.
Jedzenie na wycieczce #
Idziesz ze szkołą na wycieczkę. Mama robi Ci lunchbox - takie pudełko z przegródkami.
- W jednej przegródce: makaron z sosem.
- W drugiej: sałatka.
- W trzeciej: ciastko.
Każda przegródka ma własny widelec, własną serwetkę i własny kubeczek. Sos z makaronu nie rozleje się na ciastko. Sałatka nie zabierze smaku deserowi.
To jest Docker. Każda aplikacja ma swoje pudełko z tym, czego potrzebuje.
Stary sposób - jedzenie na stole #
Dawniej aplikacje instalowało się bezpośrednio na serwerze. To było jak wysypanie całego obiadu na jeden stół:
- Aplikacja A potrzebuje Pythona 2.7.
- Aplikacja B potrzebuje Pythona 3.11.
- Aplikacja C potrzebuje starej wersji biblioteki X.
Wszystko na jednym stole. Sos z A miesza się z B. Kucharze płaczą. Dyżurny programista nie śpi w nocy.
Z Dockerem #
Każda aplikacja dostaje swój lunchbox (kontener):
- W lunchboxie A: Python 2.7 + aplikacja A.
- W lunchboxie B: Python 3.11 + aplikacja B.
- W lunchboxie C: Stara biblioteka X + aplikacja C.
Przenosisz lunchbox na inny serwer (np. do Chmury) - jedzenie smakuje dokładnie tak samo, bo zabrało ze sobą wszystko, czego potrzebuje.
Dockerfile - przepis na lunchbox #
# Weź podstawę - to Twoja "plastikowa miska"
FROM node:22-alpine
# Przestrzeń robocza (kuchnia)
WORKDIR /app
# Składniki do miski
COPY package.json .
RUN npm install
# Reszta jedzenia
COPY . .
# Jak to zjeść (jak uruchomić aplikację)
CMD ["node", "server.js"]
To jest przepis. Babcia (Docker) na jego podstawie buduje lunchbox - obraz (image). Z jednego obrazu możesz potem odpalić 100 identycznych lunchboxów na 100 serwerach.
Kontener vs Maszyna Wirtualna #
Ludzie często mylą. Różnica:
- Maszyna wirtualna = osobna, pełna kuchnia z własnymi ścianami i podłogą. Ciężka, wolno się rozkłada.
- Kontener = lunchbox, który korzysta ze wspólnej podłogi (jądra systemu), ale ma własne ścianki. Szybki, lekki.
Kontener uruchamia się w sekundy. VM w minuty.
Po co to w 2026? #
Każdy nowoczesny system - Netflix, Spotify, Uber, Twój bank - działa w kontenerach. Kubernetes dyryguje orkiestrą tysięcy lunchboxów na raz. Nauczysz się Dockera, nauczysz się rozmawiać z chmurą.
Pro-tip: Zanim napiszesz cokolwiek w Dockerfile, zadaj sobie pytanie: co jest minimalnym zestawem składników, żeby to jedzenie miało smak? Im mniej w lunchboxie, tym mniej miejsca na bałagan.
Czym właściwie jest kontener #
Kontener to proces (albo grupa procesów) uruchomiony na hoście tak, że widzi własny, odizolowany obraz systemu. W Linuksie izolacja jest zrobiona trzema mechanizmami jądra:
- Namespaces (
pid,net,mnt,ipc,uts,user,cgroup) - odcinają widok: proces w kontenerze widzi się jako PID 1, ma własny stack sieciowy, własny mount table, itd. - Cgroups (
cgroup v2) - limitują zasoby: CPU shares, pamięć, I/O. - Capabilities / seccomp / AppArmor - ograniczają syscalle i uprawnienia, żeby root w kontenerze != root na hoście.
Kontener nie ma własnego jądra - dzieli kernel hosta. Stąd narzut ~zerowy i start w milisekundach.
Porównanie do maszyny wirtualnej #
| VM | Kontener | |
|---|---|---|
| Jądro | własne (guest OS) | współdzielone z hostem |
| Start | sekundy - minuty (boot) | milisekundy (fork + exec) |
| Narzut RAM | setki MB (guest OS) | rozmiar procesu |
| Izolacja | hypervisor (bardzo mocna) | kernel namespaces (słabsza, wystarczająca dla multi-tenant jeśli + gVisor/Kata) |
| Obraz | GB (pełen system) | MB - setki MB (same zależności aplikacji) |
Obraz = warstwowy filesystem #
Dockerowy image to łańcuch read-only warstw (OverlayFS). Każda instrukcja w Dockerfile tworzy kolejną warstwę, która jest diffem względem poprzedniej. Przy docker run na wierzch doklejana jest cienka warstwa writable (copy-on-write).
Konsekwencje praktyczne:
- Kolejność w Dockerfile ma znaczenie - warstwy są cache’owane po hashu inputu; zmieniasz linię → wszystko poniżej rebuilduje się.
COPY package.jsonprzedCOPY . .pozwala cache’ować warstwę zRUN npm installprzy zmianie tylko kodu źródłowego.- Każda warstwa zostaje w obrazie na zawsze - nie da się “usunąć” plików z poprzedniej warstwy w sensie oszczędzania miejsca. Najwyżej je ukryć. Stąd multi-stage buildy.
Dockerfile dla .NET (ASP.NET Core API) #
# title: Dockerfile
# Stage 1 - build
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
# Warstwa z restore - invalidowana tylko gdy zmieniasz .csproj
COPY ["Api/Api.csproj", "Api/"]
RUN dotnet restore "Api/Api.csproj"
# Dopiero teraz reszta kodu - zmiana .cs nie invaliduje restore
COPY . .
WORKDIR /src/Api
RUN dotnet publish "Api.csproj" -c Release -o /app/publish /p:UseAppHost=false
# Stage 2 - runtime (sam ASP.NET bez SDK → obraz 3x mniejszy)
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 8080
ENTRYPOINT ["dotnet", "Api.dll"]
Multi-stage build: finalny obraz zawiera tylko runtime + skompilowane DLL-ki, bez SDK (~700 MB → ~220 MB).
appsettings.json vs env vars #
Konfiguracja w kontenerze powinna być sterowana przez zmienne środowiskowe, nie przez pliki w obrazie - obraz jest niezmienny, a te same bity mają chodzić w dev / staging / prod.
// title: Program.cs
var builder = WebApplication.CreateBuilder(args);
// ASP.NET Core domyślnie czyta env vars z prefixem ASPNETCORE_ i DOTNET_.
// Wszystko inne z konfiguracji też jest nadpisywalne, np.:
// ConnectionStrings__Default → ConnectionStrings:Default
builder.Services.AddDbContext<AppDbContext>(opts =>
opts.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
var app = builder.Build();
app.MapGet("/health", () => Results.Ok("ok"));
app.Run();
Przy docker run:
docker run -e ConnectionStrings__Default="Host=db;..." -e ASPNETCORE_URLS=http://+:8080 myapi
Registry i dystrybucja #
Obraz identyfikuje się przez content-addressable digest (SHA-256 treści) + human-readable tag:
ghcr.io/szymon/api@sha256:abc123...
ghcr.io/szymon/api:1.4.2
ghcr.io/szymon/api:latest
Tagi są mutowalne (latest jutro może wskazywać na coś innego), digesty - nie. W produkcji pinuj po digescie, jeśli zależy Ci na reprodukowalności.
Push / pull:
docker build -t ghcr.io/szymon/api:1.4.2 .
docker push ghcr.io/szymon/api:1.4.2
Orkiestracja - kiedy wystarczy Compose, a kiedy K8s #
- Docker Compose: jedna maszyna, kilka kontenerów, YAML opisuje topologię. Dobre do dev, małych self-hosted setupów (np. blog + baza + reverse proxy).
- Kubernetes: wiele maszyn, rolling updates, self-healing, service discovery, autoscaling. Narzut operacyjny znaczący - nie wdrażaj K8s “bo trzeba”, jeśli nie masz N+1 węzłów, CI/CD i ludzi, którzy to ogarną.
Większość domowych i small-scale produkcji to Compose + reverse proxy (nginx / Traefik / Caddy), nie K8s.
Pułapki, które warto znać #
- Warstwy nie znikają -
RUN apt install ... && apt remove ...nie zmniejsza obrazu. Te same instrukcje w jednej linii tak, bo powstaje jedna warstwa bez śladu po instalacji. - Root w kontenerze - domyślnie procesy startują jako root. Dla prod:
USER appw Dockerfile + user bez shell-a. - Sekrety w build argach -
ARG API_KEY=...wyciekną w historii warstw. Używaj BuildKit secrets (--mount=type=secret) albo wstrzykuj runtime-em przez env / vault. latestw prod - przy kolejnym pullu dostaniesz inny obraz. Pinuj wersje.
Skrótowo #
Kontener to linuksowy proces w kokonie z namespaces + cgroups. Obraz to warstwowy FS, zdeterminowany przez Dockerfile. Registry dystrybuuje je po digescie. Docker to tooling nad tymi prymitywami; w prod zwykle warto go opakować w orkiestrator, zależnie od skali.
Podobne wpisy
Komputer to bardzo szybki, ale całkowicie dosłowny wykonawca
Dlaczego kod musi być pisany z dokładnością, której nigdy nie żądamy od człowieka - i co z tego wynika dla całej branży.
Bazy wektorowe - biblioteka ułożona po nastrojach
Książki o miśkach leżą obok siebie, choć tytuły zaczynają się na różne litery. Tak działa baza wektorowa.
Zero Trust - zamek, w którym każdy pokój ma strażnika
W starym modelu broniliśmy fosy. Dziś każde drzwi w zamku mają własną kontrolę.
🔗 Linkują tu