Czym jest Docker i dlaczego warto go znać?

Docker to platforma open-source, która umożliwia pakowanie aplikacji wraz ze wszystkimi jej zależnościami w lekkie, przenośne jednostki zwane kontenerami. W przeciwieństwie do tradycyjnych maszyn wirtualnych, kontenery nie emulują całego systemu operacyjnego – współdzielą jądro systemu hosta, co sprawia, że są znacznie bardziej wydajne i szybsze w uruchamianiu.

Dla programisty Docker rozwiązuje jeden z najbardziej klasycznych problemów w branży: "u mnie działa". Dzięki konteneryzacji aplikacja zachowuje się identycznie na komputerze dewelopera, w środowisku testowym i na serwerze produkcyjnym. To ogromna oszczędność czasu i nerwów.

Podstawowe pojęcia, które musisz znać

Zanim przejdziemy do praktyki, warto zapoznać się z kluczową terminologią:

  • Image (obraz) – szablon tylko do odczytu, który zawiera instrukcje tworzenia kontenera. Możemy myśleć o nim jak o przepisie kulinarnym.
  • Container (kontener) – działająca instancja obrazu. To właśnie tutaj żyje nasza aplikacja.
  • Dockerfile – plik tekstowy zawierający zestaw instrukcji do budowania obrazu Docker.
  • Docker Hub – publiczne repozytorium obrazów, z którego możemy pobierać gotowe rozwiązania (np. obrazy baz danych, serwerów WWW).
  • Docker Compose – narzędzie do definiowania i uruchamiania aplikacji składających się z wielu kontenerów.
  • Volume (wolumin) – mechanizm trwałego przechowywania danych poza cyklem życia kontenera.

Instalacja i pierwsze kroki

Instalacja Dockera jest prosta i dobrze udokumentowana. Na stronie docker.com znajdziesz instalatory dla Windows, macOS oraz różnych dystrybucji Linuxa. Zalecane jest pobranie Docker Desktop, które zawiera wszystkie niezbędne narzędzia, w tym Docker Compose i GUI do zarządzania kontenerami.

Po instalacji możesz przetestować środowisko klasycznym poleceniem:

docker run hello-world

Jeśli wszystko działa poprawnie, Docker pobierze obraz hello-world z Docker Hub i wyświetli stosowny komunikat potwierdzający poprawną instalację.

Twój pierwszy Dockerfile

Sercem każdego projektu dockeryzowanego jest plik Dockerfile. Zobaczmy, jak wygląda przykładowy plik dla prostej aplikacji Node.js:

# Wybieramy obraz bazowy
FROM node:20-alpine

# Ustawiamy katalog roboczy w kontenerze
WORKDIR /app

# Kopiujemy pliki z zależnościami
COPY package*.json ./

# Instalujemy zależności
RUN npm install

# Kopiujemy resztę kodu aplikacji
COPY . .

# Otwieramy port 3000
EXPOSE 3000

# Definiujemy polecenie uruchamiające aplikację
CMD ["node", "server.js"]

Każda linia Dockerfile to jedna warstwa obrazu. Docker inteligentnie cachuje warstwy – jeśli zmienisz tylko kod aplikacji (bez modyfikacji package.json), nie będzie potrzeby ponownej instalacji zależności. To znacząco przyspiesza czas budowania.

Aby zbudować obraz na podstawie tego pliku, uruchamiamy:

docker build -t moja-aplikacja:1.0 .

A następnie uruchamiamy kontener:

docker run -p 3000:3000 moja-aplikacja:1.0

Flaga -p 3000:3000 mapuje port 3000 kontenera na port 3000 naszego hosta, dzięki czemu możemy uzyskać dostęp do aplikacji przez przeglądarkę.

Docker Compose – zarządzanie wieloma kontenerami

Rzeczywiste aplikacje rzadko kiedy składają się z jednego kontenera. Typowy projekt webowy to przynajmniej: aplikacja backendowa, baza danych i może cache Redis. Docker Compose pozwala zdefiniować całe środowisko w jednym pliku docker-compose.yml:

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DB_HOST=postgres
      - REDIS_URL=redis://redis:6379
    depends_on:
      - postgres
      - redis
    volumes:
      - .:/app
      - /app/node_modules

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: secret
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  postgres_data:

Teraz wystarczy jedno polecenie, aby uruchomić całe środowisko:

docker-compose up -d

Flaga -d uruchamia kontenery w tle (tryb detached). Aby zatrzymać wszystko, używamy docker-compose down. Warto zauważyć, że Docker Compose automatycznie tworzy dedykowaną sieć dla wszystkich serwisów – dzięki temu kontenery mogą się komunikować między sobą używając nazw serwisów jako hostnames.

Woluminy i persystencja danych

