To jest darmowy podgląd

To jest próbka 15 pytań z naszej pełnej kolekcji 42 pytań rekrutacyjnych. Uzyskaj pełny dostęp do wszystkich pytań ze szczegółowymi odpowiedziami i przykładami kodu.

Kup pełny dostęp

Pytania rekrutacyjne Docker - Część 1

Czym jest Docker i jakie problemy rozwiązuje?

Odpowiedź w 30 sekund: Docker to platforma do konteneryzacji aplikacji, która pakuje aplikację wraz z wszystkimi jej zależnościami w izolowane kontenery. Rozwiązuje problem "u mnie działa" - zapewnia, że aplikacja będzie działać identycznie na każdym środowisku, od laptopa programisty po serwer produkcyjny.

Odpowiedź w 2 minuty: Docker to otwarta platforma do tworzenia, wdrażania i uruchamiania aplikacji w kontenerach. Kontenery umożliwiają pakowanie aplikacji wraz z wszystkimi niezbędnymi bibliotekami, zależnościami i plikami konfiguracyjnymi w standardową jednostkę, która może działać na dowolnym systemie obsługującym Dockera.

Docker rozwiązuje kilka kluczowych problemów w rozwoju oprogramowania. Po pierwsze, eliminuje różnice między środowiskami developerskim, testowym i produkcyjnym - jeśli działa w kontenerze na laptopie, będzie działać identycznie na serwerze. Po drugie, upraszcza zarządzanie zależnościami - wszystkie wymagane biblioteki i wersje są zawarte w obrazie. Po trzecie, umożliwia szybkie skalowanie aplikacji i efektywne wykorzystanie zasobów systemowych.

Dodatkowo Docker przyspiesza proces wdrażania aplikacji, ułatwia integrację ciągłą (CI/CD), zwiększa izolację między aplikacjami i ułatwia pracę w architekturze mikroserwisowej. Jest szeroko wykorzystywany w DevOps i stał się standardem w branży IT.

Przykład kodu:

# Sprawdzenie wersji Dockera
docker --version

# Uruchomienie pierwszego kontenera
docker run hello-world

# Wyświetlenie uruchomionych kontenerów
docker ps

Materiały

↑ Powrót na górę

Jak działa wieloetapowy build (multi-stage build) i kiedy go używać?

Odpowiedź w 30 sekund: Multi-stage build pozwala używać wielu instrukcji FROM w jednym Dockerfile, gdzie każdy stage może mieć własny obraz bazowy. Służy to do optymalizacji - w pierwszych stage'ach kompilujesz/budujesz aplikację, a w ostatnim stage kopiujesz tylko artefakty, bez narzędzi buildowych. Rezultat: drastycznie mniejszy finalny obraz.

Odpowiedź w 2 minuty: Multi-stage build to technika wprowadzona w Docker 17.05, która pozwala na tworzenie wielu etapów (stages) w jednym Dockerfile, gdzie każdy etap może bazować na innym obrazie. Każda instrukcja FROM rozpoczyna nowy stage. Możesz kopiować artefakty między stage'ami używając COPY --from=<stage>. Tylko ostatni stage tworzy finalny obraz - poprzednie są odrzucane.

Główne zastosowanie to optymalizacja rozmiaru obrazu. Na przykład przy aplikacji Go czy Java potrzebujesz kompilatora i narzędzi buildowych podczas budowania, ale w środowisku runtime wystarczy tylko skompilowany binary. Bez multi-stage musiałbyś includować cały toolchain w finalnym obrazie (setki MB). Z multi-stage: pierwszy stage kompiluje aplikację używając pełnego obrazu (golang:1.21, maven:3.9), a drugi stage kopiuje tylko skompilowany binary do minimalnego obrazu (alpine, distroless).

Dodatkowe korzyści to lepsze bezpieczeństwo (mniej zainstalowanych pakietów = mniejsza powierzchnia ataku), szybszy deployment (mniejszy obraz = szybsze pull/push) i czystszy Dockerfile. Można też używać multi-stage do różnych celów - stage do testów, stage do developmentu, stage do produkcji. Każdy stage może być budowany osobno używając --target flag.

Przykład kodu:

# Stage 1: Build stage - duży obraz z narzędziami
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp

# Stage 2: Production stage - minimalny obraz
FROM alpine:3.18
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# Kopiowanie tylko binarki ze stage'a builder
COPY --from=builder /app/myapp .
EXPOSE 8080
CMD ["./myapp"]
# Multi-stage dla Node.js z oddzielnymi stages
FROM node:18 AS dependencies
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM node:18 AS builder
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:18-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=dependencies /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]
# Budowanie całego multi-stage Dockerfile
docker build -t myapp:prod .

# Budowanie tylko konkretnego stage (np. do testów)
docker build --target builder -t myapp:builder .

# Porównanie rozmiaru
# Obraz z golang:1.21 (z kompilatorami): ~800MB
# Obraz finalny alpine z binary: ~15MB
docker images

Materiały

↑ Powrót na górę

Czym różni się kontener od maszyny wirtualnej?

