Docker Compose dla początkujących: praktyczny przewodnik

MJ

Mateusz JanotaCEO & Founder

Docker Compose dla początkujących: od jednej komendy do całego środowiska

Docker uczy uruchamiania pojedynczych kontenerów. Docker Compose rozwiązuje następny problem: realny projekt prawie nigdy nie składa się z jednego procesu. Masz aplikację, bazę danych, Redis, proces roboczy, kolejkę, narzędzia programistyczne i czasem observability. Compose pozwala opisać to w jednym pliku i uruchomić jedną komendą.

Ten poradnik jest praktyczny. Zbudujemy lokalne środowisko: aplikacja Node.js, PostgreSQL i Redis. Po drodze przejdziemy przez compose.yaml, porty, sieci, wolumeny, .env, kontrole gotowości, logi, debugowanie i sprzątanie.

Jeżeli Docker jako taki jest jeszcze niejasny, najpierw przeczytaj Docker dla początkujących. Ten artykuł zakłada, że wiesz już czym jest obraz, kontener, port i wolumen.

Wymagania i instalacja Docker Compose

W nowych instalacjach Compose jest wtyczką Docker CLI, więc komenda ma formę docker compose, a nie stare docker-compose. Stary binarny docker-compose v1 jest przestarzałe.

macOS i Windows

  • Zainstaluj Docker Desktop.

  • Docker Compose jest dołączony do Docker Desktop.

  • Po uruchomieniu Docker Desktop sprawdź wersję w terminalu.

bash
docker version
docker compose version

Linux / Ubuntu

Jeśli instalowałeś Docker Engine według aktualnej instrukcji Dockera, doinstaluj wtyczkę Compose:

bash
sudo apt-get update
sudo apt-get install -y docker-compose-plugin
docker compose version

Jeśli działa tylko docker-compose, masz prawdopodobnie starą wersję i warto ją wymienić na plugin.

Co zbudujemy

  • app - mała aplikacja HTTP w Node.js.

  • db - PostgreSQL z trwałym wolumenem.

  • redis - Redis jako przykład pamięci podręcznej albo kolejki / zależności środowiska uruchomieniowego.

  • Jedna prywatna sieć Compose, w której usługi widzą się po nazwach.

  • Kontrola gotowości dla Postgresa i warunek startu aplikacji po gotowości bazy.

Krok 1: przygotuj katalog projektu

bash
mkdir compose-demo
cd compose-demo
mkdir app

W tym poradniku trzymamy aplikację w katalogu app, a plik Compose w głównym katalogu projektu.

Krok 2: napisz minimalną aplikację Node.js

Utwórz app/package.json:

app/package.json
{
  "name": "compose-demo-app",
  "version": "1.0.0",
  "private": true,
  "scripts": { "start": "node server.js" },
  "dependencies": {
    "pg": "^8.13.0",
    "redis": "^4.7.0"
  }
}

Utwórz app/server.js:

app/server.js
const http = require("node:http");
const { Client } = require("pg");
const { createClient } = require("redis");

const port = Number(process.env.PORT || 3000);
const databaseUrl = process.env.DATABASE_URL;
const redisUrl = process.env.REDIS_URL;

async function checkPostgres() {
  const client = new Client({ connectionString: databaseUrl });
  await client.connect();
  const result = await client.query("select now() as now");
  await client.end();
  return result.rows[0].now;
}

async function checkRedis() {
  const client = createClient({ url: redisUrl });
  await client.connect();
  await client.set("compose-demo:last-check", new Date().toISOString());
  const value = await client.get("compose-demo:last-check");
  await client.disconnect();
  return value;
}

const server = http.createServer(async (_req, res) => {
  try {
    const [postgresTime, redisValue] = await Promise.all([checkPostgres(), checkRedis()]);
    res.writeHead(200, { "content-type": "application/json" });
    res.end(JSON.stringify({ ok: true, postgresTime, redisValue }));
  } catch (error) {
    res.writeHead(500, { "content-type": "application/json" });
    res.end(JSON.stringify({ ok: false, error: error.message }));
  }
});

server.listen(port, "0.0.0.0", () => {
  console.log("App listening on 0.0.0.0:" + port);
});

Krok 3: dodaj Dockerfile aplikacji

Compose może budować obraz aplikacji z lokalnego Dockerfile. Utwórz app/Dockerfile:

app/Dockerfile
FROM node:22-alpine

WORKDIR /app

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

COPY server.js ./

ENV NODE_ENV=production
ENV PORT=3000

EXPOSE 3000

CMD ["npm", "start"]

Dodaj też app/.dockerignore:

app/.dockerignore
node_modules
npm-debug.log
.env
.git
coverage
dist
.next

Krok 4: pierwszy compose.yaml

Utwórz plik compose.yaml w głównym katalogu projektu:

compose.yaml
services:
  app:
    build:
      context: ./app
    ports:
      - "3000:3000"
    environment:
      PORT: "3000"
      DATABASE_URL: postgres://app:app@db:5432/app
      REDIS_URL: redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app
      POSTGRES_DB: app
    ports:
      - "5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 3s
      retries: 10

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

volumes:
  postgres-data:

To jest serce Compose: trzy usługi, jedna sieć tworzona automatycznie i jeden nazwany wolumen dla danych Postgresa.

Krok 5: uruchom całe środowisko

bash
docker compose up --build

Pierwszy start zbuduje obraz aplikacji i pobierze obrazy Postgresa oraz Redisa. Po starcie sprawdź aplikację:

bash
curl http://localhost:3000

Odpowiedź powinna zawierać ok: true, czas z Postgresa i wartość zapisaną w Redisie.