Kontenery są z natury efemeryczne – po usunięciu kontenera wszystkie dane w nim przechowywane znikają. W powyższym przykładzie widzimy rozwiązanie tego problemu: woluminy.

Istnieją dwa główne typy:

  • Named volumes (np. postgres_data) – zarządzane przez Docker, idealne do przechowywania danych bazy danych.
  • Bind mounts (np. .:/app) – mapowanie katalogu z hosta do kontenera, świetne podczas developmentu, gdy chcemy, aby zmiany w kodzie były natychmiast widoczne w kontenerze.

Bind mount w przykładzie z Node.js sprawia, że nie musimy przebudowywać obrazu za każdym razem, gdy zmienimy kod – zmiany są od razu dostępne w kontenerze. W połączeniu z narzędziami takimi jak nodemon, uzyskujemy wygodne środowisko z automatycznym przeładowywaniem.

Przydatne polecenia Docker na co dzień

Oto zestaw poleceń, które będziesz używać najczęściej:

# Wyświetl listę działających kontenerów
docker ps

# Wyświetl wszystkie kontenery (również zatrzymane)
docker ps -a

# Wejdź do powłoki działającego kontenera
docker exec -it CONTAINER_ID bash

# Wyświetl logi kontenera
docker logs -f CONTAINER_ID

# Wyświetl listę obrazów
docker images

# Usuń zatrzymane kontenery, nieużywane sieci i obrazy
docker system prune

# Wyświetl statystyki zużycia zasobów
docker stats

Polecenie docker exec -it CONTAINER_ID bash jest szczególnie przydatne do debugowania – pozwala "wejść" do wnętrza działającego kontenera i sprawdzić jego stan bezpośrednio z poziomu terminala.

Dobre praktyki w pracy z Dockerem

Poznanie Dockera to jedno, ale efektywne i bezpieczne jego używanie to zupełnie inna kwestia. Oto kilka zasad, których warto przestrzegać:

  • Używaj lekkich obrazów bazowych – wersje alpine są znacznie mniejsze niż standardowe. Obraz node:20-alpine waży kilkadziesiąt MB, podczas gdy node:20 to kilkaset MB.
  • Nie przechowuj sekretów w Dockerfile – hasła i klucze API powinny być przekazywane przez zmienne środowiskowe lub dedykowane rozwiązania jak Docker Secrets czy zewnętrzne narzędzia (Vault, AWS Secrets Manager).
  • Stosuj plik .dockerignore – podobnie jak .gitignore, pozwala wykluczyć niepotrzebne pliki (np. node_modules, .git) z kontekstu budowania, co przyspiesza proces i zmniejsza rozmiar obrazu.
  • Jeden proces na kontener – zgodnie z filozofią Unix, każdy kontener powinien wykonywać jedno zadanie. To ułatwia skalowanie i debugowanie.
  • Taguj obrazy semantycznie – zamiast polegać na tagu latest, używaj konkretnych wersji (np. moja-app:2.1.0), co zapewnia przewidywalność wdrożeń.
  • Optymalizuj kolejność warstw – umieszczaj instrukcje, które zmieniają się rzadko (np. instalacja zależności), przed tymi, które zmieniają się często (kopiowanie kodu). Maksymalizuje to korzyści z cache.

Docker w pipeline CI/CD

Docker świetnie integruje się z narzędziami CI/CD takimi jak GitHub Actions, GitLab CI czy Jenkins. Typowy pipeline wygląda następująco: po pushu kodu do repozytorium, system CI buduje obraz Docker, uruchamia testy wewnątrz kontenera, a następnie publikuje gotowy obraz do rejestru (Docker Hub, AWS ECR, GitHub Container Registry). Serwer produkcyjny pobiera nowy obraz i zastępuje nim poprzednią wersję – zazwyczaj bez żadnego downtime'u dzięki mechanizmom rolling update.

Taki przepływ pracy gwarantuje, że środowisko testowe i produkcyjne są identyczne, eliminując całą klasę błędów związanych z różnicami w konfiguracji.

Podsumowanie

Docker stał się standardem w branży i znajomość konteneryzacji jest dziś praktycznie wymagana od każdego programisty pracującego z aplikacjami webowymi czy mikroserwisami. Zaczynając od prostego Dockerfile, przez Docker Compose, aż po integrację z pipeline'ami CI/CD – każdy krok przynosi realne korzyści w postaci szybszego developmentu, większej niezawodności i łatwiejszego wdrażania aplikacji.

Najlepszym sposobem nauki jest praktyka – weź swój bieżący projekt i spróbuj go skonteneryzować. Napotkasz trudności, ale każda z nich nauczy Cię czegoś nowego o tym, jak naprawdę działa Twoja aplikacja. Docker to inwestycja, która zwraca się bardzo szybko.