27. Реальный проект: Full-stack
Реальное production-приложение обычно состоит из нескольких сервисов: фронтенд, бэкенд API, база данных, кэш, прокси-сервер. Docker Compose позволяет описать всю эту инфраструктуру в одном файле и управлять ею как единым целым.
Архитектура приложения
Заголовок раздела «Архитектура приложения»Рассмотрим типичный full-stack стек:
Internet → Nginx (80/443) → React SPA (статика) → Node.js API (:3000) ↓ ↓ PostgreSQL Redis (данные) (кэш/сессии)Nginx выступает точкой входа: отдаёт статические файлы React и проксирует /api/* запросы к Node.js.
Структура проекта
Заголовок раздела «Структура проекта»myapp/├── docker-compose.yml├── .env├── nginx/│ ├── Dockerfile│ └── nginx.conf├── api/│ ├── Dockerfile│ └── src/└── web/ ├── Dockerfile └── src/Полный docker-compose.yml
Заголовок раздела «Полный docker-compose.yml»version: '3.9'
services: # ────────────────────────────────────── # PostgreSQL — основная база данных # ────────────────────────────────────── postgres: image: postgres:16-alpine container_name: myapp_postgres restart: unless-stopped environment: POSTGRES_DB: ${DB_NAME:-myapp} POSTGRES_USER: ${DB_USER:-myapp} POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - postgres_data:/var/lib/postgresql/data - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro networks: - backend healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-myapp}"] interval: 10s timeout: 5s retries: 5
# ────────────────────────────────────── # Redis — кэш и хранилище сессий # ────────────────────────────────────── redis: image: redis:7-alpine container_name: myapp_redis restart: unless-stopped command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD} volumes: - redis_data:/data networks: - backend healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 5
# ────────────────────────────────────── # Node.js API # ────────────────────────────────────── api: build: context: ./api dockerfile: Dockerfile target: production container_name: myapp_api restart: unless-stopped environment: NODE_ENV: production PORT: 3000 DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME} REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379 JWT_SECRET: ${JWT_SECRET} depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - backend - frontend healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 15s timeout: 5s retries: 3 start_period: 30s
# ────────────────────────────────────── # React frontend (собирается и отдаётся Nginx) # ────────────────────────────────────── web: build: context: ./web dockerfile: Dockerfile target: production container_name: myapp_web networks: - frontend
# ────────────────────────────────────── # Nginx — reverse proxy + статика # ────────────────────────────────────── nginx: build: context: ./nginx dockerfile: Dockerfile container_name: myapp_nginx restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./ssl:/etc/nginx/ssl:ro depends_on: api: condition: service_healthy networks: - frontend healthcheck: test: ["CMD", "nginx", "-t"] interval: 30s timeout: 5s retries: 3
networks: frontend: driver: bridge backend: driver: bridge internal: true # backend недоступен снаружи напрямую
volumes: postgres_data: driver: local redis_data: driver: localDockerfile для Node.js API
Заголовок раздела «Dockerfile для Node.js API»FROM node:20-alpine AS baseWORKDIR /appCOPY package*.json ./
FROM base AS depsRUN npm ci --only=production
FROM base AS buildRUN npm ciCOPY . .RUN npm run build
FROM base AS productionCOPY --from=deps /app/node_modules ./node_modulesCOPY --from=build /app/dist ./distUSER nodeEXPOSE 3000CMD ["node", "dist/index.js"]Nginx конфигурация
Заголовок раздела «Nginx конфигурация»events { worker_connections 1024; }
http { upstream api { server api:3000; keepalive 32; }
server { listen 80;
# Статика React location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; expires 1y; add_header Cache-Control "public, immutable"; }
# API proxy location /api/ { proxy_pass http://api/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Connection ""; }
# WebSocket поддержка location /ws { proxy_pass http://api; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }}Переменные окружения (.env)
Заголовок раздела «Переменные окружения (.env)»# .env (НЕ коммитить в Git!)DB_NAME=myappDB_USER=myappDB_PASSWORD=super_secret_db_passwordREDIS_PASSWORD=super_secret_redis_passwordJWT_SECRET=your_jwt_secret_here_min_32_charsУправление стеком
Заголовок раздела «Управление стеком»# Запуск всех сервисовdocker compose up -d
# Только зависимости (без приложения)docker compose up -d postgres redis
# Просмотр логовdocker compose logs -f api
# Проверка статусаdocker compose ps
# Выполнение команд внутри контейнераdocker compose exec api node scripts/migrate.jsdocker compose exec postgres psql -U myapp -d myapp
# Остановка без удаления данныхdocker compose stop
# Полная очистка (ОСТОРОЖНО: удаляет volumes)docker compose down -vПорядок запуска и зависимости
Заголовок раздела «Порядок запуска и зависимости»Docker Compose запускает сервисы с учётом depends_on. Граф зависимостей нашего стека:
postgres ─────┐ ├──→ api ──→ nginxredis ────────┘
web ──────────────→ nginxcondition: service_healthy гарантирует, что сервис не просто запущен, но прошёл health check.