bash
docker compose up --build -d
docker compose ps

Krok 6: zrozum nazwy hostów w Compose

W Compose usługi w tej samej sieci widzą się po nazwie serwisu. Dlatego aplikacja używa hostów db i redis, a nie localhost.

  • Z komputera hosta Postgres jest dostępny przez localhost:5432, bo wystawiliśmy port.

  • Z kontenera app Postgres jest dostępny przez db:5432.

  • Z kontenera app Redis jest dostępny przez redis:6379.

To jeden z najczęstszych błędów początkujących: wpisują localhost w connection stringu aplikacji działającej w kontenerze i dziwią się, że baza nie odpowiada.

Krok 7: logi, shell i debugowanie

bash
docker compose ps
docker compose logs app
docker compose logs -f db
docker compose exec app sh
docker compose exec db psql -U app -d app
docker compose exec redis redis-cli ping

Jeśli aplikacja nie startuje, zacznij od docker compose ps i docker compose logs app. Nie zgaduj.

Krok 8: zmienne środowiskowe i plik .env

W demo wpisaliśmy hasło Postgresa w compose.yaml, bo to lokalny przykład. W projekcie lepiej przenieść konfigurację do .env, a prawdziwych sekretów nie commitować.

.env
POSTGRES_USER=app
POSTGRES_PASSWORD=app
POSTGRES_DB=app
APP_PORT=3000

Następnie w Compose możesz użyć interpolacji:

compose.yaml
services:
  app:
    ports:
      - "${APP_PORT}:3000"
    environment:
      DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}

  db:
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}

Do repo dodaj raczej .env.example, a nie prawdziwe .env z sekretami.

Krok 9: wolumeny i reset bazy

Dane Postgresa są w named volume postgres-data. Dzięki temu restart kontenera nie kasuje bazy.

bash
docker compose down
docker compose up -d
# dane nadal istnieją

Jeśli chcesz świadomie wyczyścić bazę, usuń zestaw usług razem z wolumenami:

bash
docker compose down -v

Uwaga: down -v usuwa dane.

Krok 10: rebuild, restart i praca nad kodem

bash
docker compose build app
docker compose up -d app

Po zmianie samej konfiguracji Compose zwykle wystarczy:

bash
docker compose up -d

Do pracy programistycznej można dodać bind mount kodu i osobny target dev, ale najpierw warto mieć działający prosty wariant.

Krok 11: profiles, czyli usługi opcjonalne

Compose profiles przydają się, gdy część usług nie ma startować zawsze.

compose.yaml
services:
  adminer:
    image: adminer:latest
    profiles: ["tools"]
    ports:
      - "8080:8080"
bash
docker compose --profile tools up -d

Krok 12: Compose w pracy z AI i agentami

Compose jest świetny dla zespołów pracujących z AI, bo agent kodujący nie musi zgadywać, jak uruchomić bazę, pamięć podręczną, kolejkę czy lokalny emulator. Ma jeden plik i jedną komendę.

  • Nowa osoba w zespole odpala docker compose up -d zamiast instalować Postgresa i Redisa globalnie.

  • Agent AI może uruchamiać testy integracyjne w takim samym środowisku jak programista.

  • CI może użyć tego samego modelu zależności, tylko z innymi sekretami i bez wystawiania zbędnych portów.

Krok 13: kiedy Compose przestaje wystarczać

Docker Compose nie jest Kubernetesem. Jest świetny do lokalnej pracy programistycznej, prostych środowisk testowych i małych wdrożeń, ale nie rozwiązuje sam z siebie skalowania, wdrożeń kroczących, automatycznego skalowania, zarządzania sekretami czy zaawansowanej orkiestracji.

  • Do lokalnego zestawu usług i CI - Compose jest bardzo dobry.

  • Do prostego VPS-a - Compose bywa wystarczający, jeśli świadomie obsłużysz backupy, update’y i monitoring.

  • Do większego systemu produkcyjnego - zwykle wchodzą platformy managed, Kubernetes, Nomad albo PaaS.

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

  1. Używanie localhost między kontenerami zamiast nazw usług, np. db albo redis.

  2. Trzymanie danych bazy bez wolumenu i utrata danych po re-create kontenera.

  3. Commitowanie .env z prawdziwymi sekretami.

  4. Brak kontroli gotowości dla bazy i start aplikacji zanim baza jest gotowa.

  5. Wystawianie na hosta portów, które nie muszą być dostępne poza siecią Compose.

  6. Mylenie docker compose down z docker compose down -v. Druga komenda usuwa wolumeny.

  7. Traktowanie Compose jako produkcyjnego orchestratora do wszystkiego.

Minimalna checklista dobrego compose.yaml

  • Każda usługa ma czytelną nazwę.

  • Aplikacja używa nazw usług jako hostów, nie localhost.

  • Bazy danych mają named volumes.

  • Sekrety nie są zapisane na stałe w obrazie ani w repo.

  • Usługi zależne od bazy mają kontrolę gotowości albo logikę ponawiania prób.

  • Wystawiasz tylko te porty, które faktycznie muszą być dostępne z hosta.

  • README opisuje up, down, logs, exec, reset danych i typowe błędy.

Docker Compose jest najlepszym następnym krokiem po podstawach Dockera. Zamiast pamiętać kilka długich komend docker run, opisujesz środowisko w pliku, który rozumie programista, agent AI i CI.

Najważniejsze rzeczy do zapamiętania: usługi komunikują się po nazwach, dane trwałe trzymaj w wolumenach, konfigurację podawaj przez zmienne środowiskowe, logi sprawdzaj przez docker compose logs, a down -v traktuj jak świadomy reset danych.

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ć.