Odpowiedź w 30 sekund: Kontenery dzielą jądro systemu operacyjnego hosta i wirtualizują tylko warstwę aplikacji, podczas gdy maszyny wirtualne (VM) wirtualizują cały system operacyjny wraz ze sprzętem. Kontenery są lżejsze, szybsze w uruchamianiu (sekundy vs minuty) i zużywają znacznie mniej zasobów niż VM.

Odpowiedź w 2 minuty: Główna różnica między kontenerami a maszynami wirtualnymi polega na poziomie wirtualizacji. Maszyny wirtualne wirtualizują cały sprzęt komputera - każda VM ma własny pełny system operacyjny, kernel i alokowane zasoby (RAM, CPU). Wymaga to hypervisora do zarządzania warstwą wirtualizacji. W efekcie VMs są ciężkie (GB rozmiaru), wolno się uruchamiają (minuty) i zużywają dużo zasobów.

Kontenery natomiast dzielą kernel systemu operacyjnego hosta i izolują tylko przestrzeń użytkownika aplikacji. Docker Engine zarządza kontenerami bezpośrednio na systemie hosta, wykorzystując funkcje jądra Linux (namespaces, cgroups) do izolacji. Dzięki temu kontenery są bardzo lekkie (MB rozmiaru), uruchamiają się błyskawicznie (sekundy) i można uruchomić ich setki na jednym hoście.

Kontenery są idealne do aplikacji cloud-native, mikroserwisów i szybkiego skalowania. VMs lepiej sprawdzają się gdy potrzebna jest pełna izolacja systemowa, różne systemy operacyjne lub aplikacje wymagające głębokiej konfiguracji OS. W praktyce często używa się obu technologii - kontenery działające wewnątrz VM w chmurze.

Przykład kodu:

# Kontener uruchamia się w sekundy
time docker run alpine echo "Hello"

# Jeden host może uruchamiać wiele lekkich kontenerów
docker run -d nginx
docker run -d redis
docker run -d postgres

# Sprawdzenie zużycia zasobów przez kontenery
docker stats

Materiały

↑ Powrót na górę

Czym jest obraz Docker (Docker image)?

Odpowiedź w 30 sekund: Obraz Docker to niemutowalny szablon zawierający wszystko co potrzebne do uruchomienia aplikacji - kod, runtime, biblioteki, zmienne środowiskowe i pliki konfiguracyjne. Obrazy są budowane z warstw (layers) i służą jako podstawa do tworzenia kontenerów.

Odpowiedź w 2 minuty: Obraz Docker to read-only template, który definiuje jak powinien wyglądać kontener. Można go porównać do klasy w programowaniu obiektowym - obraz to definicja, a kontener to instancja tej definicji. Obrazy są budowane na podstawie instrukcji zawartych w pliku Dockerfile.

Kluczową cechą obrazów jest ich warstwowa architektura. Każda instrukcja w Dockerfile tworzy nową warstwę, która jest cache'owana. Jeśli podczas kolejnego buildu określona warstwa się nie zmieniła, Docker wykorzysta wersję z cache, co znacznie przyspiesza proces budowania. Warstwy są współdzielone między obrazami - jeśli kilka obrazów bazuje na tym samym obrazie podstawowym (np. Ubuntu), ta warstwa jest przechowywana tylko raz.

Obrazy mogą być przechowywane w rejestrach takich jak Docker Hub, które działają jak "GitHub dla obrazów Docker". Możesz pobierać (pull) publiczne obrazy lub publikować (push) własne. Obrazy są oznaczane tagami (np. nginx:latest, node:18-alpine) do wersjonowania. Dzięki immutability obrazów mamy pewność, że aplikacja zawsze uruchomi się w identycznym środowisku.

Przykład kodu:

# Pobranie obrazu z Docker Hub
docker pull nginx:latest

# Wyświetlenie lokalnych obrazów
docker images

# Zbudowanie własnego obrazu z Dockerfile
docker build -t moja-aplikacja:1.0 .

# Wyświetlenie historii warstw obrazu
docker history nginx

# Usunięcie obrazu
docker rmi nginx:latest

Materiały

↑ Powrót na górę

Czym jest kontener Docker?

Odpowiedź w 30 sekund: Kontener Docker to działająca instancja obrazu Docker - izolowany proces z własnym systemem plików, siecią i przestrzenią procesów. Kontenery są lekkie, przenośne i zawierają wszystko co potrzebne do uruchomienia aplikacji, działając konsekwentnie w każdym środowisku.

Odpowiedź w 2 minuty: Kontener to działająca instancja obrazu Docker, podobnie jak obiekt jest instancją klasy w programowaniu. Gdy uruchamiasz obraz poprzez docker run, Docker tworzy kontener - izolowany proces działający w przestrzeni użytkownika systemu operacyjnego. Każdy kontener ma własny system plików (pochodzący z obrazu), własną sieć wirtualną i izolowaną przestrzeń procesów.

Kontenery wykorzystują mechanizmy jądra Linux - namespaces do izolacji (PID, network, mount, UTS, IPC, user) i cgroups do ograniczania zasobów (CPU, pamięć, I/O). Dzięki temu kontener "myśli", że jest jedynym procesem w systemie, mimo że dzieli kernel z innymi kontenerami. Ta izolacja zapewnia bezpieczeństwo i przewidywalność bez overhead'u pełnej wirtualizacji.

