Docker dla początkujących: praktyczny przewodnik

MJ

Mateusz JanotaCEO & Founder

Docker dla początkujących: praktyczny przewodnik od zera

Ten artykuł jest dla osoby, która nie chce kolejnej definicji konteneryzacji, tylko chce faktycznie odpalić pierwszy kontener, zbudować własny obraz i zrozumieć, co się dzieje po drodze.

Przejdziemy przez Dockera na przykładach: najpierw gotowy obraz, potem własna mała aplikacja Node.js, następnie Dockerfile, porty, zmienne środowiskowe, wolumeny, sieci, debugowanie i sprzątanie po sobie.

Co zbudujesz w tym poradniku

  • Uruchomisz pierwszy kontener i sprawdzisz jego logi.

  • Zrozumiesz różnicę między obrazem a kontenerem.

  • Napiszesz minimalną aplikację HTTP w Node.js.

  • Dodasz Dockerfile i .dockerignore.

  • Zbudujesz własny obraz i uruchomisz go lokalnie.

  • Podłączysz port, zmienne środowiskowe, wolumen i osobną sieć Dockera.

  • Zobaczysz, kiedy przejść z pojedynczego docker run do Docker Compose.

Wymagania i instalacja Dockera

W oryginalnym poradniku zaczynaliśmy od bardzo praktycznej rzeczy: gdzie wejść i co zainstalować. To nadal jest ważne, bo bez działającego Docker Engine reszta komend będzie tylko teorią.

macOS

  • Wejdź na Docker Desktop for Mac.

  • Pobierz wersję dla swojego procesora: Apple Silicon albo Intel.

  • Przenieś Docker.app do Applications i uruchom aplikację.

  • Poczekaj, aż w menu bar zobaczysz, że Docker Desktop działa. Dopiero wtedy terminalowe komendy docker ... zaczną odpowiadać.

bash
docker version
docker run hello-world

Windows

  • Wejdź na Docker Desktop for Windows.

  • Zainstaluj Docker Desktop z backendem WSL 2. Jeśli instalator poprosi o włączenie WSL, zrób restart i dokończ konfigurację.

  • Kod projektu trzymaj najlepiej w filesystemie WSL, np. ~/projects/app, a nie w C:\..., bo będzie szybciej i bliżej Linuxowego środowiska produkcyjnego.

  • Uruchom Docker Desktop, otwórz terminal WSL i sprawdź komendy.

bash
docker version
docker run hello-world

Linux / Ubuntu

Na serwerach i Linuxowych stacjach roboczych zwykle instalujesz Docker Engine, bez Docker Desktop. Przykład dla Ubuntu:

bash
sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Po instalacji sprawdź usługę:

bash
sudo systemctl status docker
sudo docker run hello-world

Jeśli chcesz uruchamiać Dockera bez sudo, dodaj użytkownika do grupy docker i zaloguj się ponownie:

bash
sudo usermod -aG docker $USER
newgrp docker
docker run hello-world

Uwaga: grupa docker daje bardzo wysokie uprawnienia na maszynie. Na własnym laptopie to normalny kompromis wygody, ale na współdzielonym serwerze trzeba to robić świadomie.

Jak sprawdzić, czy instalacja jest gotowa

Minimalny smoke test po instalacji wygląda tak:

bash
docker version
docker info
docker run --rm hello-world

Jeśli docker version pokazuje część Client, ale nie pokazuje Server, CLI jest zainstalowane, ale daemon Dockera nie działa albo użytkownik nie ma do niego dostępu.

Najpierw mentalny model: image, container, registry

W Dockerze najłatwiej pogubić się przez nazwy. W praktyce wystarczą trzy pojęcia:

  • Image / obraz - gotowy szablon systemu plików i komendy startowej. Obraz budujesz albo pobierasz z registry.

  • Container / kontener - uruchomiona instancja obrazu. Kontener jest procesem z własnym izolowanym środowiskiem.

  • Registry - miejsce przechowywania obrazów, np. Docker Hub, GitHub Container Registry albo prywatne registry w firmie.

Dobra analogia: obraz to przepis i przygotowana paczka, kontener to faktycznie uruchomiony proces z tej paczki.

Krok 1: uruchom pierwszy kontener

Zacznijmy od najprostszego testu. hello-world pobiera mały obraz i uruchamia kontener, który wypisuje komunikat, a potem kończy działanie.

bash
docker run hello-world

