Docker - Pytania Rekrutacyjne dla DevOps i Backend Developera [2026]
Przygotowujesz się do rozmowy na stanowisko DevOps Engineer lub Backend Developer? Docker to jedna z najważniejszych technologii, o którą rekruterzy pytają na każdej rozmowie. Ten przewodnik zawiera 42 pytania rekrutacyjne z odpowiedziami - od podstaw konteneryzacji po zaawansowane tematy jak Docker Compose i bezpieczeństwo.
Spis treści
- Podstawy Docker
- Dockerfile
- Zarządzanie kontenerami
- Wolumeny i przechowywanie danych
- Sieciowanie
- Docker Compose
- Bezpieczeństwo
- Optymalizacja i dobre praktyki
- Zobacz też
Podstawy Docker
Czym jest Docker i jakie problemy rozwiązuje?
Odpowiedź w 30 sekund: Docker to platforma do konteneryzacji aplikacji, która rozwiązuje problem "działa na mojej maszynie". Pakuje aplikację wraz ze wszystkimi zależnościami (biblioteki, konfiguracja, runtime) w przenośny kontener, który działa identycznie na każdym środowisku.
Odpowiedź w 2 minuty: Docker rewolucjonizuje sposób budowania, dystrybucji i uruchamiania aplikacji. Główne problemy, które rozwiązuje:
| Problem | Rozwiązanie Docker |
|---|---|
| "Działa u mnie" | Identyczne środowisko wszędzie |
| Konflikty zależności | Izolacja per kontener |
| Skomplikowany deployment |
docker run zamiast długiej dokumentacji |
| Wolne środowiska dev | Sekundy zamiast minut na uruchomienie |
| Skalowanie | Natychmiastowe uruchomienie nowych instancji |
# Zamiast: instalacja Node, npm, zależności, konfiguracja...
# Wystarczy:
docker run -p 3000:3000 myapp
# Cały zespół używa tego samego środowiska
docker compose up
Docker standaryzuje deployment - ten sam obraz działa na laptopie developera, w CI/CD, na staging i produkcji.
Czym różni się kontener od maszyny wirtualnej?
Odpowiedź w 30 sekund: Kontener współdzieli kernel z hostem i izoluje tylko procesy aplikacji, dając szybszy start (sekundy vs minuty) i mniejsze zużycie zasobów. Maszyna wirtualna wirtualizuje całe hardware i uruchamia osobny system operacyjny, co daje silniejszą izolację kosztem wydajności.
Odpowiedź w 2 minuty:
Kluczowa różnica leży w poziomie izolacji i sposobie wirtualizacji. Kontenery wirtualizują na poziomie systemu operacyjnego, współdzieląc kernel z hostem, co daje im znacznie mniejszy narzut. Maszyny wirtualne wirtualizują całe hardware przez hypervisor, uruchamiając pełny system operacyjny dla każdej instancji.
| Cecha | Kontener | Maszyna wirtualna |
|---|---|---|
| Izolacja | Procesy (cgroups, namespaces) | Hardware (hypervisor) |
| Kernel | Współdzielony z hostem | Własny per VM |
| Rozmiar | MB (tylko aplikacja) | GB (pełny OS) |
| Start | Sekundy | Minuty |
| Gęstość | Setki na hoście | Dziesiątki na hoście |
| Overhead | Minimalny | Znaczący |
┌─────────────────────────────────────┐
│ KONTENERY │
├──────────┬──────────┬──────────────┤
│ App A │ App B │ App C │
│ Libs A │ Libs B │ Libs C │
├──────────┴──────────┴──────────────┤
│ Docker Engine │
├────────────────────────────────────┤
│ Host OS (Linux) │
├────────────────────────────────────┤
│ Hardware │
└────────────────────────────────────┘
┌─────────────────────────────────────┐
│ MASZYNY WIRTUALNE │
├──────────┬──────────┬──────────────┤
│ App A │ App B │ App C │
│ Guest OS │ Guest OS │ Guest OS │
├──────────┴──────────┴──────────────┤
│ Hypervisor │
├────────────────────────────────────┤
│ Host OS │
├────────────────────────────────────┤
│ Hardware │
└────────────────────────────────────┘
Kiedy użyć czego:
- Kontenery - mikrousługi, CI/CD, szybkie skalowanie, jednolite środowiska
- VM - pełna izolacja OS, różne systemy operacyjne, legacy aplikacje
Czym jest obraz Docker (Docker image)?
Odpowiedź w 30 sekund: Obraz Docker to niezmienne, warstwowe "snapshot" aplikacji zawierający kod, runtime, biblioteki i konfigurację. Jest szablonem do tworzenia kontenerów - jeden obraz może uruchomić wiele kontenerów. Obrazy przechowujemy w rejestrach jak Docker Hub.
Odpowiedź w 2 minuty: Obraz składa się z warstw (layers), gdzie każda warstwa reprezentuje instrukcję z Dockerfile. Warstwy są read-only i współdzielone między obrazami, co oszczędza miejsce.
# Każda instrukcja = nowa warstwa
FROM node:20-alpine # Warstwa bazowa
WORKDIR /app # Warstwa 2
COPY package*.json ./ # Warstwa 3
RUN npm install # Warstwa 4
COPY . . # Warstwa 5
CMD ["node", "server.js"]
# Zarządzanie obrazami
docker images # Lista lokalnych obrazów
docker pull nginx:latest # Pobierz z rejestru
docker build -t myapp:v1 . # Zbuduj z Dockerfile
docker push myrepo/myapp:v1 # Wyślij do rejestru
docker rmi myapp:v1 # Usuń obraz
docker image prune # Usuń nieużywane
Nazewnictwo obrazów:
registry.example.com/namespace/image:tag
└──────────────────┘ └────────┘ └───┘ └─┘
Rejestr Repo Nazwa Tag
Przykłady:
- nginx:latest
- node:20-alpine
- gcr.io/my-project/api:v2.1.0
Czym jest kontener Docker?
Odpowiedź w 30 sekund: Kontener to uruchomiona instancja obrazu Docker - izolowany proces (lub grupa procesów) z własnym systemem plików, siecią i przestrzenią procesów. W przeciwieństwie do obrazu, kontener jest "żywy" i może być uruchomiony, zatrzymany, usunięty.
Odpowiedź w 2 minuty: Kontener dodaje zapisywalną warstwę (writable layer) na górze warstw obrazu. Wszystkie zmiany w kontenerze są zapisywane w tej warstwie i znikają po usunięciu kontenera (chyba że używamy wolumenów).
# Cykl życia kontenera
docker create nginx # Utwórz (nie uruchomiony)
docker start <container_id> # Uruchom
docker stop <container_id> # Zatrzymaj (graceful)
docker kill <container_id> # Zabij (force)
docker restart <container_id># Restart
docker rm <container_id> # Usuń
# Popularne kombinacje
docker run -d nginx # create + start w tle
docker run -it ubuntu bash # Interaktywny terminal
Stany kontenera:
Created → Running → Paused → Running → Stopped → Removed
↑__________________|
Wiele kontenerów może działać z tego samego obrazu, każdy z własną zapisywalną warstwą:
docker run -d --name web1 nginx
docker run -d --name web2 nginx
docker run -d --name web3 nginx
# 3 niezależne kontenery, ten sam obraz bazowy
Co to jest Docker Hub i do czego służy?
Odpowiedź w 30 sekund: Docker Hub to publiczny rejestr obrazów Docker - "GitHub dla kontenerów". Zawiera oficjalne obrazy (nginx, postgres, node), obrazy społeczności oraz pozwala hostować prywatne repozytoria. Integruje się z CI/CD do automatycznego budowania obrazów.
Odpowiedź w 2 minuty:
Docker Hub funkcjonuje podobnie do GitHub, ale dla obrazów kontenerów. Oferuje oficjalne obrazy utrzymywane przez Docker Inc., zweryfikowane obrazy od partnerów oraz możliwość hostowania własnych repozytoriów publicznych i prywatnych.
# Praca z Docker Hub
docker login # Zaloguj się
docker search nginx # Szukaj obrazów
docker pull nginx:alpine # Pobierz obraz
docker tag myapp user/myapp:v1 # Oznacz do push
docker push user/myapp:v1 # Wyślij do Hub
Typy obrazów na Docker Hub:
| Typ | Przykład | Opis |
|---|---|---|
| Oficjalne |
nginx, postgres
|
Utrzymywane przez Docker, zweryfikowane |
| Verified | bitnami/nginx |
Od zweryfikowanych wydawców |
| Community | user/custom-app |
Od społeczności |
Alternatywne rejestry:
- Amazon ECR - AWS
- Google GCR - Google Cloud
- Azure ACR - Azure
- GitHub GHCR - GitHub Container Registry
- Harbor - self-hosted open source
# Użycie alternatywnego rejestru
docker pull gcr.io/google-samples/hello-app:1.0
docker pull ghcr.io/owner/image:tag
Dockerfile
Czym jest Dockerfile i jakie ma zastosowanie?
Odpowiedź w 30 sekund: Dockerfile to plik tekstowy zawierający instrukcje do automatycznego budowania obrazu Docker. Definiuje obraz bazowy, kopiowanie plików, instalację zależności, konfigurację i komendę startową. Jest wersjonowany razem z kodem aplikacji.
Odpowiedź w 2 minuty:
Dockerfile to przepis krok po kroku, który Docker wykonuje podczas budowania obrazu. Każda instrukcja tworzy nową warstwę w obrazie, a kolejność ma znaczenie dla wydajności cache. Dobrze napisany Dockerfile powinien być deterministyczny i produkować identyczny obraz przy każdym buildzie.
# Przykładowy Dockerfile dla Node.js
FROM node:20-alpine
# Metadane
LABEL maintainer="dev@example.com"
LABEL version="1.0"
# Katalog roboczy
WORKDIR /app
# Najpierw zależności (lepsze cache)
COPY package*.json ./
RUN npm ci --only=production
# Potem kod aplikacji
COPY . .
# Port (dokumentacja)
EXPOSE 3000
# Użytkownik non-root
USER node
# Healthcheck
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -q --spider http://localhost:3000/health || exit 1
# Komenda startowa
CMD ["node", "server.js"]
Budowanie obrazu:
# Podstawowe budowanie
docker build -t myapp:v1 .
# Z określeniem Dockerfile
docker build -f Dockerfile.prod -t myapp:prod .
# Z argumentami build-time
docker build --build-arg NODE_ENV=production -t myapp .
# Bez cache (pełny rebuild)
docker build --no-cache -t myapp .
Jaka jest różnica między instrukcjami RUN, CMD i ENTRYPOINT?
Odpowiedź w 30 sekund: RUN wykonuje komendy podczas budowania obrazu (np. instalacja pakietów). CMD definiuje domyślną komendę uruchamianą w kontenerze (można nadpisać). ENTRYPOINT definiuje stałą komendę wykonywalną, której argumenty można rozszerzać przez CMD lub linię poleceń.
Odpowiedź w 2 minuty:
Te trzy instrukcje są często mylone, ale działają na zupełnie różnych etapach cyklu życia kontenera. RUN modyfikuje obraz podczas budowania, tworząc nowe warstwy. CMD i ENTRYPOINT definiują zachowanie podczas uruchamiania kontenera, przy czym ich współdziałanie pozwala tworzyć elastyczne obrazy.
# RUN - wykonywane podczas BUILD
FROM ubuntu
RUN apt-get update
RUN apt-get install -y nginx
# Każdy RUN = nowa warstwa w obrazie
# CMD - domyślna komenda (nadpisywalna)
CMD ["nginx", "-g", "daemon off;"]
# ENTRYPOINT - stała komenda
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
Różnice w praktyce:
| Instrukcja | Kiedy wykonywana | Nadpisywalna | Użycie |
|---|---|---|---|
| RUN | docker build | N/A | Instalacja, konfiguracja |
| CMD | docker run | Tak, przez args | Domyślne argumenty |
| ENTRYPOINT | docker run | Tylko z --entrypoint | Główna komenda |
# CMD można nadpisać
docker run myimage echo "hello" # Zastępuje CMD
# ENTRYPOINT rozszerza się
docker run myimage -c "custom.conf" # Dodaje do ENTRYPOINT
Typowy wzorzec:
# Obraz jako "executable"
FROM python:3.11-slim
WORKDIR /app
COPY . .
ENTRYPOINT ["python", "script.py"]
CMD ["--help"]
# docker run myimage → python script.py --help
# docker run myimage --verbose → python script.py --verbose
Czym różni się instrukcja COPY od ADD w Dockerfile?
Odpowiedź w 30 sekund: COPY kopiuje pliki z hosta do obrazu - jest prostsza i preferowana. ADD ma dodatkowe funkcje: automatycznie rozpakowuje archiwa tar i może pobierać pliki z URL. Best practice: używaj COPY, chyba że potrzebujesz rozpakowywania tar.
Odpowiedź w 2 minuty:
Obie instrukcje służą do przenoszenia plików do obrazu, ale mają różne możliwości i zastosowania. COPY jest prostsze i bardziej przejrzyste, dlatego jest zalecane jako domyślny wybór. ADD ma ukryte zachowania, które mogą być nieintuicyjne i utrudniają zrozumienie Dockerfile.
# COPY - proste kopiowanie (preferowane)
COPY package.json ./
COPY src/ ./src/
COPY ["plik ze spacjami.txt", "./"]
# ADD - dodatkowe funkcje
ADD app.tar.gz /app/ # Auto-rozpakowanie
ADD https://example.com/file /app/ # Pobieranie (niezalecane)
| Cecha | COPY | ADD |
|---|---|---|
| Kopiowanie lokalne | ✅ | ✅ |
| Rozpakowanie tar | ❌ | ✅ |
| Pobieranie URL | ❌ | ✅ (niezalecane) |
| Przejrzystość | ✅ | ❌ |
| Best practice | ✅ | Tylko dla tar |
Dlaczego COPY jest preferowane:
# ❌ ADD ukrywa intencję
ADD https://example.com/file.tar.gz /app/
# ✅ Jawne i kontrolowane
RUN curl -L https://example.com/file.tar.gz -o /tmp/file.tar.gz \
&& tar -xzf /tmp/file.tar.gz -C /app/ \
&& rm /tmp/file.tar.gz
Kiedy używać ADD:
# Jedyny sensowny przypadek - rozpakowanie lokalnego archiwum
ADD rootfs.tar.gz /
Jak działa wieloetapowy build (multi-stage build) i kiedy go używać?
Odpowiedź w 30 sekund: Multi-stage build pozwala użyć wielu instrukcji FROM w jednym Dockerfile. Każdy stage może kopiować artefakty z poprzednich stage'ów. Używaj do separacji środowiska build od runtime - finalny obraz zawiera tylko niezbędne pliki, bez narzędzi kompilacji.
Odpowiedź w 2 minuty:
Multi-stage build rozwiązuje problem "grubych" obrazów produkcyjnych. W tradycyjnym podejściu finalny obraz zawiera wszystkie narzędzia build-time (kompilatory, devDependencies), które są niepotrzebne w runtime. Multi-stage pozwala użyć pełnego środowiska do budowania, a do finalnego obrazu skopiować tylko niezbędne artefakty.
# STAGE 1: Build
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# STAGE 2: Production
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]
# Opcjonalny stage testów
FROM builder AS test
RUN npm test
Korzyści:
Single-stage: 1.2 GB (Node + devDeps + src + build tools)
Multi-stage: 150 MB (Alpine + production deps + dist)
# Buduj konkretny stage
docker build --target builder -t myapp:builder .
docker build --target production -t myapp:prod .
docker build --target test -t myapp:test .
Go - klasyczny przykład:
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o /app/main
FROM scratch
COPY --from=builder /app/main /main
ENTRYPOINT ["/main"]
# Finalny obraz: ~10 MB zamiast 1 GB!
Jak zoptymalizować rozmiar obrazu Docker?
Odpowiedź w 30 sekund: Używaj multi-stage builds, minimalnych obrazów bazowych (alpine, distroless), łącz komendy RUN, umieszczaj rzadko zmieniane warstwy wyżej (lepszy cache), usuwaj cache i niepotrzebne pliki, używaj .dockerignore.
Odpowiedź w 2 minuty:
1. Minimalne obrazy bazowe:
# ❌ Duży obraz
FROM ubuntu:22.04 # ~77 MB
FROM node:20 # ~1 GB
# ✅ Małe obrazy
FROM alpine:3.18 # ~7 MB
FROM node:20-alpine # ~180 MB
FROM gcr.io/distroless/nodejs20 # ~130 MB
2. Łączenie komend RUN:
# ❌ Wiele warstw
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean
# ✅ Jedna warstwa + czyszczenie
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
3. Optymalna kolejność (cache):
# Najpierw zależności (rzadko się zmieniają)
COPY package*.json ./
RUN npm ci --only=production
# Potem kod (często się zmienia)
COPY . .
4. .dockerignore:
node_modules
.git
.env
*.md
Dockerfile
.dockerignore
coverage
dist
5. Multi-stage (patrz poprzednie pytanie)
Porównanie rozmiarów:
node:20 ~1.1 GB
node:20-slim ~240 MB
node:20-alpine ~180 MB
distroless ~130 MB
Zarządzanie kontenerami
Jak uruchomić kontener w tle (tryb detached)?
Odpowiedź w 30 sekund:
Użyj flagi -d lub --detach: docker run -d nginx. Kontener działa w tle, a terminal pozostaje wolny. Logi można sprawdzić przez docker logs, a wejść do kontenera przez docker exec.
Odpowiedź w 2 minuty:
Tryb detached to standardowy sposób uruchamiania kontenerów w produkcji i development. Kontener działa jako proces w tle, a Docker zwraca kontrolę do terminala natychmiast po uruchomieniu. Do interakcji z takim kontenerem używasz osobnych komend.
# Uruchom w tle
docker run -d --name webserver nginx
# Sprawdź status
docker ps
# Sprawdź logi
docker logs webserver
docker logs -f webserver # Follow (jak tail -f)
# Wejdź do kontenera
docker exec -it webserver bash
# Zatrzymaj
docker stop webserver
Typowe flagi docker run:
| Flaga | Opis | Przykład |
|---|---|---|
-d |
Detached (w tle) | docker run -d nginx |
-p |
Mapowanie portów | -p 8080:80 |
--name |
Nazwa kontenera | --name myapp |
-e |
Zmienna środowiskowa | -e NODE_ENV=prod |
-v |
Wolumen | -v ./data:/data |
--rm |
Usuń po zatrzymaniu | docker run --rm alpine |
--restart |
Polityka restart | --restart unless-stopped |
# Produkcyjny przykład
docker run -d \
--name api \
-p 3000:3000 \
-e NODE_ENV=production \
-e DB_HOST=postgres \
-v ./logs:/app/logs \
--restart unless-stopped \
myapp:v1.0
Jak wejść do działającego kontenera (uruchomić sesję shell)?
Odpowiedź w 30 sekund:
Użyj docker exec -it <container> bash lub sh dla kontenerów Alpine. Flaga -i to interactive, -t to terminal (TTY). Dla debugowania można też użyć docker attach, ale to podłącza do głównego procesu.
Odpowiedź w 2 minuty:
Docker exec tworzy nowy proces wewnątrz działającego kontenera, co pozwala na debugowanie, inspekcję i administrację bez przerywania głównego procesu. To podstawowe narzędzie do rozwiązywania problemów z kontenerami w runtime.
# Bash (jeśli dostępny)
docker exec -it mycontainer bash
# Sh (zawsze dostępny, Alpine)
docker exec -it mycontainer sh
# Konkretna komenda
docker exec mycontainer ls -la /app
docker exec mycontainer cat /etc/nginx/nginx.conf
# Jako root (nawet jeśli USER jest inny)
docker exec -u root -it mycontainer bash
# Z ustawionymi zmiennymi
docker exec -e DEBUG=true mycontainer npm run debug
Różnica exec vs attach:
| Komenda | Zachowanie |
|---|---|
docker exec |
Nowy proces w kontenerze |
docker attach |
Podłącza do PID 1 (głównego procesu) |
# attach - ctrl+c zatrzyma kontener!
docker attach mycontainer
# Bezpieczne wyjście z attach
# Ctrl+P, Ctrl+Q (detach sequence)
Debugowanie kontenera bez shell:
# Dla minimalistycznych obrazów (distroless)
docker run -it --rm --pid=container:myapp busybox sh
Jak przekazać zmienne środowiskowe do kontenera?
Odpowiedź w 30 sekund:
Użyj flagi -e dla pojedynczych zmiennych lub --env-file dla pliku z wieloma zmiennymi. W Docker Compose definiuj w sekcji environment lub env_file. Secrets przekazuj przez Docker secrets lub montowane pliki, nie przez zmienne.
Odpowiedź w 2 minuty:
Zmienne środowiskowe to standardowy sposób konfiguracji kontenerów zgodny z metodologią 12-factor app. Pozwalają oddzielić konfigurację od kodu i używać tego samego obrazu w różnych środowiskach. Docker oferuje kilka metod ich przekazywania, od pojedynczych flag po pliki konfiguracyjne.
# Pojedyncze zmienne
docker run -e NODE_ENV=production \
-e DB_HOST=localhost \
-e DB_PORT=5432 \
myapp
# Plik ze zmiennymi
docker run --env-file .env myapp
# Przekazanie zmiennej z hosta
export API_KEY=secret123
docker run -e API_KEY myapp # Użyje wartości z hosta
.env file:
NODE_ENV=production
DB_HOST=postgres
DB_USER=admin
DB_PASS=secret123
# Komentarze są ignorowane
Docker Compose:
services:
api:
image: myapp
environment:
- NODE_ENV=production
- DB_HOST=postgres
env_file:
- .env
- .env.local
Bezpieczeństwo - NIE używaj dla secrets:
# ❌ Widoczne w docker inspect
docker run -e DB_PASSWORD=secret myapp
# ✅ Docker secrets (Swarm) lub montowany plik
docker secret create db_pass ./password.txt
docker run --secret db_pass myapp
# ✅ Montowany plik
docker run -v ./secrets:/run/secrets:ro myapp
Wolumeny i przechowywanie danych
Czym są wolumeny Docker (Docker volumes)?
Odpowiedź w 30 sekund: Wolumeny to preferowany mechanizm persystencji danych w Docker. Są zarządzane przez Docker, przechowywane poza warstwą kontenera i przeżywają restart/usunięcie kontenera. Można je współdzielić między kontenerami i łatwo backupować.
Odpowiedź w 2 minuty:
Wolumeny są zarządzane przez Docker daemon i przechowywane w specjalnym katalogu na hoście (domyślnie /var/lib/docker/volumes). Ich główna zaleta to niezależność od cyklu życia kontenerów - dane przeżywają restart, aktualizację czy nawet usunięcie kontenera.
# Tworzenie wolumenu
docker volume create mydata
# Lista wolumenów
docker volume ls
# Szczegóły wolumenu
docker volume inspect mydata
# Użycie wolumenu
docker run -v mydata:/app/data myapp
# Usuwanie
docker volume rm mydata
docker volume prune # Usuń nieużywane
Zalety wolumenów:
- Zarządzane przez Docker
- Niezależne od systemu plików hosta
- Można używać driverów (NFS, cloud storage)
- Łatwe backup i migracja
- Lepsze I/O performance
# Docker Compose z wolumenami
services:
db:
image: postgres:15
volumes:
- postgres_data:/var/lib/postgresql/data
app:
image: myapp
volumes:
- app_logs:/app/logs
volumes:
postgres_data:
app_logs:
Backup wolumenu:
docker run --rm \
-v mydata:/source:ro \
-v $(pwd):/backup \
alpine tar czf /backup/mydata.tar.gz -C /source .
Jaka jest różnica między wolumenem a bind mount?
Odpowiedź w 30 sekund:
Wolumen jest zarządzany przez Docker i przechowywany w /var/lib/docker/volumes. Bind mount montuje konkretny katalog z hosta. Wolumeny są zalecane dla production, bind mounts dla development (hot reload kodu).
Odpowiedź w 2 minuty:
Wybór między wolumenem a bind mount zależy od przypadku użycia. Wolumeny są lepsze dla danych, które powinny być zarządzane przez Docker (bazy danych, pliki aplikacji). Bind mounts są idealne gdy chcesz współdzielić konkretny katalog z hosta, szczególnie podczas development z hot reload.
| Cecha | Wolumen | Bind Mount |
|---|---|---|
| Lokalizacja | Docker zarządza | Ty kontrolujesz |
| Tworzenie | docker volume create |
Katalog musi istnieć |
| Przenośność | Wysoka | Zależna od ścieżki |
| Backup | Łatwy | Standardowe narzędzia |
| Performance | Zoptymalizowane | Zależy od FS |
| Użycie | Production | Development |
# Wolumen (zalecany)
docker run -v myvolume:/app/data myapp
# Bind mount (development)
docker run -v /host/path:/container/path myapp
docker run -v $(pwd)/src:/app/src myapp
# Nowa składnia --mount (bardziej czytelna)
docker run --mount type=volume,src=myvolume,dst=/app/data myapp
docker run --mount type=bind,src=$(pwd)/src,dst=/app/src myapp
Development z hot reload:
services:
app:
build: .
volumes:
- ./src:/app/src # Bind mount - kod
- node_modules:/app/node_modules # Wolumen - deps
command: npm run dev
volumes:
node_modules:
Read-only bind mount:
docker run -v $(pwd)/config:/app/config:ro myapp
Sieciowanie
Jakie są domyślne typy sieci w Docker?
Odpowiedź w 30 sekund: Docker ma trzy domyślne sieci: bridge (domyślna, izolowana sieć dla kontenerów), host (kontener używa sieci hosta bezpośrednio), none (brak sieci). Można też tworzyć własne sieci bridge dla lepszej izolacji i DNS.
Odpowiedź w 2 minuty:
Docker oferuje różne drivery sieciowe dla różnych przypadków użycia. Domyślna sieć bridge izoluje kontenery od hosta, ale pozwala im komunikować się między sobą. Własne sieci bridge (user-defined) dodają automatyczne DNS resolution po nazwach kontenerów.
# Lista sieci
docker network ls
# Domyślne sieci:
NETWORK ID NAME DRIVER
xxxx bridge bridge # Domyślna
yyyy host host # Sieć hosta
zzzz none null # Brak sieci
| Sieć | Opis | Użycie |
|---|---|---|
| bridge | Izolowana sieć, NAT do hosta | Domyślne dla kontenerów |
| host | Bez izolacji, porty hosta | Maksymalna wydajność |
| none | Brak sieci | Pełna izolacja |
| custom bridge | User-defined, własny DNS | Production |
# Bridge (domyślna)
docker run -d nginx # Automatycznie w bridge
# Host - kontener używa sieci hosta
docker run --network host nginx
# None - brak sieci
docker run --network none alpine
# Własna sieć (zalecane!)
docker network create myapp-network
docker run --network myapp-network --name api myapp
docker run --network myapp-network --name db postgres
# api może łączyć się z db przez: postgres://db:5432
Jak kontenery komunikują się ze sobą w ramach tej samej sieci?
Odpowiedź w 30 sekund:
W user-defined networks (własne sieci bridge) kontenery mogą komunikować się przez nazwy kontenerów jako DNS hostname. Docker zapewnia wbudowane DNS. W domyślnej sieci bridge trzeba używać IP lub --link (deprecated).
Odpowiedź w 2 minuty:
W user-defined networks Docker automatycznie konfiguruje wbudowany DNS server. Każdy kontener może łączyć się z innymi kontenerami w tej samej sieci używając ich nazw jako hostname. To eliminuje potrzebę ręcznego zarządzania adresami IP i upraszcza konfigurację mikroserwisów.
# Tworzenie własnej sieci
docker network create app-network
# Uruchom kontenery w tej samej sieci
docker run -d --network app-network --name postgres postgres:15
docker run -d --network app-network --name api myapp
# Teraz api może łączyć się przez:
# postgres://postgres:5432
Przykład Node.js łączący się z PostgreSQL:
const { Pool } = require('pg');
const pool = new Pool({
host: 'postgres', // Nazwa kontenera jako hostname
port: 5432,
database: 'mydb',
user: 'admin',
password: process.env.DB_PASSWORD
});
Docker Compose - automatyczne sieci:
services:
api:
image: myapp
environment:
- DB_HOST=postgres # Nazwa usługi
- REDIS_HOST=redis
postgres:
image: postgres:15
redis:
image: redis:alpine
# Compose automatycznie tworzy sieć: projectname_default
# Wszystkie usługi są w niej dostępne przez nazwy
Jak mapować porty między hostem a kontenerem?
Odpowiedź w 30 sekund:
Użyj flagi -p lub --publish: -p HOST_PORT:CONTAINER_PORT. Możesz też podać IP: -p 127.0.0.1:8080:80. Bez mapowania porty są dostępne tylko wewnątrz sieci Docker.
Odpowiedź w 2 minuty:
Mapowanie portów to mechanizm NAT (Network Address Translation), który przekierowuje ruch z portu hosta do portu kontenera. Bez mapowania aplikacja w kontenerze jest dostępna tylko dla innych kontenerów w tej samej sieci Docker, ale nie z zewnątrz.
# Podstawowe mapowanie
docker run -p 8080:80 nginx
# http://localhost:8080 → kontener:80
# Wiele portów
docker run -p 80:80 -p 443:443 nginx
# Konkretny interfejs
docker run -p 127.0.0.1:8080:80 nginx # Tylko localhost
docker run -p 192.168.1.10:8080:80 nginx # Konkretne IP
# Losowy port hosta
docker run -p 80 nginx
docker port <container_id> # Sprawdź przydzielony port
# UDP
docker run -p 53:53/udp dns-server
# Zakres portów
docker run -p 8000-8010:8000-8010 myapp
Docker Compose:
services:
nginx:
image: nginx
ports:
- "80:80"
- "443:443"
api:
image: myapp
ports:
- "3000:3000"
expose:
- "9090" # Tylko wewnętrzny, nie mapowany na host
EXPOSE vs -p:
-
EXPOSEw Dockerfile - dokumentacja, nie otwiera portu -
-pw docker run - faktycznie mapuje port
Docker Compose
Czym jest Docker Compose i kiedy go używać?
Odpowiedź w 30 sekund: Docker Compose to narzędzie do definiowania i uruchamiania wielokontenerowych aplikacji Docker. Definiujesz usługi, sieci i wolumeny w pliku YAML i uruchamiasz jedną komendą. Używaj dla local development, testów, i prostych deploymentów.
Odpowiedź w 2 minuty:
Docker Compose deklaratywnie definiuje całą infrastrukturę aplikacji w jednym pliku YAML. Zamiast pamiętać długie komendy docker run z wieloma flagami, definiujesz wszystko raz i uruchamiasz jedną komendą. Compose automatycznie tworzy sieć dla usług, zarządza wolumenami i pilnuje kolejności uruchamiania.
# docker-compose.yml
version: '3.8'
services:
api:
build: ./api
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- DB_HOST=postgres
- REDIS_HOST=redis
volumes:
- ./api/src:/app/src
depends_on:
- postgres
- redis
postgres:
image: postgres:15
environment:
POSTGRES_DB: myapp
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:alpine
ports:
- "6379:6379"
volumes:
postgres_data:
# Podstawowe komendy
docker compose up # Uruchom wszystko
docker compose up -d # W tle
docker compose up --build # Przebuduj obrazy
docker compose down # Zatrzymaj i usuń
docker compose down -v # + usuń wolumeny
docker compose logs -f api # Logi usługi
docker compose exec api sh # Shell w usłudze
docker compose ps # Status usług
Kiedy używać:
- ✅ Local development
- ✅ Testy integracyjne
- ✅ CI/CD pipelines
- ✅ Proste deploymenty (single host)
- ❌ Produkcja multi-host (użyj Kubernetes/Swarm)
Jak zarządzać zależnościami między usługami (depends_on)?
Odpowiedź w 30 sekund:
depends_on kontroluje kolejność uruchamiania, ale nie czeka na gotowość usługi! Kontener bazy może być "running" zanim baza jest gotowa. Dla pełnej gotowości użyj health checks z condition: service_healthy.
Odpowiedź w 2 minuty:
Domyślne depends_on kontroluje tylko kolejność startu kontenerów, nie gwarantuje że usługa jest gotowa na połączenia. Kontener bazy danych może być w stanie "running" zanim serwer bazy faktycznie nasłuchuje na porcie. Dla prawdziwej gotowości potrzebne są health checks.
services:
api:
build: .
depends_on:
- postgres
- redis
# Uruchomi się PO starcie postgres i redis
# ALE nie czeka na gotowość bazy!
postgres:
image: postgres:15
Problem: baza nie jest gotowa:
services:
api:
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:15
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin"]
interval: 5s
timeout: 5s
retries: 5
Alternatywa - wait script w aplikacji:
services:
api:
command: sh -c "./wait-for-it.sh postgres:5432 -- npm start"
Wszystkie warunki depends_on:
depends_on:
postgres:
condition: service_started # Domyślne
redis:
condition: service_healthy # Czeka na healthcheck
migrations:
condition: service_completed_successfully # Czeka na zakończenie
Bezpieczeństwo
Dlaczego nie należy uruchamiać procesów w kontenerze jako root?
Odpowiedź w 30 sekund:
Jeśli atakujący ucieknie z kontenera, ma uprawnienia root na hoście. Uruchamianie jako non-root minimalizuje ryzyko eskalacji uprawnień. Używaj instrukcji USER w Dockerfile i unikaj --privileged.
Odpowiedź w 2 minuty:
Uruchamianie jako root w kontenerze to poważne ryzyko bezpieczeństwa. Mimo izolacji kontenerów, luki w kernelu lub błędy konfiguracji mogą pozwolić na "container escape". Jeśli proces w kontenerze działa jako root, atakujący po ucieczce ma pełne uprawnienia na hoście.
# ❌ Domyślnie jako root
FROM node:20
WORKDIR /app
COPY . .
CMD ["node", "server.js"]
# ✅ Jako non-root
FROM node:20
WORKDIR /app
COPY --chown=node:node . .
USER node
CMD ["node", "server.js"]
Dlaczego to ważne:
- Container escape + root = pełna kontrola nad hostem
- Princple of least privilege
- Compliance requirements (PCI-DSS, SOC2)
# Sprawdź jako kto działa kontener
docker exec mycontainer whoami
# Uruchom jako konkretny użytkownik
docker run --user 1000:1000 myapp
Obrazy distroless - brak shell, brak root:
FROM gcr.io/distroless/nodejs20
COPY --from=builder /app/dist /app
CMD ["server.js"]
# Brak shell, brak package managera, non-root
Dodatkowe zabezpieczenia:
# Brak nowych uprawnień
docker run --security-opt=no-new-privileges myapp
# Read-only filesystem
docker run --read-only myapp
# Nigdy nie używaj bez potrzeby!
docker run --privileged myapp # ❌ Pełen dostęp do hosta
Jakie są najlepsze praktyki bezpieczeństwa dla obrazów Docker?
Odpowiedź w 30 sekund:
Używaj oficjalnych/zweryfikowanych obrazów, pinuj wersje (nie latest), skanuj obrazy (Trivy, Snyk), minimalizuj powierzchnię ataku (alpine/distroless), uruchamiaj jako non-root, nie przechowuj secrets w obrazie.
Odpowiedź w 2 minuty:
1. Oficjalne obrazy + pinowane wersje:
# ❌ Nieprzewidywalny
FROM node:latest
# ✅ Pinowana wersja
FROM node:20.10.0-alpine3.18
2. Skanowanie obrazów:
# Trivy (popularne, open source)
trivy image myapp:v1
# Docker Scout
docker scout cves myapp:v1
# Snyk
snyk container test myapp:v1
3. Multi-stage + minimalne obrazy:
FROM node:20-alpine AS builder
# build steps...
FROM gcr.io/distroless/nodejs20
COPY --from=builder /app/dist /app
USER nonroot
CMD ["server.js"]
4. Secrets poza obrazem:
# ❌ Secrets w obrazie
ENV API_KEY=secret123
COPY .env /app/
# ✅ Secrets w runtime
# docker run -e API_KEY=$API_KEY myapp
# lub Docker secrets / mounted files
5. Checklist bezpieczeństwa:
- Oficjalny/zweryfikowany obraz bazowy
- Pinowane wersje (nie latest)
- Multi-stage build
- Non-root user
- Brak secrets w obrazie
- Skanowanie CVE
- Read-only filesystem gdzie możliwe
- Minimal capabilities
Optymalizacja i dobre praktyki
Jak efektywnie wykorzystać cache warstw podczas budowania obrazu?
Odpowiedź w 30 sekund: Umieszczaj instrukcje rzadko się zmieniające na początku Dockerfile (system packages, dependencies), a często zmieniające się (kod aplikacji) na końcu. Docker cachuje warstwy i przebudowuje tylko od miejsca zmiany.
Odpowiedź w 2 minuty:
Docker buduje obrazy warstwa po warstwie, cachując wynik każdej instrukcji. Gdy plik się zmieni, Docker invaliduje cache tej warstwy i wszystkich następnych. Kluczem do wydajnego budowania jest ułożenie instrukcji tak, by często zmieniające się pliki były kopiowane jak najpóźniej.
# ❌ Słaby cache - każda zmiana kodu = reinstall deps
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]
# ✅ Dobry cache - dependencies cachowane oddzielnie
FROM node:20-alpine
WORKDIR /app
# 1. Najpierw tylko package.json (rzadko się zmienia)
COPY package*.json ./
RUN npm ci
# 2. Potem kod (często się zmienia)
COPY . .
CMD ["node", "server.js"]
Jak działa cache:
Warstwa 1: FROM node:20-alpine → cached ✓
Warstwa 2: WORKDIR /app → cached ✓
Warstwa 3: COPY package*.json → cached ✓ (jeśli nie zmienione)
Warstwa 4: RUN npm ci → cached ✓
Warstwa 5: COPY . . → INVALIDATED (kod się zmienił)
↓
Wszystko poniżej
przebudowywane
Wskazówki:
# Łącz RUN dla mniejszej liczby warstw
RUN apt-get update && \
apt-get install -y package1 package2 && \
rm -rf /var/lib/apt/lists/*
# Używaj .dockerignore
node_modules
.git
*.md
Dockerfile
Kiedy używać Docker a kiedy rozważyć alternatywy (Podman, containerd)?
Odpowiedź w 30 sekund: Docker to standard z największym ekosystemem. Podman to bezdem-onowa alternatywa (rootless, daemonless) kompatybilna z Docker CLI. containerd to runtime używany przez Kubernetes. Wybór zależy od wymagań bezpieczeństwa i środowiska.
Odpowiedź w 2 minuty:
Docker to de facto standard, ale nie jedyne rozwiązanie. Alternatywy powstały głównie z potrzeb bezpieczeństwa (brak demona, rootless by default) oraz integracji z Kubernetes (który usunął wsparcie dla Docker runtime na rzecz containerd). Wybór narzędzia zależy od wymagań środowiska i polityk bezpieczeństwa.
| Narzędzie | Daemon | Rootless | Kubernetes | Kiedy używać |
|---|---|---|---|---|
| Docker | Tak | Eksperymentalnie | Przestarzałe | Development, CI/CD |
| Podman | Nie | Natywnie | Tak | Security-first, RHEL |
| containerd | Tak | Nie | Natywnie | Kubernetes production |
| nerdctl | Nie (containerd) | Tak | Tak | Lekki, containerd CLI |
Podman - drop-in replacement:
# Instalacja
brew install podman # macOS
sudo dnf install podman # Fedora/RHEL
# Te same komendy co Docker
podman run -d -p 8080:80 nginx
podman build -t myapp .
podman-compose up
# Alias dla kompatybilności
alias docker=podman
Zalety Podman:
- Bez demona (fork-exec model)
- Rootless by default
- Kompatybilność z OCI
- Pods (jak Kubernetes)
Kiedy Docker:
- Większy ekosystem
- Docker Desktop dla Mac/Windows
- Docker Compose
- Lepsze wsparcie community
Kiedy Podman:
- Wymagania bezpieczeństwa (rootless)
- Środowiska RHEL/Fedora
- Bez demona (CI/CD runners)
Zobacz też
- Kompletny Przewodnik - Rozmowa DevOps Engineer - pełny przewodnik przygotowania do rozmowy DevOps
- Wzorce i Architektura Backend - mikrousługi i konteneryzacja w praktyce
Ten artykuł jest częścią serii przygotowującej do rozmów rekrutacyjnych na stanowisko DevOps Engineer. Sprawdź nasze fiszki Docker z 42 pytaniami i odpowiedziami do nauki.
Chcesz więcej pytań rekrutacyjnych?
To tylko jeden temat z naszego kompletnego przewodnika po rozmowach rekrutacyjnych. Uzyskaj dostęp do 800+ pytań z 13 technologii.