Kontenery są efemeryczne - zaprojektowane do bycia tworzonymi, niszczonymi i odtwarzanymi. Dane wewnątrz kontenera są tracone po jego usunięciu, chyba że używasz wolumenów (volumes) lub bind mounts do persystencji. Stan aplikacji powinien być przechowywany poza kontenerem. To podejście "cattle not pets" ułatwia skalowanie i orkiestrację.

Przykład kodu:

# Utworzenie i uruchomienie kontenera
docker run -d --name moj-nginx -p 8080:80 nginx

# Wyświetlenie uruchomionych kontenerów
docker ps

# Wyświetlenie wszystkich kontenerów (także zatrzymanych)
docker ps -a

# Zatrzymanie kontenera
docker stop moj-nginx

# Uruchomienie zatrzymanego kontenera
docker start moj-nginx

# Usunięcie kontenera
docker rm moj-nginx

# Wykonanie komendy wewnątrz działającego kontenera
docker exec -it moj-nginx bash

Materiały

↑ Powrót na górę

Co to jest Docker Hub i do czego służy?

Odpowiedź w 30 sekund: Docker Hub to publiczny rejestr obrazów Docker - największe repozytorium kontenerów na świecie. Umożliwia przechowywanie, udostępnianie i pobieranie obrazów Docker, zarówno oficjalnych (np. nginx, postgres) jak i stworzonych przez społeczność czy prywatnych.

Odpowiedź w 2 minuty: Docker Hub to hostowany przez Docker rejestr kontenerów, działający jako centralne repozytorium dla obrazów Docker. Jest to domyślne miejsce, z którego Docker pobiera obrazy przy użyciu komendy docker pull. Można go porównać do GitHub dla kodu - tylko że zamiast repozytoriów kodu przechowuje gotowe do uruchomienia obrazy kontenerów.

Docker Hub oferuje miliony publicznych obrazów, w tym oficjalne obrazy dla popularnych technologii (Node.js, Python, PostgreSQL, Redis, etc.), weryfikowane przez Docker Inc. Możesz też tworzyć własne repozytoria - darmowe konto oferuje nieograniczoną liczbę publicznych repozytoriów oraz jedno prywatne. Płatne plany pozwalają na więcej prywatnych repozytoriów i zaawansowane funkcje zespołowe.

Docker Hub integruje się z GitHub i Bitbucket, umożliwiając automatyczne budowanie obrazów (Automated Builds) po każdym push do repozytorium kodu. Wspiera także webhooks do automatyzacji workflow CI/CD. Alternatywami dla Docker Hub są AWS ECR, Google Container Registry, Azure Container Registry czy prywatne rejestry oparte na Docker Registry.

Przykład kodu:

# Wyszukiwanie obrazów na Docker Hub
docker search postgres

# Pobranie oficjalnego obrazu
docker pull postgres:15

# Logowanie do Docker Hub
docker login

# Tagowanie obrazu przed wysłaniem
docker tag moja-aplikacja:latest username/moja-aplikacja:1.0

# Wysłanie obrazu do Docker Hub
docker push username/moja-aplikacja:1.0

# Pobranie prywatnego obrazu
docker pull username/prywatny-obraz:latest

Materiały

↑ Powrót na górę

Jak sprawdzić zainstalowaną wersję Dockera?

Odpowiedź w 30 sekund: Użyj komendy docker --version lub docker version aby sprawdzić wersję Dockera. Pierwsza komenda pokazuje skróconą informację, druga wyświetla szczegółowe informacje o kliencie i serwerze Docker (Docker Engine), w tym wersje API, Go runtime i system operacyjny.

Odpowiedź w 2 minuty: Docker składa się z dwóch głównych komponentów - klienta (Docker CLI) i serwera (Docker Engine/daemon). Komenda docker --version pokazuje tylko wersję zainstalowanego klienta w uproszczonej formie, np. "Docker version 24.0.7, build afdd53b". Jest to wystarczające do szybkiego sprawdzenia wersji.

Komenda docker version (bez podwójnego myślnika) wyświetla znacznie więcej informacji - oddzielnie dla klienta i serwera. Pokazuje wersję silnika Docker, wersję API (ważne przy kompatybilności), wersję Go użytą do kompilacji, system operacyjny i architekturę. Jeśli daemon nie jest uruchomiony lub jest problem z połączeniem, zobaczysz tylko informacje o kliencie.

Dodatkowo komenda docker info dostarcza szczegółowych informacji o całej instalacji Docker - liczbę kontenerów i obrazów, driver storage, ustawienia sieci, pluginy, informacje o Swarm i wiele więcej. Jest to przydatne przy diagnozowaniu problemów i weryfikacji konfiguracji systemu Docker.

Przykład kodu:

# Szybkie sprawdzenie wersji
docker --version
# Output: Docker version 24.0.7, build afdd53b

# Szczegółowe informacje o wersji klienta i serwera
docker version

# Kompleksowe informacje o instalacji Docker
docker info

# Sprawdzenie wersji Docker Compose
docker-compose --version

# Sprawdzenie wersji Docker Compose V2 (plugin)
docker compose version

Materiały

↑ Powrót na górę