Teraz coś praktyczniejszego: uruchom Nginx i wystaw go na lokalny port 8080.

bash
docker run --name web-demo --rm -p 8080:80 nginx:alpine

Otwórz w przeglądarce http://localhost:8080. Powinieneś zobaczyć stronę startową Nginx.

Co oznaczają flagi?

  • --name web-demo nadaje kontenerowi czytelną nazwę.

  • --rm usuwa kontener automatycznie po zatrzymaniu.

  • -p 8080:80 mapuje port 80 z kontenera na port 8080 na hoście.

  • nginx:alpine to nazwa obrazu i tag. alpine oznacza lekką odmianę obrazu.

Krok 2: podstawowe komendy, których będziesz używać codziennie

W drugim terminalu sprawdź działający kontener:

bash
docker ps

Zobacz logi:

bash
docker logs web-demo

Wejdź do środka kontenera shellem:

bash
docker exec -it web-demo sh

Wyjdź z shella komendą exit, a następnie zatrzymaj kontener:

bash
docker stop web-demo

Po docker stop kontener zniknie, bo uruchomiliśmy go z --rm. Gdyby nie było --rm, zobaczyłbyś zatrzymane kontenery tak:

bash
docker ps -a

Krok 3: przygotuj małą aplikację do konteneryzacji

Zrobimy minimalną aplikację HTTP w Node.js bez frameworka. Chodzi o to, żeby zobaczyć cały przepływ Dockera bez ukrywania szczegółów za Next.js czy NestJS.

bash
mkdir docker-node-demo
cd docker-node-demo
npm init -y

Utwórz plik server.js:

server.js
const http = require("node:http");

const port = Number(process.env.PORT || 3000);
const message = process.env.MESSAGE || "Hello from Docker";

const server = http.createServer((req, res) => {
  res.writeHead(200, { "content-type": "application/json" });
  res.end(JSON.stringify({ message, path: req.url, hostname: process.env.HOSTNAME }));
});

server.listen(port, "0.0.0.0", () => {
  console.log(`Server listening on 0.0.0.0:${port}`);
});

Zmień scripts w package.json, żeby aplikację dało się wystartować przez npm start:

package.json
{
  "name": "docker-node-demo",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  }
}

Sprawdź lokalnie bez Dockera:

bash
npm start
# w drugim terminalu:
curl http://localhost:3000

Krok 4: napisz Dockerfile

Dockerfile to instrukcja budowy obrazu. Zacznijmy od prostej, czytelnej wersji.

Dockerfile
FROM node:22-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev

COPY server.js ./

ENV NODE_ENV=production
ENV PORT=3000

EXPOSE 3000

CMD ["npm", "start"]

Co robią poszczególne linie?

  • FROM node:22-alpine wybiera bazowy obraz z Node.js.

  • WORKDIR /app ustawia katalog roboczy wewnątrz kontenera.

  • COPY package*.json ./ kopiuje manifesty zależności przed kodem, żeby Docker mógł zapisywać w pamięci podręcznej warstwę instalacji.

  • RUN npm ci --omit=dev instaluje zależności produkcyjne w powtarzalny sposób.

  • COPY server.js ./ kopiuje kod aplikacji.

  • EXPOSE 3000 dokumentuje port aplikacji. Samo EXPOSE nie publikuje portu na hosta — od tego jest -p.

  • CMD ["npm", "start"] definiuje domyślną komendę startową kontenera.

Krok 5: dodaj .dockerignore

Bez .dockerignore Docker może wysłać do build context rzeczy, których nie chcesz w obrazie: node_modules, .git, pamięć podręczna, pliki lokalne albo sekrety.

.dockerignore
node_modules
npm-debug.log
.git
.gitignore
Dockerfile
.dockerignore
.env
.env.*
coverage
dist
.next

To ważne nie tylko dla rozmiaru obrazu. To też kwestia bezpieczeństwa: sekret, który trafi do obrazu, może potem zostać w historii warstw.

Krok 6: zbuduj obraz

Będąc w katalogu z Dockerfile, zbuduj obraz i nadaj mu tag:

bash
docker build -t docker-node-demo:1.0 .

Sprawdź, czy obraz istnieje lokalnie:

bash
docker images docker-node-demo

Jeśli build się wywali, najczęstsze powody to: brak pliku wskazanego w COPY, zbyt agresywny .dockerignore, brak lockfile albo komenda instalacji niedopasowana do package managera.

