Wszystkie wpisy

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 #

VMKontener
Jądrowłasne (guest OS)współdzielone z hostem
Startsekundy - minuty (boot)milisekundy (fork + exec)
Narzut RAMsetki MB (guest OS)rozmiar procesu
Izolacjahypervisor (bardzo mocna)kernel namespaces (słabsza, wystarczająca dla multi-tenant jeśli + gVisor/Kata)
ObrazGB (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.json przed COPY . . pozwala cache’ować warstwę z RUN npm install przy 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 app w 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.
  • latest w 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

🔗 Linkują tu