Czym jest Dockerfile i jakie ma zastosowanie?

Odpowiedź w 30 sekund: Dockerfile to tekstowy plik zawierający instrukcje do automatycznego budowania obrazu Docker. Definiuje obraz bazowy, kroki instalacji zależności, kopiowanie plików aplikacji i konfigurację środowiska uruchomieniowego. Jest to "przepis" na stworzenie reprodukowalnego obrazu aplikacji.

Odpowiedź w 2 minuty: Dockerfile to plik tekstowy (bez rozszerzenia) zawierający serię instrukcji, które Docker wykonuje sekwencyjnie aby zbudować obraz. Każda instrukcja tworzy nową warstwę w obrazie, co umożliwia cache'owanie i optymalizację. Dockerfile zapewnia Infrastructure as Code - całe środowisko aplikacji jest zdefiniowane w kodzie, wersjonowane w Git i łatwe do reprodukcji.

Typowy Dockerfile zaczyna się od instrukcji FROM definiującej obraz bazowy (np. node:18, python:3.11-slim). Następnie używa instrukcji jak WORKDIR (ustawienie katalogu roboczego), COPY/ADD (kopiowanie plików), RUN (wykonanie komend podczas buildu jak instalacja pakietów), ENV (zmienne środowiskowe), EXPOSE (dokumentacja portów) i CMD/ENTRYPOINT (definicja domyślnej komendy uruchomieniowej).

Dockerfile promuje best practices - pozwala na wieloetapowe buildy (multi-stage builds) do optymalizacji rozmiaru, wykorzystuje .dockerignore do wykluczenia niepotrzebnych plików, umożliwia parametryzację przez ARG i budowanie dedykowanych obrazów dla różnych środowisk. Jest fundamentem automatyzacji CI/CD - ten sam Dockerfile tworzy identyczne obrazy na każdym etapie pipeline'u.

Przykład kodu:

# Obraz bazowy
FROM node:18-alpine

# Ustawienie katalogu roboczego
WORKDIR /app

# Kopiowanie plików package.json
COPY package*.json ./

# Instalacja zależności
RUN npm ci --only=production

# Kopiowanie kodu aplikacji
COPY . .

# Port na którym aplikacja nasłuchuje
EXPOSE 3000

# Komenda uruchomieniowa
CMD ["node", "server.js"]
# Zbudowanie obrazu z Dockerfile
docker build -t moja-app:1.0 .

# Build z konkretnym plikiem (nie Dockerfile)
docker build -f Dockerfile.prod -t moja-app:prod .

# Build z argumentami
docker build --build-arg NODE_ENV=production -t moja-app .

Materiały

↑ Powrót na górę

Jaka jest różnica między instrukcjami RUN, CMD i ENTRYPOINT?

Odpowiedź w 30 sekund: RUN wykonuje komendy podczas budowania obrazu i tworzy nową warstwę (np. instalacja pakietów). CMD definiuje domyślną komendę uruchamianą gdy startuje kontener, można ją nadpisać. ENTRYPOINT określa główny proces kontenera, którego nie można łatwo nadpisać - używa się go gdy kontener ma działać jak executable.

Odpowiedź w 2 minuty: RUN wykonuje się podczas fazy budowania obrazu (docker build). Każda instrukcja RUN tworzy nową warstwę w obrazie i jest cache'owana. Używamy RUN do instalacji pakietów, aktualizacji systemu, tworzenia katalogów czy kompilacji kodu. Przykład: RUN apt-get update && apt-get install -y curl. Można mieć wiele instrukcji RUN w Dockerfile.

CMD definiuje domyślną komendę i/lub parametry, które zostaną wykonane gdy kontener startuje. W Dockerfile może być tylko jedna instrukcja CMD (jeśli jest więcej, liczy się ostatnia). CMD łatwo nadpisać podając argumenty do docker run - na przykład docker run ubuntu ls nadpisze domyślne CMD. CMD jest idealne dla aplikacji, które mogą być uruchamiane na różne sposoby.

ENTRYPOINT określa główny wykonywalny proces kontenera. W przeciwieństwie do CMD, ENTRYPOINT nie jest łatwo nadpisywane - argumenty przekazane do docker run są dodawane do ENTRYPOINT, a nie go zastępują. ENTRYPOINT używamy gdy chcemy, aby kontener działał jak konkretne narzędzie/komenda. Często łączy się ENTRYPOINT (główna komenda) z CMD (domyślne parametry): ENTRYPOINT to "executable", CMD to "default parameters".

Przykład kodu:

# RUN - wykonuje się podczas budowania
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y python3
RUN mkdir /app

# CMD - domyślna komenda, łatwo nadpisywana
FROM node:18
WORKDIR /app
COPY . .
CMD ["npm", "start"]
# docker run app          -> uruchomi npm start
# docker run app npm test -> uruchomi npm test (CMD nadpisane)

# ENTRYPOINT - główny proces, trudny do nadpisania
FROM alpine
ENTRYPOINT ["ping"]
CMD ["localhost"]
# docker run app           -> ping localhost
# docker run app google.pl -> ping google.pl (CMD nadpisane, ENTRYPOINT nie)