Krok 7: uruchom własny obraz

Uruchom aplikację z własnego obrazu:

bash
docker run --name node-demo --rm -p 3000:3000 docker-node-demo:1.0

W drugim terminalu sprawdź odpowiedź:

bash
curl http://localhost:3000
curl http://localhost:3000/health

Jeśli działa, zatrzymaj kontener:

bash
docker stop node-demo

Krok 8: zmienne środowiskowe

Konfiguracji nie wpisuj na sztywno do obrazu. Podawaj ją przy uruchomieniu kontenera.

bash
docker run --name node-demo --rm \
  -p 3000:3000 \
  -e MESSAGE='Configured at runtime' \
  docker-node-demo:1.0

Teraz curl http://localhost:3000 powinien zwrócić JSON z innym message. To jest podstawowy wzorzec: obraz jest ten sam, konfiguracja zależy od środowiska.

Sekretów nie wkładaj do Dockerfile przez ENV, nie commituj .env i nie buduj obrazu z sekretami w środku. Sekrety powinny trafić do środowiska uruchomieniowego: zmienne środowiskowe, menedżer sekretów, CI/CD albo platforma wdrożeniowa.

Krok 9: bind mount w trybie pracy lokalnej

Do nauki i pracy programistycznej przydaje się bind mount: katalog z hosta jest widoczny wewnątrz kontenera. Dla naszej prostej aplikacji możesz podmienić server.js bez budowania obrazu od nowa.

bash
docker run --name node-dev --rm \
  -p 3000:3000 \
  -v "$PWD/server.js:/app/server.js:ro" \
  docker-node-demo:1.0

:ro oznacza read-only. Kontener widzi plik, ale nie może go nadpisać. Dla kodu źródłowego to często bezpieczniejszy default.

Krok 10: named volume dla danych

Kontener jest ulotny. Jeśli usuniesz kontener, tracisz dane zapisane tylko w jego filesystemie. Dlatego bazy danych uruchamia się z wolumenem.

bash
docker volume create postgres-data

docker run --name postgres-demo --rm \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=app \
  -p 5432:5432 \
  -v postgres-data:/var/lib/postgresql/data \
  postgres:17-alpine

W tym przykładzie kontener można zatrzymać i uruchomić ponownie, a dane zostaną w wolumenie postgres-data. Usuń wolumen dopiero wtedy, gdy świadomie chcesz usunąć dane:

bash
docker volume ls
docker volume rm postgres-data

Krok 11: sieć Dockera zamiast localhost

Gdy dwa kontenery mają ze sobą rozmawiać, nie zakładaj localhost. localhost wewnątrz kontenera oznacza ten konkretny kontener, nie hosta i nie inny kontener.

Utwórz sieć i uruchom Postgresa w tej sieci:

bash
docker network create demo-net

docker run -d --name postgres-demo \
  --network demo-net \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=app \
  postgres:17-alpine

Teraz inne kontenery w tej samej sieci mogą użyć hosta postgres-demo. To jest dokładnie ten sam mechanizm, który później w Docker Compose działa po nazwie serwisu.

bash
docker run --rm --network demo-net postgres:17-alpine \
  pg_isready -h postgres-demo -U postgres

Posprzątaj:

bash
docker stop postgres-demo
docker network rm demo-net

Krok 12: debugowanie kontenera

Kiedy coś nie działa, nie zgaduj. Docker daje kilka prostych komend diagnostycznych.

bash
# działające kontenery
docker ps

# wszystkie kontenery, także zatrzymane
docker ps -a

# logi
docker logs <container-name>

# szczegóły konfiguracji kontenera
docker inspect <container-name>

# wejście do środka
docker exec -it <container-name> sh

# zużycie CPU/RAM
docker stats

Najczęstszy błąd początkujących z aplikacjami webowymi: aplikacja słucha tylko na 127.0.0.1 wewnątrz kontenera. W kontenerze serwer powinien zwykle słuchać na 0.0.0.0, inaczej port może być poprawnie zmapowany, ale aplikacja i tak nie odpowie z hosta.

Krok 13: sprzątanie obrazów, kontenerów i pamięć podręczna

Docker potrafi zająć dużo miejsca, jeśli budujesz dużo obrazów. Zacznij od bezpiecznego przeglądu:

bash
docker system df
docker images
docker ps -a
docker volume ls

Usuń zatrzymany kontener:

