Docker - Pytania Rekrutacyjne dla DevOps i Backend Developera [2026]

Sławomir Plamowski 25 min czytania
backend ci-cd devops docker konteneryzacja kubernetes

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

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:

  • EXPOSE w Dockerfile - dokumentacja, nie otwiera portu
  • -p w 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:

  1. Container escape + root = pełna kontrola nad hostem
  2. Princple of least privilege
  3. 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ż


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.

Kup pełny dostęp Zobacz bezpłatny podgląd
Powrót do blogu

Zostaw komentarz

Pamiętaj, że komentarze muszą zostać zatwierdzone przed ich opublikowaniem.