# ENTRYPOINT + CMD razem - best practice
FROM python:3.11
ENTRYPOINT ["python", "app.py"]
CMD ["--help"]
# docker run app           -> python app.py --help
# docker run app --verbose -> python app.py --verbose

Materiały

↑ Powrót na górę

Czym różni się instrukcja COPY od ADD w Dockerfile?

Odpowiedź w 30 sekund: COPY to prosta instrukcja kopiująca pliki i katalogi z hosta do obrazu. ADD ma dodatkowe funkcje - automatycznie rozpakowuje archiwa tar i pobiera pliki z URL. W praktyce zaleca się używać COPY dla przejrzystości, a ADD tylko gdy potrzebna jest automatyczna ekstrakcja archiwów.

Odpowiedź w 2 minuty: COPY jest prostszą i bardziej przewidywalną instrukcją - kopiuje pliki i katalogi z lokalnego systemu plików (build context) do obrazu Docker. Składnia: COPY <źródło> <cel>. Robi dokładnie to co nazwa sugeruje, bez niespodzianek. Jest to zalecane podejście zgodnie z Docker best practices, ponieważ jest jasne i czytelne.

ADD ma rozszerzoną funkcjonalność - oprócz kopiowania plików również: (1) automatycznie rozpakowuje lokalne archiwa tar (tar, gzip, bzip2, xz) do katalogu docelowego, (2) może pobierać pliki bezpośrednio z URL. Przykład: ADD https://example.com/file.tar.gz /app/ pobierze i skopiuje plik (ale nie rozpakuje URL!). ADD archive.tar.gz /app/ skopiuje i automatycznie rozpakuje archiwum.

Te dodatkowe funkcje ADD mogą prowadzić do nieoczekiwanego zachowania i utrudniać debugowanie. Jeśli skopiujesz archiwum tar używając ADD, zostanie ono automatycznie rozpakowane - co może nie być intencją. Dlatego Docker zaleca: używaj COPY chyba że naprawdę potrzebujesz funkcji ADD (głównie automatycznej ekstrakcji tar). Dla URL lepiej użyć RUN z wget/curl dla większej kontroli.

Przykład kodu:

# COPY - zalecane dla zwykłego kopiowania plików
FROM node:18
WORKDIR /app

# Kopiowanie pojedynczego pliku
COPY package.json .

# Kopiowanie wielu plików
COPY package.json package-lock.json ./

# Kopiowanie całego katalogu
COPY src/ ./src/

# Kopiowanie wszystkiego (respektuje .dockerignore)
COPY . .

# ADD - używaj tylko gdy potrzebna jest specjalna funkcjonalność
FROM ubuntu:22.04

# Automatyczne rozpakowanie archiwum tar
ADD myarchive.tar.gz /app/
# Plik zostanie rozpakowany w /app/

# Pobieranie z URL (lepiej użyć RUN curl/wget)
ADD https://example.com/config.json /etc/app/

# Prawidłowe użycie dla URL - większa kontrola
RUN curl -L https://example.com/file.tar.gz | tar xz -C /app

Materiały

↑ Powrót na górę

Wolumeny i przechowywanie danych

Jaka jest różnica między wolumenem a bind mount?

Odpowiedź w 30 sekund: Wolumeny są w pełni zarządzane przez Docker i przechowywane w jego lokalizacji, podczas gdy bind mount mapuje dowolny katalog lub plik z systemu hosta do kontenera. Wolumeny są bardziej przenośne i bezpieczne, bind mount oferuje bezpośredni dostęp do systemu plików hosta.

Odpowiedź w 2 minuty: Główna różnica leży w zarządzaniu i lokalizacji. Wolumeny są przechowywane w części systemu plików zarządzanej przez Docker (/var/lib/docker/volumes/ na Linuxie) i można nimi zarządzać wyłącznie przez Docker CLI. Bind mount może wskazywać na dowolną ścieżkę w systemie hosta - może to być katalog projektu, plik konfiguracyjny, czy gniazdo Docker.

Wolumeny oferują izolację i przenośność - działają identycznie na różnych systemach operacyjnych, są odporne na przypadkowe usunięcia przez procesy hosta, obsługują sterowniki do przechowywania w chmurze i mogą być wypełnione początkową zawartością z obrazu Docker. Docker zarządza ich cyklem życia i oferuje dedykowane komendy do backupu i migracji.

Bind mount jest prostszy i bardziej bezpośredni - świetnie sprawdza się w development, gdzie chcemy aby zmiany w kodzie na hoście były natychmiast widoczne w kontenerze (live reload). Ma pełne prawa dostępu do zamontowanego katalogu i może go modyfikować, co jest potencjalnym ryzykiem bezpieczeństwa. Wymaga absolutnej ścieżki na hoście, co ogranicza przenośność między środowiskami.

Docker Compose domyślnie używa wolumenów dla nazwanych montowań i bind mount dla ścieżek względnych/absolutnych. W produkcji preferowane są wolumeny, w developmencie często bind mount dla wygody.

Odpowiedź w 2 minuty:

Przykład kodu:

# BIND MOUNT - mapowanie katalogu z hosta
# Składnia: -v /ścieżka/na/hoście:/ścieżka/w/kontenerze
docker run -d -v /home/user/app:/app nginx

