Docker Compose - Lokalne środowisko deweloperskie od A do Z
Docker Compose Lokalne
devopsDocker Compose - Lokalne środowisko deweloperskie od A do Z
Konfiguracja lokalnego środowiska deweloperskiego to jedno z pierwszych wyzwań, z jakimi mierzy się każdy zespół programistów. Nowy członek zespołu spędza często cały pierwszy dzień na instalowaniu baz danych, konfiguracji zmiennych środowiskowych i rozwiązywaniu konfliktów wersji. Docker Compose eliminuje te problemy, pozwalając uruchomić kompletne środowisko deweloperskie jednym poleceniem: docker compose up.
W tym artykule pokażemy krok po kroku, jak zbudować profesjonalne lokalne środowisko deweloperskie obejmujące frontend (Next.js), backend (.NET API), bazę danych (PostgreSQL), cache (Redis) oraz serwer e-mail (Mailhog). Omówimy składnię pliku docker-compose.yml, wolumeny, zmienne środowiskowe, sieci, health checki, hot reload, migracje bazodanowe oraz debugowanie w kontenerach.
Czym jest Docker Compose?#
Docker Compose to narzędzie do definiowania i uruchamiania wielokontenerowych aplikacji Docker. Zamiast ręcznie uruchamiać każdy kontener osobnym poleceniem docker run, definiujesz wszystkie usługi w jednym pliku YAML i zarządzasz nimi za pomocą prostych komend.
Kluczowe zalety Docker Compose:
- Jeden plik konfiguracyjny - cała infrastruktura opisana w
docker-compose.yml - Powtarzalność - identyczne środowisko na każdej maszynie deweloperskiej
- Izolacja - brak konfliktów między projektami, każdy ma własne kontenery
- Łatwy onboarding - nowy deweloper uruchamia
docker compose upi pracuje - Wersjonowanie - plik compose jest częścią repozytorium, zmiany są śledzone w Git
Składnia pliku docker-compose.yml#
Plik docker-compose.yml to serce Docker Compose. Oto podstawowa struktura:
# Wersja specyfikacji (opcjonalna od Compose v2)
version: "3.9"
services:
# Definicje usług (kontenerów)
nazwa-uslugi:
image: obraz:tag # Gotowy obraz z Docker Hub
build: ./sciezka # Lub budowanie z Dockerfile
ports:
- "host:kontener" # Mapowanie portów
volumes:
- ./local:/container # Mapowanie wolumenów
environment: # Zmienne środowiskowe
- KLUCZ=wartosc
env_file:
- .env # Plik ze zmiennymi
depends_on: # Zależności między usługami
- inna-usluga
networks: # Sieci
- moja-siec
restart: unless-stopped # Polityka restartu
volumes:
# Nazwane wolumeny (trwałe dane)
dane-postgres:
networks:
# Własne sieci
moja-siec:
driver: bridge
Każda sekcja services definiuje jeden kontener. Sekcja volumes deklaruje nazwane wolumeny do trwałego przechowywania danych, a networks definiuje sieci pozwalające kontenerom się komunikować.
Kompletne środowisko: Next.js + .NET API + PostgreSQL + Redis + Mailhog#
Przejdźmy do praktyki. Poniżej znajduje się kompletna konfiguracja środowiska deweloperskiego dla typowej aplikacji enterprise. Struktura projektu wygląda następująco:
projekt/
├── docker-compose.yml
├── docker-compose.override.yml
├── docker-compose.prod.yml
├── .env
├── .env.example
├── frontend/
│ ├── Dockerfile
│ ├── Dockerfile.dev
│ ├── package.json
│ └── src/
├── backend/
│ ├── Dockerfile
│ ├── Dockerfile.dev
│ ├── MyApi.sln
│ └── src/
└── database/
├── init.sql
└── migrations/
Główny plik docker-compose.yml#
version: "3.9"
services:
# ─── Frontend (Next.js) ────────────────────────────────
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- ./frontend/src:/app/src
- ./frontend/public:/app/public
- /app/node_modules
environment:
- NODE_ENV=development
- NEXT_PUBLIC_API_URL=http://localhost:5000/api
- WATCHPACK_POLLING=true
depends_on:
backend:
condition: service_healthy
networks:
- app-network
restart: unless-stopped
# ─── Backend (.NET API) ────────────────────────────────
backend:
build:
context: ./backend
dockerfile: Dockerfile.dev
ports:
- "5000:5000"
- "5001:5001"
volumes:
- ./backend/src:/app/src
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://+:5000
- ConnectionStrings__DefaultConnection=Host=postgres;Port=5432;Database=myapp_dev;Username=devuser;Password=devpass123
- Redis__ConnectionString=redis:6379
- Email__SmtpHost=mailhog
- Email__SmtpPort=1025
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
networks:
- app-network
restart: unless-stopped
# ─── PostgreSQL ────────────────────────────────────────
postgres:
image: postgres:16-alpine
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
- ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
environment:
- POSTGRES_DB=myapp_dev
- POSTGRES_USER=devuser
- POSTGRES_PASSWORD=devpass123
- POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C
healthcheck:
test: ["CMD-SHELL", "pg_isready -U devuser -d myapp_dev"]
interval: 5s
timeout: 5s
retries: 5
networks:
- app-network
restart: unless-stopped
# ─── Redis ─────────────────────────────────────────────
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
networks:
- app-network
restart: unless-stopped
# ─── Mailhog (serwer e-mail do testów) ────────────────
mailhog:
image: mailhog/mailhog:latest
ports:
- "1025:1025"
- "8025:8025"
networks:
- app-network
restart: unless-stopped
volumes:
postgres-data:
driver: local
redis-data:
driver: local
networks:
app-network:
driver: bridge
Ta konfiguracja uruchamia pięć usług, które razem tworzą kompletne środowisko deweloperskie. Frontend Next.js nasłuchuje na porcie 3000, backend .NET na porcie 5000, PostgreSQL na standardowym porcie 5432, Redis na 6379, a panel Mailhog jest dostępny pod adresem http://localhost:8025.
Wolumeny - trwałość danych#
Wolumeny w Docker Compose pełnią dwie kluczowe role: zapewniają trwałość danych między restartami kontenerów oraz umożliwiają synchronizację plików między hostem a kontenerem (niezbędne dla hot reload).
Nazwane wolumeny (dane trwałe)#
volumes:
postgres-data:
driver: local
Nazwane wolumeny (np. postgres-data) przechowują dane bazy danych niezależnie od cyklu życia kontenera. Usunięcie kontenera PostgreSQL poleceniem docker compose down nie usunie danych. Aby wyczyścić dane, trzeba jawnie użyć docker compose down -v.
Bind mounts (synchronizacja plików)#
volumes:
- ./frontend/src:/app/src # Kod źródłowy
- ./frontend/public:/app/public # Pliki statyczne
- /app/node_modules # Anonimowy wolumen - nie nadpisuj!
Bind mounts montują katalogi z hosta bezpośrednio w kontenerze. Zmiana pliku na hoście jest natychmiast widoczna w kontenerze, co jest fundamentem hot reload. Ważne jest użycie anonimowego wolumenu dla node_modules - bez tego katalog node_modules z hosta nadpisałby ten z kontenera, co prowadziłoby do problemów z zależnościami skompilowanymi dla innej platformy.
Zmienne środowiskowe#
Docker Compose obsługuje zmienne środowiskowe na kilka sposobów. Najlepsza praktyka to przechowywanie wartości w pliku .env:
Plik .env#
# .env (NIE commituj do Git!)
POSTGRES_DB=myapp_dev
POSTGRES_USER=devuser
POSTGRES_PASSWORD=devpass123
REDIS_PASSWORD=
NODE_ENV=development
API_PORT=5000
FRONTEND_PORT=3000
Plik .env.example#
# .env.example (commituj do Git jako szablon)
POSTGRES_DB=myapp_dev
POSTGRES_USER=devuser
POSTGRES_PASSWORD=CHANGE_ME
REDIS_PASSWORD=
NODE_ENV=development
API_PORT=5000
FRONTEND_PORT=3000
Użycie w docker-compose.yml#
services:
postgres:
image: postgres:16-alpine
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
ports:
- "${POSTGRES_PORT:-5432}:5432"
Składnia ${POSTGRES_PORT:-5432} ustawia domyślną wartość 5432, jeśli zmienna nie jest zdefiniowana. Plik .env powinien być dodany do .gitignore, a .env.example commitowany do repozytorium jako szablon dla nowych deweloperów.
Networking - komunikacja między kontenerami#
Docker Compose automatycznie tworzy sieć dla każdego projektu. Kontenery w ramach jednej sieci mogą się komunikować używając nazw usług jako hostów DNS.
networks:
app-network:
driver: bridge
W naszej konfiguracji backend łączy się z PostgreSQL używając hosta postgres (nazwa usługi):
Host=postgres;Port=5432;Database=myapp_dev
A frontend komunikuje się z backendem przez backend:5000 wewnątrz sieci Docker. Natomiast z przeglądarki (po stronie klienta) komunikacja odbywa się przez localhost:5000, ponieważ przeglądarka działa poza siecią Docker.
Izolacja sieci między projektami#
Każdy projekt Docker Compose ma własną izolowaną sieć. Kontenery z projektu A nie widzą kontenerów z projektu B, nawet jeśli używają tych samych portów wewnętrznych. Konflikty mogą wystąpić jedynie na poziomie portów hosta - dlatego warto parametryzować porty zmiennymi środowiskowymi.
Health checks - kontrola gotowości usług#
Health checki to mechanizm sprawdzania, czy usługa jest w pełni gotowa do obsługi żądań. Bez nich depends_on gwarantuje jedynie, że kontener został uruchomiony, a nie że usługa jest gotowa.
postgres:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U devuser -d myapp_dev"]
interval: 5s # Co ile sprawdzać
timeout: 5s # Maksymalny czas oczekiwania na odpowiedź
retries: 5 # Ile razy ponowić próbę przed uznaniem za unhealthy
start_period: 10s # Czas na rozruch (healthcheck nie oznacza unhealthy)
backend:
depends_on:
postgres:
condition: service_healthy # Czekaj aż PostgreSQL będzie healthy
redis:
condition: service_healthy # Czekaj aż Redis będzie healthy
Bez health checków backend mógłby próbować połączyć się z bazą danych, zanim ta zdąży się uruchomić. Dyrektywa condition: service_healthy w depends_on zapewnia prawidłową kolejność startu.
Pliki Compose: development vs production#
W praktyce potrzebujemy różnych konfiguracji dla środowiska deweloperskiego i produkcyjnego. Docker Compose obsługuje nakładanie plików (override), co pozwala na eleganckie rozdzielenie konfiguracji.
docker-compose.yml (bazowa konfiguracja)#
Zawiera wspólne definicje usług, sieci i wolumenów - to co jest identyczne dla obu środowisk.
docker-compose.override.yml (development)#
# docker-compose.override.yml
# Automatycznie ładowany razem z docker-compose.yml
version: "3.9"
services:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.dev
volumes:
- ./frontend/src:/app/src
- ./frontend/public:/app/public
- /app/node_modules
environment:
- NODE_ENV=development
- WATCHPACK_POLLING=true
backend:
build:
context: ./backend
dockerfile: Dockerfile.dev
volumes:
- ./backend/src:/app/src
environment:
- ASPNETCORE_ENVIRONMENT=Development
ports:
- "5000:5000"
- "5001:5001" # Port debuggera
postgres:
ports:
- "5432:5432" # Dostęp z hosta (np. pgAdmin, DBeaver)
redis:
ports:
- "6379:6379" # Dostęp z hosta (np. RedisInsight)
docker-compose.prod.yml (production)#
# docker-compose.prod.yml
version: "3.9"
services:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
args:
- NEXT_PUBLIC_API_URL=https://api.mojadomena.pl
environment:
- NODE_ENV=production
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
backend:
build:
context: ./backend
dockerfile: Dockerfile
environment:
- ASPNETCORE_ENVIRONMENT=Production
deploy:
resources:
limits:
cpus: "2.0"
memory: 1G
postgres:
environment:
- POSTGRES_PASSWORD=${PROD_DB_PASSWORD}
# Brak mapowania portów na hosta - dostęp tylko z sieci Docker
ports: []
redis:
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD} --maxmemory 512mb
ports: []
Uruchamianie:
# Development (domyślnie ładuje docker-compose.yml + docker-compose.override.yml)
docker compose up
# Production (jawne wskazanie plików)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
W środowisku produkcyjnym porty baz danych nie są eksponowane na host, obrazy są budowane z produkcyjnych Dockerfile (wieloetapowe buildy, optymalizacja rozmiaru), a zasoby kontenerów są limitowane.
Hot reload - automatyczne przeładowanie kodu#
Hot reload to kluczowa funkcja dla produktywności deweloperów. Każda zmiana w kodzie powinna być natychmiast widoczna w przeglądarce lub w logach serwera.
Hot reload dla Next.js#
# frontend/Dockerfile.dev
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# Next.js domyślnie obsługuje hot reload
# WATCHPACK_POLLING jest potrzebne na Windows/WSL2
ENV WATCHPACK_POLLING=true
EXPOSE 3000
CMD ["npm", "run", "dev"]
Kluczowe elementy to wolumen montujący kod źródłowy (./frontend/src:/app/src) oraz zmienna WATCHPACK_POLLING=true, która jest niezbędna na systemach Windows z WSL2, gdzie standardowe powiadomienia o zmianach plików (inotify) mogą nie działać poprawnie.
Hot reload dla .NET#
# backend/Dockerfile.dev
FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /app
COPY *.sln .
COPY src/**/*.csproj ./src/
RUN dotnet restore
COPY . .
EXPOSE 5000 5001
CMD ["dotnet", "watch", "run", "--project", "src/MyApi", "--urls", "http://+:5000"]
Polecenie dotnet watch run automatycznie rekompiluje i restartuje aplikację przy każdej zmianie pliku .cs. Wolumen montujący ./backend/src:/app/src zapewnia synchronizację plików.
Migracje bazodanowe w kontenerach#
Migracje bazodanowe powinny być uruchamiane automatycznie przy starcie kontenera lub jako osobna usługa.
Podejście 1: Skrypt inicjalizacyjny PostgreSQL#
-- database/init.sql
-- Uruchamiany automatycznie przy pierwszym starcie kontenera PostgreSQL
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- Tabele początkowe
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS audit_log (
id BIGSERIAL PRIMARY KEY,
user_id UUID REFERENCES users(id),
action VARCHAR(100) NOT NULL,
details JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
Podejście 2: Osobna usługa migracji#
services:
migrations:
build:
context: ./backend
dockerfile: Dockerfile.dev
command: ["dotnet", "ef", "database", "update", "--project", "src/MyApi"]
environment:
- ConnectionStrings__DefaultConnection=Host=postgres;Port=5432;Database=myapp_dev;Username=devuser;Password=devpass123
depends_on:
postgres:
condition: service_healthy
networks:
- app-network
restart: "no" # Uruchom raz i zakończ
Osobna usługa migracji uruchamia się raz, wykonuje migracje i kończy pracę. Dzięki restart: "no" kontener nie jest restartowany po zakończeniu.
Podejście 3: Entrypoint z migracjami#
# backend/entrypoint.sh
#!/bin/bash
set -e
echo "Uruchamianie migracji..."
dotnet ef database update --project src/MyApi
echo "Uruchamianie aplikacji..."
exec dotnet watch run --project src/MyApi --urls "http://+:5000"
backend:
entrypoint: ["/bin/bash", "./entrypoint.sh"]
To podejście uruchamia migracje automatycznie przed startem aplikacji. Jest wygodne, ale wydłuża czas uruchamiania kontenera.
Debugowanie w kontenerach#
Debugowanie aplikacji działających w kontenerach wymaga odpowiedniej konfiguracji, ale nie jest trudne.
Debugowanie .NET w kontenerze (Visual Studio Code)#
Dodaj konfigurację debuggera w .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Docker .NET Attach",
"type": "docker",
"request": "attach",
"platform": "netCore",
"sourceFileMap": {
"/app/src": "${workspaceFolder}/backend/src"
}
}
]
}
Debugowanie Next.js w kontenerze#
Dodaj flagę debugowania do polecenia startowego:
frontend:
command: ["node", "--inspect=0.0.0.0:9229", "node_modules/.bin/next", "dev"]
ports:
- "3000:3000"
- "9229:9229" # Port debuggera Node.js
Konfiguracja VS Code:
{
"version": "0.2.0",
"configurations": [
{
"name": "Docker Next.js Attach",
"type": "node",
"request": "attach",
"port": 9229,
"remoteRoot": "/app",
"localRoot": "${workspaceFolder}/frontend"
}
]
}
Logi i diagnoza problemów#
# Logi konkretnej usługi
docker compose logs backend
# Logi w czasie rzeczywistym (follow)
docker compose logs -f backend postgres
# Logi z ostatnich 100 linii
docker compose logs --tail=100 backend
# Wejście do kontenera (interaktywna powłoka)
docker compose exec backend bash
# Sprawdzenie stanu kontenerów
docker compose ps
# Sprawdzenie health checków
docker inspect --format='{{.State.Health.Status}}' projekt-postgres-1
Przydatne polecenia Docker Compose#
Oto lista najczęściej używanych komend, które przyspieszą codzienną pracę:
# ─── Zarządzanie cyklem życia ───────────────────────────
docker compose up # Uruchom wszystko
docker compose up -d # Uruchom w tle (detached)
docker compose up --build # Przebuduj obrazy i uruchom
docker compose up backend postgres # Uruchom tylko wybrane usługi
docker compose down # Zatrzymaj i usuń kontenery
docker compose down -v # Zatrzymaj i usuń kontenery + wolumeny
docker compose stop # Zatrzymaj kontenery (bez usuwania)
docker compose start # Uruchom zatrzymane kontenery
docker compose restart backend # Restart konkretnej usługi
# ─── Budowanie ──────────────────────────────────────────
docker compose build # Zbuduj wszystkie obrazy
docker compose build --no-cache # Zbuduj bez cache
docker compose build backend # Zbuduj konkretny obraz
# ─── Diagnostyka ────────────────────────────────────────
docker compose ps # Status kontenerów
docker compose logs -f # Logi wszystkich usług (follow)
docker compose logs -f backend # Logi konkretnej usługi
docker compose top # Procesy w kontenerach
# ─── Interakcja z kontenerami ───────────────────────────
docker compose exec postgres psql -U devuser -d myapp_dev # Konsola PostgreSQL
docker compose exec redis redis-cli # Konsola Redis
docker compose exec backend dotnet ef migrations add Init # Nowa migracja
# ─── Czyszczenie ────────────────────────────────────────
docker compose down -v --rmi all # Usuń wszystko (kontenery, wolumeny, obrazy)
docker system prune -a # Wyczyść nieużywane zasoby Docker
docker volume prune # Wyczyść nieużywane wolumeny
Typowe problemy i rozwiązania#
Port już zajęty#
# Sprawdź, co zajmuje port
# Linux/Mac:
lsof -i :5432
# Windows:
netstat -ano | findstr :5432
# Zmień port w .env
POSTGRES_PORT=5433
Wolne wolumeny na Windows/WSL2#
Na systemie Windows z WSL2 bind mounts mogą być bardzo wolne. Rozwiązania:
# 1. Użyj delegated/cached (Docker Desktop)
volumes:
- ./frontend/src:/app/src:delegated
# 2. Ogranicz synchronizowane katalogi
volumes:
- ./frontend/src:/app/src # Tylko kod źródłowy
- /app/node_modules # node_modules w kontenerze
- /app/.next # Build cache w kontenerze
Kontener ciągle się restartuje#
# Sprawdź logi
docker compose logs backend
# Sprawdź health check
docker inspect projekt-backend-1 | jq '.[0].State.Health'
# Wejdź do kontenera i debuguj ręcznie
docker compose run --entrypoint bash backend
Podsumowanie#
Docker Compose to niezbędne narzędzie w arsenale każdego zespołu deweloperskiego. Pozwala na:
- Standaryzację środowisk - koniec z "u mnie działa"
- Szybki onboarding - nowy deweloper zaczyna pracę w minuty, nie godziny
- Izolację projektów - wiele projektów na jednej maszynie bez konfliktów
- Automatyzację - migracje, seed danych i health checki działają automatycznie
- Produktywność - hot reload, debugowanie i logi w jednym miejscu
Dobrze skonfigurowane środowisko Docker Compose to inwestycja, która zwraca się przy każdym nowym członku zespołu i każdej zmianie w infrastrukturze. Zacznij od prostej konfiguracji i rozbudowuj ją stopniowo - Docker Compose wspiera iteracyjne podejście do budowania infrastruktury.
Potrzebujesz profesjonalnego środowiska deweloperskiego?#
W MDS Software Solutions Group pomagamy zespołom wdrażać Docker Compose w codziennej pracy. Oferujemy:
- Konfigurację lokalnych środowisk deweloperskich
- Optymalizację istniejących plików Compose
- Wdrożenie CI/CD z kontenerami Docker
- Szkolenia dla zespołów deweloperskich
- Migrację projektów do architektury kontenerowej
Skontaktuj się z nami, aby przyspieszyć pracę Twojego zespołu!
Zespół ekspertów programistycznych specjalizujących się w nowoczesnych technologiach webowych.