Docker dla programisty. Kiedy warto, kiedy overkill, mój production setup dla projektów klienta
Pierwszy raz zobaczyłem Docker w 2019. Kolega z zespołu chwalił się, że “aplikacja odpala się jedną komendą”. Sprawdziłem, faktycznie odpala. Ale nie zrozumiałem, po co, skoro npm start też odpala.
Rok później dołączyłem do zespołu, gdzie każdy programista miał inny setup lokalnie. “U mnie działa” było memem. Deploy na staging to była loteria. Wtedy zrozumiałem Docker.
Dziś każdy projekt w mojej agencji ma Docker Compose dla lokalnego dev’u i containers na produkcji. Bez tego nie wyobrażam sobie pracy zespołowej.
Ten post to praktyczny przewodnik. Kiedy Docker ma sens, kiedy nie, konkretne kompozycje dla mojego stacka, i najczęstsze błędy z produkcji.
Co to jest Docker
Docker to platforma do konteneryzacji. Zamiast instalować aplikację na maszynie, pakujesz ją w kontener - lightweight izolowaną paczkę z kodem, zależnościami, konfiguracją.
Kontener uruchamiasz na każdej maszynie z Dockerem. Ten sam kontener działa lokalnie u Ciebie, u kolegi, na CI, na produkcji. “Works on my machine” znika.
To nie wirtualna maszyna. Kontener dzieli kernel z host’em, jest lekki (setki MB, nie dziesiątki GB), uruchamia się w sekundach.
Kiedy Docker ma sens
Ma sens:
- Zespół 2+ osób pracujących na tym samym projekcie
- Aplikacja wymagająca specyficznej wersji Node/Python/PHP/etc.
- Stack z kilkoma serwisami (backend, frontend, DB, Redis, queue)
- Deployment poza Vercelem/Netlify (self-hosted, VPS, AWS ECS)
- Micro-services albo distributed systems
- Onboarding nowego developera (jeden
docker compose upzamiast 2h setupu)
Nie ma sensu:
- Solo project, jeden serwer, jeden stack
- Frontend-only na Vercelu
- Prototyp jednodniowy
- Statyczna strona (Jekyll, Astro, wysyła się na CDN)
Overhead Docker (build, config, learning curve) nie zawsze się spina. Solo dev na Vercelu spokojnie może pominąć.
Dla mojego mrellwart.com (Jekyll static site) nie używam Dockera. Dla projektów klientów w monorepo Next.js + Strapi + Postgres zawsze.
Podstawowe pojęcia (bez teorii)
Image: template, z którego uruchamiasz kontenery. Jak klasa w OOP.
Container: uruchomiona instancja obrazu. Jak instancja klasy.
Dockerfile: przepis, jak zbudować image. Instrukcje: skopiuj kod, zainstaluj zależności, uruchom.
Docker Compose: definiuje kilka kontenerów razem plus network między nimi. YAML zamiast kupy docker run komend.
Volume: persistent storage. Dane, które przeżywają restart kontenera (np. baza danych).
Network: kontenery komunikują się między sobą. Compose robi to za Ciebie automatycznie.
Reszta (registry, orchestration, Kubernetes) to advanced tematy, których nie potrzebujesz na początek.
Mój Dockerfile dla Next.js
# apps/web/Dockerfile
FROM node:20-alpine AS base
# Install pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate
# Dependencies layer
FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY apps/web/package.json ./apps/web/
COPY packages/types/package.json ./packages/types/
RUN pnpm install --frozen-lockfile
# Build layer
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/apps/web/node_modules ./apps/web/node_modules
COPY . .
RUN pnpm --filter web build
# Runtime layer
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "apps/web/server.js"]
Kluczowe rzeczy:
- Multi-stage build: separate stages dla deps, build, runtime. Runtime image jest mały (bez source code, bez dev deps).
- Layer caching: package.json copiowane pierwsze, dopiero potem source. Docker cache’uje layer z install, jeśli deps się nie zmieniły.
- Non-root user: security. Nie uruchamiaj kontenerów jako root.
- Alpine: mały base image (5MB), szybszy download.
Mój Docker Compose dla monorepo
# docker-compose.yml
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: devpassword
POSTGRES_DB: myapp
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dev"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
strapi:
build:
context: .
dockerfile: apps/cms/Dockerfile
environment:
DATABASE_CLIENT: postgres
DATABASE_HOST: postgres
DATABASE_PORT: 5432
DATABASE_NAME: myapp
DATABASE_USERNAME: dev
DATABASE_PASSWORD: devpassword
NODE_ENV: development
ports:
- "1337:1337"
volumes:
- ./apps/cms:/app/apps/cms
- /app/apps/cms/node_modules
depends_on:
postgres:
condition: service_healthy
web:
build:
context: .
dockerfile: apps/web/Dockerfile.dev
environment:
NEXT_PUBLIC_STRAPI_URL: http://strapi:1337
DATABASE_URL: postgresql://dev:devpassword@postgres:5432/myapp
ports:
- "3000:3000"
volumes:
- ./apps/web:/app/apps/web
- /app/apps/web/node_modules
depends_on:
- strapi
volumes:
postgres_data:
redis_data:
Uruchamianie: docker compose up. Nowy programista klonuje repo, uruchamia to, wszystko działa w 2 minuty.
Development vs Production Dockerfile
Osobne Dockerfile dla dev i prod. Bo:
Dev:
- Volume mount source code (żeby hot reload działał)
- Więcej dev tools (debugging, testing)
- Verbose logging
- Nie optymalizowane pod size
Prod:
- Multi-stage build (mały runtime image)
- Non-root user
- Health checks
- Minimalne dependencies
- Optymalizowane pod startup time
Konwencja u mnie: Dockerfile dla prod, Dockerfile.dev dla dev.
Volume mounting: jak nie zabić performance
Klasyczna pułapka. Volume mount z source code + node_modules = ekstremalnie wolny build.
Zły:
volumes:
- ./apps/web:/app/apps/web
To mountuje WSZYSTKO, włącznie z node_modules z Twojego hosta. Jeśli host to Mac, a kontener to Linux, node_modules są niekompatybilne.
Dobry:
volumes:
- ./apps/web:/app/apps/web
- /app/apps/web/node_modules
Drugi mount to “anonymous volume” - mówi Dockerowi “użyj tego, co jest w kontenerze, nie w hoście”. node_modules pozostaje kontenerowy.
Bez tego triku dev environment jest 10x wolniejszy.
Zdrowie kontenerów: health checks
Kontener się uruchamia. Ale czy działa? Nie zawsze wiadomo.
Health check w Dockerfile albo Compose:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Docker sprawdza co 30s. Jeśli 3 fail, kontener uznany za unhealthy.
Load balancery (nginx, Traefik, ECS) używają tego do routing. Unhealthy kontener nie dostaje ruchu.
Dla każdej aplikacji web dodaj /api/health endpoint zwracający 200 OK.
Networking między kontenerami
W Docker Compose kontenery widzą się po nazwie serwisu. W przykładzie wyżej:
- Web uderza w Strapi przez
http://strapi:1337 - Strapi uderza w Postgres przez
postgres:5432
Bez portów ludzkiego typu localhost. Wewnętrzny DNS.
Ports w Compose expose’ują kontener do host (Twojego Maca). Jeśli chcesz otworzyć localhost:3000 w browserze, potrzebujesz ports: - "3000:3000".
Networking pułapka: aplikacja w kontenerze próbuje uderzyć w localhost:5432 (na hoście). Nie działa, bo kontener ma swój localhost. Użyj nazwy serwisu (postgres:5432).
Optymalizacja rozmiaru images
Duży image = wolne push, pull, deploy.
Techniki:
Multi-stage builds. Runtime tylko z tym, co potrzeba. Bez dev deps, source code, build tools.
Alpine base. node:20-alpine (140 MB) vs node:20 (900 MB). 6x mniejszy.
.dockerignore. Nie kopiuj .git, node_modules, .next, .env.
Kolejność COPY. Copy package.json pierwsze, potem source. Layer z install cache’owany.
Distroless (advanced). Google’s distroless images, jeszcze mniejsze niż Alpine, tylko runtime, bez shell. Dla max optymalizacji.
Mój prod image dla Next.js: ~180 MB (vs 900 MB dla naiwnego setupu).
Deployment: docker w produkcji
Jak deployujesz kontener na produkcji, opcje:
Vercel/Netlify. Nie potrzebujesz Docker, oni robią to za Ciebie.
Fly.io. Bardzo dev-friendly, dobrze zintegrowany z Dockerem. Deploy przez fly deploy. Świetny dla małych i średnich apps.
Railway. Podobne do Fly.io, łatwiejsze onboarding, mniej flexibility.
Coolify (self-hosted). Otwiera się poza platformami, na własnym VPS. Wybór, jeśli chcesz kontroli albo prywatności.
Docker Swarm. Kubernetes lite. Dobre dla single-server multi-container. Rzadko używane w 2026 (przegrało z K8s).
Kubernetes (K8s). Standard enterprise. Overkill dla większości projektów. Wymaga dedicated devops.
AWS ECS / Fargate. Managed AWS. Dobre dla teams already on AWS.
Dla klientów mojej agencji: głównie Fly.io albo Coolify. K8s tylko, jeśli klient ma dedicated devops team.
Docker w CI/CD
Podstawowe użycie: build image w CI, push do registry, deploy z registry.
Pisałem szerzej w GitHub Actions CI/CD. Krótki flow:
- Push do main branch
- GitHub Actions triggeruje
- Build Docker image
- Run tests w kontenerze
- Push image do GitHub Container Registry
- Deploy do Fly.io (albo swojego serwera przez SSH)
Cały pipeline: 5-10 minut. Zero manual steps.
Bezpieczeństwo kontenerów
Non-root user. Nigdy USER root w produkcji. Attackers, którzy dostaną się do kontenera, mają root=nothing.
Minimalne dependencies. Im mniej rzeczy w image, tym mniejszy attack surface.
Scanning. Docker Scout, Trivy, Snyk skanują image pod kątem CVE. Włącz w CI.
Secrets nie w image. .env w .dockerignore. Sekrety w environment variables przy deploy.
Updated base images. node:20-alpine dostaje security patches. Rebuild image regularnie.
Read-only filesystem. --read-only flag lub read_only: true w Compose. Kontener nie może modyfikować siebie.
Monitorowanie kontenerów
Kontener działa. Ale czy zdrowy?
Docker stats (basic): CPU, memory per kontener w real-time.
Dozzle (open source): web UI do logów kontenerów. Prosty.
Portainer (open source): full-featured management UI dla Dockera.
Grafana + Prometheus (advanced): metrics stack. Alerty, dashboards, historia.
Sentry (application-level): errors, traces, performance. Independent from Docker.
Mój standard: Dozzle dla logów lokalnie, Fly.io built-in monitoring dla produkcji.
Częste błędy
Ignorowanie warstw. Copy source jako pierwsze = deps re-install przy każdej zmianie kodu. Copy package.json first, source second.
Bloated images. Twój image ma 2 GB. Powód: kopiujesz cały monorepo, dev deps, git history. Multi-stage plus .dockerignore fix.
Missing health checks. Kontener odpala się, ale nie odpowiada. Load balancer nadal wysyła ruch. Users dostają 502. Add health check.
Volume permission issues. Zapisujesz plik jako user X w kontenerze, host widzi jako root. Chown w Dockerfile albo używaj bind mounts z uwagą.
Hardcoded configs. Docker image z hardcoded database URL dla dev. Deploy na prod, aplikacja próbuje uderzyć w dev DB. Environment variables od dnia 1.
Root user w prod. Sec risk. Zawsze non-root user w prod images.
Ignorowanie logów. Kontener padnie w produkcji, nikt nie wie dlaczego. Zbieraj logi (Loki, CloudWatch, Datadog).
Alternatywy dla Dockera
Podman: rootless container runtime, kompatybilny z Docker. Rising alternatywa, głównie dla Red Hat ecosystem.
Nix: reproducible builds, całkiem inna filozofia. Steep learning curve, ale purists lubią.
Native builds (Vercel/Netlify/Cloudflare Workers): dla frontendu Docker nie potrzebny.
Serverless (AWS Lambda, Cloudflare Workers): container-less deployment. Prostsze dla małych functions.
Dla większości projektów Docker to nadal default. Alternatywy mają swoje niche.
Co czytać dalej
- “Docker Deep Dive” Nigel Poulton. Standard reference. Dobra do systematycznego uczenia.
- Docker docs. Best-in-class dokumentacja.
- DevOps for Developers newsletter. Praktyczne case studies.
Nie kupuj kursów Dockera na Udemy. Docs + jeden real project = wystarczy.
Od czego zacząć
Jeśli nie używałeś jeszcze Dockera:
- Zainstaluj Docker Desktop. Free dla personal use, płatne dla firm >250 employees.
- Uruchom
docker run hello-world. Pierwszy kontener. - Zbuduj Dockerfile dla jednego swojego projektu. Prostszego niż full monorepo.
- Dodaj docker-compose.yml dla dev environment.
- Ucz się przez debugowanie. Container failed to start? Log check, fix, repeat.
- Po tygodniu zbuduj multi-service compose (backend + DB + frontend).
- Po miesiącu deploy container do produkcji (Fly.io start).
Docker to fundamental skill dla programisty w 2026. Nawet jeśli używasz Vercela dla frontendu (Dockerless), backend, DB, workers wymagają kontenerów.
Programiści bez Dockera skills będą ograniczeni w wyborze projektów i pracodawców. Programiści z Dockerem są przygotowani na cały spektrum, od SaaS na Vercelu do enterprise na K8s.
Pisałem o mojej stack agencji w postach o TypeScript, Next.js, Strapi. Docker to klej, który spina to wszystko na produkcji.
Powiązane wpisy
-
GitHub Actions. Mój CI CD dla Next.js plus Strapi w 100 liniach YAML
Pierwszy CI/CD, który skonfigurowałem, to był Jenkins w 2019. Trzy tygodnie setupu, custom serwer, plugin hell. Po ka...
-
N8n dla agencji. Automatyzacje, które oszczędzają mi 10 godzin tygodniowo w agencji
W 2024 płaciłem Zapierowi 400 zł miesięcznie za 15 automatyzacji. W 2025 przeszedłem na n8n self-hosted. Płacę 40 zł ...