# Bind mount tylko do odczytu
docker run -d -v /home/user/config:/config:ro nginx

# Bind mount pliku (np. docker.sock dla Docker-in-Docker)
docker run -v /var/run/docker.sock:/var/run/docker.sock docker

# WOLUMIN - zarządzany przez Docker
# Składnia: -v nazwa-wolumenu:/ścieżka/w/kontenerze
docker run -d -v moje-dane:/app/data nginx

# Porównanie w praktyce - development z bind mount
docker run -d \
  --name dev-app \
  -v $(pwd)/src:/app/src \
  -v $(pwd)/package.json:/app/package.json \
  node-app

# Porównanie w praktyce - produkcja z wolumenem
docker run -d \
  --name prod-app \
  -v app-data:/app/data \
  -v app-logs:/app/logs \
  node-app

# Nowa składnia --mount (bardziej czytelna)
# Bind mount
docker run -d \
  --mount type=bind,source=/home/user/app,target=/app \
  nginx

# Wolumin
docker run -d \
  --mount type=volume,source=moje-dane,target=/app/data \
  nginx

# Docker Compose - przykład użycia obu typów

Przykład docker-compose.yml:

version: '3.8'
services:
  web:
    image: nginx
    volumes:
      # Bind mount - development (kod źródłowy)
      - ./src:/app/src
      - ./nginx.conf:/etc/nginx/nginx.conf:ro

      # Wolumin - dane aplikacji
      - app-data:/app/data
      - static-files:/app/static

volumes:
  # Nazwane wolumeny (zarządzane przez Docker)
  app-data:
  static-files:

Materiały

↑ Powrót na górę

Docker - Część 3: Sieciowanie i Docker Compose

Jak zdefiniować wielokontenerową aplikację w docker-compose.yml?

Odpowiedź w 30 sekund: Plik docker-compose.yml definiuje usługi (services) jako główną sekcję, gdzie każda usługa reprezentuje kontener. Dodatkowo można zdefiniować sieci (networks), wolumeny (volumes) oraz zmienne środowiskowe. Każda usługa może określać obraz, porty, wolumeny, zależności i konfigurację środowiska.

Odpowiedź w 2 minuty: Struktura pliku docker-compose.yml opiera się na kilku głównych sekcjach. Sekcja services jest najważniejsza - każdy klucz w niej reprezentuje osobną usługę (kontener). Dla każdej usługi możesz określić image (gotowy obraz) lub build (zbuduj z Dockerfile), ports do mapowania portów, environment dla zmiennych środowiskowych, volumes do montowania danych oraz depends_on dla określenia zależności startowych.

Sekcja volumes na najwyższym poziomie definiuje nazwane wolumeny, które mogą być współdzielone między usługami. To lepsze rozwiązanie niż bind mounty dla danych produkcyjnych. Sekcja networks pozwala tworzyć niestandardowe sieci - domyślnie Compose tworzy jedną sieć dla wszystkich usług, ale możesz zdefiniować wiele sieci dla izolacji.

Compose wspiera również zaawansowane funkcjonalności: zmienne środowiskowe (przez ${VARIABLE} lub plik .env), extends do dziedziczenia konfiguracji, profiles do warunkowego uruchamiania usług, health checks, limity zasobów i więcej. Możesz używać anchors YAML (&, *) do redukcji duplikacji. Ważne jest także określenie version na początku pliku, choć w nowszych wersjach Compose to opcjonalne.

Przykład kodu:

# docker-compose.yml - kompletny przykład aplikacji e-commerce
version: '3.8'

services:
  # Frontend aplikacji
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - REACT_APP_API_URL=http://backend:5000
    depends_on:
      - backend
    networks:
      - frontend-network
    restart: unless-stopped

  # Backend API
  backend:
    build: ./backend
    ports:
      - "5000:5000"
    environment:
      - DATABASE_URL=postgresql://postgres:haslo@postgres:5432/sklep
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET=${JWT_SECRET}  # z pliku .env
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    volumes:
      - ./backend:/app
      - /app/node_modules
    networks:
      - frontend-network
      - backend-network
    restart: unless-stopped

  # Baza danych PostgreSQL
  postgres:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=sklep
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=haslo
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./init-db.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - backend-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  # Redis do cache'owania
  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data
    networks:
      - backend-network
    restart: unless-stopped

  # Nginx jako reverse proxy (opcjonalnie)
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - frontend
      - backend
    networks:
      - frontend-network
    profiles:
      - production

# Definiuj nazwane wolumeny
volumes:
  postgres-data:
    driver: local
  redis-data:
    driver: local

# Definiuj sieci
networks:
  frontend-network:
    driver: bridge
  backend-network:
    driver: bridge
# Plik .env w tym samym katalogu
JWT_SECRET=super-tajny-klucz-123

# Uruchom aplikację
docker compose up -d

# Uruchom z profilem production
docker compose --profile production up -d

# Skaluj backend do 3 instancji
docker compose up -d --scale backend=3

Materiały

↑ Powrót na górę

Optymalizacja i dobre praktyki

Jak debugować problemy z budowaniem obrazu?