bash
docker rm <container-id-or-name>

Usuń obraz:

bash
docker rmi docker-node-demo:1.0

Usuń nieużywane rzeczy, ale uważaj na wolumeny, bo mogą zawierać dane baz danych:

bash
docker system prune
# bardziej agresywnie, razem z nieużywanymi obrazami:
docker system prune -a
# wolumeny tylko świadomie:
docker volume prune

Kiedy Docker Compose ma więcej sensu niż docker run

Pojedynczy docker run jest świetny do nauki i szybkiego testu. W realnym projekcie szybko pojawiają się jednak: aplikacja, baza danych, Redis, mailhog, proces roboczy, kolejka, emulator usługi plików, kolektor telemetrii. Wtedy jedna długa komenda przestaje być dokumentacją, a zaczyna być źródłem błędów.

W takim momencie przejdź do Compose. Zamiast ręcznie odpalać kilka komend, opisujesz usługi w compose.yaml i startujesz wszystko jedną komendą.

Następny krok: Docker Compose dla początkujących.

Docker w pracy z AI i nowoczesnym pracą programistyczną

Docker jest dziś szczególnie przydatny przy pracy z AI i agentami kodującymi. Agent nie powinien zgadywać, jaką wersję Node.js, Pythona, Playwrighta, pnpm, libvips czy Postgresa ma mieć lokalnie. Powinien dostać środowisko opisane jako kod.

  • Devcontainer daje zespołowi i agentom AI ten sam punkt startu.

  • Konteneryzowane zależności ograniczają sytuacje typu działa u mnie.

  • CI może uruchamiać testy integracyjne na takim samym zestawie usług jak lokalna praca programistyczna.

  • Nowy programista albo agent może wejść w projekt bez ręcznego instalowania połowy internetu.

Najczęstsze błędy początkujących

  1. Mylenie obrazu z kontenerem. Obraz budujesz, kontener uruchamiasz.

  2. Oczekiwanie, że EXPOSE samo wystawi port na hosta. Do tego potrzebujesz -p host:container.

  3. Trzymanie danych w filesystemie kontenera zamiast w wolumenie.

  4. Kopiowanie całego repo do obrazu bez .dockerignore.

  5. Wkładanie sekretów do Dockerfile albo budowanie obrazu z .env w środku.

  6. Uruchamianie aplikacji webowej na 127.0.0.1 zamiast 0.0.0.0 wewnątrz kontenera.

  7. Debugowanie przez zgadywanie zamiast docker logs, docker inspect, docker exec i docker ps -a.

Minimalna checklista do własnego projektu

  • Masz Dockerfile, który buduje się od zera na czystej maszynie.

  • Masz .dockerignore i nie kopiujesz sekretów ani lokalnych artefaktów.

  • Aplikacja słucha na 0.0.0.0, nie tylko na localhost.

  • Konfiguracja trafia przez zmienne środowiskowe w czasie uruchomienia, nie przez hardcode w obrazie.

  • Dane trwałe są w wolumenach albo zewnętrznych usługach.

  • README zawiera konkretne komendy: build, run, logs, sprzątanie.

  • Jeśli masz więcej niż jedną usługę, masz plan przejścia na Docker Compose.

Docker nie jest magią ani miniaturowym VPS-em. To sposób na spakowanie procesu aplikacji z jego środowiskiem tak, żeby dało się go powtarzalnie uruchomić lokalnie, w CI i na serwerze.

Jeśli po tym poradniku umiesz zbudować obraz, uruchomić kontener, wystawić port, przekazać zmienne środowiskowe, podpiąć wolumen, stworzyć sieć i przeczytać logi, masz solidne podstawy. Dopiero wtedy Docker Compose zaczyna być naturalnym kolejnym krokiem, a nie kolejną warstwą chaosu.

Masz projekt w głowie?

Napisz do nas

Porozmawiajmy o tym, jak możemy pomóc w realizacji Twoich pomysłów.

Zanek

Nie nadążasz za zmianami w świecie AI?

Pozwól, że weźmiemy to na siebie. Co tydzień destylujemy najważniejsze wydarzenia ze świata AI w skupiony, 5-minutowy przegląd - żebyś był na bieżąco bez szumu.

Dowiedz się więcej
Tygodnik AIonline
Wyselekcjonowane wiadomości AI do porannej kawy. Co tydzień.
Wyślij mi swój email, żeby się zapisać.