Odpowiedź w 30 sekund: Debugowanie problemów z budowaniem obrazu można przeprowadzić przez: użycie docker build z flagą --progress=plain dla szczegółowych logów, uruchomienie kontenera z poprzedniej udanej warstwy używając docker run -it <layer-id> sh, inspekcję błędów instalacji pakietów, weryfikację kontekstu buildu i sprawdzenie .dockerignore.

Odpowiedź w 2 minuty: Debugowanie problemów z buildowaniem obrazów Docker wymaga systematycznego podejścia. Pierwszym krokiem jest uzyskanie szczegółowych informacji o procesie buildu używając docker build --progress=plain --no-cache, co wyświetli pełne wyjście każdej komendy bez używania cache. To często ujawnia konkretne błędy ukryte w domyślnym skróconym wyjściu BuildKit.

Kiedy build failuje na konkretnej warstwie, możesz uruchomić kontener z ostatniej udanej warstwy używając jej ID (widocznego w outputcie buildu) i ręcznie wykonać problematyczną komendę. Na przykład, jeśli RUN npm install failuje, uruchom docker run -it <previous-layer-id> sh i wykonaj npm install ręcznie, aby zobaczyć dokładne komunikaty błędów i testować rozwiązania.

Częste problemy to: zbyt duży build context (sprawdź co faktycznie jest wysyłane do Docker daemon używając docker build --no-cache --progress=plain .), brakujące zależności systemowe (szczególnie przy kompilacji natywnych modułów), problemy z uprawnieniami plików, niekompatybilne wersje pakietów, czy różnice między architekturami (np. build na Mac M1 a deploy na Linux x86).

Narzędzia pomocnicze to: docker build --target <stage-name> dla buildowania tylko konkretnego stage w multi-stage Dockerfile, docker buildx build --load dla zaawansowanego debugowania z BuildKit, oraz zewnętrzne lintry jak hadolint dla walidacji składni i best practices Dockerfile. Sprawdzaj również logi Docker daemon (journalctl -u docker na Linux) dla problemów na poziomie systemu.

Przykład kodu:

# Build z pełnymi logami i bez cache
docker build --progress=plain --no-cache -t myapp:debug .

# Build z wyświetleniem wszystkich intermediate containers
DOCKER_BUILDKIT=0 docker build -t myapp:debug .

# Zatrzymaj build na konkretnej instrukcji używając target
# W Dockerfile dodaj: FROM node:20 AS debug-point
docker build --target debug-point -t myapp:debug .

# Uruchom kontener z konkretnej warstwy dla manualnego debugowania
# Znajdź ID warstwy w output buildu, np: ---> a1b2c3d4e5f6
docker run -it --rm a1b2c3d4e5f6 sh

# Lub użyj tagu intermediate image jeśli build zawiódł
docker run -it --rm $(docker images -q -f "dangling=true" | head -1) sh

# Sprawdź rozmiar kontekstu buildu
docker build --no-cache -t test . 2>&1 | grep "Sending build context"

# Wylistuj pliki w kontekście buildu
tar -czf - . | tar -tzv | less

# Debugowanie multi-platform builds
docker buildx build --platform linux/amd64,linux/arm64 --progress=plain .

# Testowanie Dockerfile z hadolint (linter)
docker run --rm -i hadolint/hadolint < Dockerfile

# Inspekcja warstw gotowego obrazu
docker history myapp:latest --no-trunc

# Sprawdzenie co znajduje się w każdej warstwie
docker save myapp:latest | tar -x
# Techniki debugowania w Dockerfile

# Dodanie punktów breakpoint w multi-stage build
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
# Możesz zbudować tylko do tego punktu: docker build --target deps

FROM deps AS builder
COPY . .
# Debug: wypisz zmienne środowiskowe i zawartość katalogów
RUN echo "PATH=$PATH" && \
    echo "Node version:" && node --version && \
    echo "NPM version:" && npm --version && \
    echo "Files:" && ls -la
RUN npm run build

# Dodanie verbose logging dla debugowania
FROM builder AS debug
RUN npm run build -- --verbose 2>&1 | tee build.log

FROM alpine:latest AS production
# ... reszta konfiguracji
# Debugowanie problemów z siecią podczas buildu
docker build --network=host -t myapp:debug .

# Build z custom DNS (przydatne w corporate networks)
docker build --dns 8.8.8.8 -t myapp:debug .

# Sprawdzenie BUILD ARGS
docker build --build-arg NODE_ENV=development -t myapp:debug .
docker inspect myapp:debug | jq '.[0].Config.Labels'

# Debugowanie cache
docker build --target deps --cache-from myapp:latest -t myapp:deps .

# Eksport logów buildu do pliku
docker build -t myapp:debug . 2>&1 | tee build-log.txt

# Użycie dive do analizy warstw obrazu
dive myapp:latest
# instalacja: https://github.com/wagoodman/dive
# Skrypt pomocniczy do debugowania buildu
#!/bin/bash

set -e

echo "=== Sprawdzanie Docker daemon ==="
docker info

echo -e "\n=== Sprawdzanie rozmiaru build context ==="
du -sh .

echo -e "\n=== Pliki w build context (top 20) ==="
find . -type f -not -path '*/\.*' | head -20

echo -e "\n=== Weryfikacja .dockerignore ==="
if [ -f .dockerignore ]; then
    cat .dockerignore
else
    echo "UWAGA: Brak pliku .dockerignore!"
fi

echo -e "\n=== Uruchamianie buildu z pełnymi logami ==="
DOCKER_BUILDKIT=1 docker build \
    --progress=plain \
    --no-cache \
    -t myapp:debug \
    . 2>&1 | tee build-output.log

echo -e "\n=== Analiza warstw ==="
docker history myapp:debug --no-trunc

echo -e "\n=== Rozmiar obrazu ==="
docker images myapp:debug

Materiały

↑ Powrót na górę

Docker - Zarządzanie kontenerami i wolumeny

Jak uruchomić kontener w tle (tryb detached)?

Odpowiedź w 30 sekund: Aby uruchomić kontener w tle, należy użyć flagi -d lub --detach z poleceniem docker run. Kontener będzie działał w tle bez blokowania terminala, a Docker zwróci ID kontenera.

Odpowiedź w 2 minuty: Tryb detached (odłączony) pozwala na uruchomienie kontenera w tle, dzięki czemu terminal pozostaje dostępny do wykonywania innych poleceń. Jest to standardowy sposób uruchamiania kontenerów produkcyjnych, takich jak serwery webowe czy bazy danych.

Po uruchomieniu kontenera w trybie detached, Docker wyświetla ID kontenera i zwraca kontrolę nad terminalem. Kontener nadal działa w tle, a jego logi można podejrzeć używając docker logs. Można również połączyć się z działającym kontenerem za pomocą docker attach lub docker exec.

Dodatkowe flagi, takie jak --name pozwalają nadać kontenerowi czytelną nazwę, a --restart określa politykę restartowania kontenera w przypadku awarii lub restartu systemu.

Przykład kodu:

# Uruchomienie kontenera Nginx w tle
docker run -d nginx

# Uruchomienie z nazwą i mapowaniem portów
docker run -d --name moj-nginx -p 8080:80 nginx

# Uruchomienie z automatycznym restartem
docker run -d --name moj-nginx --restart unless-stopped -p 8080:80 nginx

# Sprawdzenie czy kontener działa
docker ps

Materiały

↑ Powrót na górę

Bezpieczeństwo

Jakie są najlepsze praktyki bezpieczeństwa dla obrazów Docker?

Odpowiedź w 30 sekund: Najlepsze praktyki bezpieczeństwa Docker obejmują: używanie oficjalnych i minimalnych obrazów bazowych, uruchamianie kontenerów jako użytkownik nieuprzywilejowany, regularne skanowanie obrazów pod kątem podatności, minimalizowanie powierzchni ataku przez usuwanie niepotrzebnych narzędzi i aktualizowanie zależności.

Odpowiedź w 2 minuty: Bezpieczeństwo obrazów Docker wymaga wielowarstwowego podejścia. Po pierwsze, zawsze używaj oficjalnych obrazów z Docker Hub lub weryfikowanych źródeł i preferuj minimalne obrazy bazowe jak Alpine Linux czy distroless, które zawierają tylko niezbędne komponenty, redukując powierzchnię ataku.

Kluczową praktyką jest uruchamianie procesów w kontenerze jako użytkownik nieuprzywilejowany zamiast root. W Dockerfile używaj instrukcji USER do zmiany użytkownika. Dodatkowo, regularnie skanuj obrazy narzędziami takimi jak Trivy, Clair czy Docker Scout, aby wykrywać znane podatności w zależnościach i bibliotekach systemowych.

Ważne jest również minimalizowanie zawartości obrazu - usuwaj niepotrzebne narzędzia, szczególnie kompilatory i narzędzia debugowania z obrazów produkcyjnych. Używaj multi-stage builds, aby oddzielić środowisko budowania od środowiska runtime. Pamiętaj o regularnym aktualizowaniu obrazów bazowych i zależności.

Inne istotne praktyki to: unikanie przechowywania sekretów w obrazach (używaj Docker secrets lub zmiennych środowiskowych), podpisywanie obrazów za pomocą Docker Content Trust, używanie read-only filesystem gdzie to możliwe, oraz ograniczanie capabilities Linuxa do niezbędnego minimum używając flag --cap-drop i --cap-add.

Przykład kodu:

# Użyj oficjalnego minimalnego obrazu bazowego
FROM node:20-alpine

# Utwórz użytkownika nieuprzywilejowanego
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001

# Ustaw katalog roboczy
WORKDIR /app

# Skopiuj pliki jako root
COPY package*.json ./

# Zainstaluj zależności
RUN npm ci --only=production && \
    npm cache clean --force

# Skopiuj kod aplikacji
COPY --chown=nodejs:nodejs . .

# Przełącz się na użytkownika nieuprzywilejowanego
USER nodejs

# Eksponuj port
EXPOSE 3000

# Uruchom aplikację
CMD ["node", "server.js"]

Materiały

↑ Powrót na górę

Chcesz więcej pytań?

Uzyskaj dostęp do 800+ pytań z 13 technologii - JavaScript, React, TypeScript, Node.js, SQL i więcej. Natychmiastowy dostęp na 30 dni.

Kup pełny dostęp za 49,99 zł