Развертывание многоконтейнерных веб-приложений с помощью Docker Compose и Nginx

Введение

Современные веб-приложения редко работают как единый монолитный процесс. Вместо этого они состоят из нескольких сотрудничающих сервисов: интерфейса, API, базы данных, кэша, очереди сообщений и, возможно, бегуна. Ручное управление жизненным циклом каждого контейнера, их сетей и их зависимостей быстро становится подверженным ошибкам и трудоемким. Docker Compose и Nginx вместе обеспечивают проверенное на практике, удобное для производства решение для определения и развертывания этих многоконтейнерных систем с минимальным трением. Docker Compose позволяет описать весь стек приложений в одном файле YAML, в то время как Nginx действует как надежный обратный прокси и балансировщик нагрузки, маршрутизация трафика в соответствующую службу. В этом расширенном руководстве мы пройдем через реалистичную настройку, углубимся в детали конфигурации, рассмотрим лучшие практики безопасности и покажем вам, как масштабироваться от разработки до производства.

Предпосылки

Перед погружением убедитесь, что на вашей системе установлено следующее:

Необязательно иметь доменное имя, указывающее на ваш сервер, если вы планируете следовать разделу конфигурации SSL.

Docker Compose в глубине

Docker Compose - это не просто обертка с «запуском докера». Это декларативный инструмент, который позволяет определять услуги, сети, объемы, переменные среды, политики перезапуска и проверки здоровья в одном файле под названием . Спецификация Compose (теперь часть Compose Specification) стандартизирует описание многоконтейнерных приложений, делая вашу настройку портативной для различных трубопроводов CI / CD и облачных провайдеров.

Услуги

Каждое изображение контейнера, его порты, объемы, среда и зависимости определяются под ключом . Сервисы могут общаться друг с другом через выделенную мостовую сеть, которую Compose создает автоматически. Это означает, что вам никогда не нужно открывать порты хоста для межсервисной связи — только обратный прокси-сервер должен подвергать порты внешнему миру.

Сети

По умолчанию Compose создает единую сеть для всех сервисов, предоставляя им обнаружение сервисов на основе DNS с использованием имени службы в качестве имени хоста. Например, служба под названием может быть достигнута другими службами просто как . Вы также можете определить пользовательские сети для изоляции — например, поместив только обратный прокси-сервер в общедоступную сеть и сохраняя контейнеры баз данных во внутренней сети.

networks:
 frontend:
 backend:
services:
 nginx:
 networks:
 - frontend
 app:
 networks:
 - frontend
 - backend
 db:
 networks:
 - backend

Объемы

Объемы являются предпочтительным механизмом для сохранения данных, генерируемых контейнерами Docker. В Compose можно объявить именованные тома на верхнем уровне и монтировать их в сервисы. Это необходимо для баз данных, загрузок файлов и любого государственного сервиса.

volumes:
 db_data:
services:
 postgres:
 image: postgres:16
 volumes:
 - db_data:/var/lib/postgresql/data

Переменные окружающей среды

Вы можете жестко закодировать их в файле Compose для разработки, но для производства вы должны использовать файлы или секреты Docker. Compose автоматически загружает файл , расположенный в том же каталоге, если вы не указываете .

services:
 api:
 image: my-api:latest
 env_file:
 - ./api.env
 environment:
 - NODE_ENV=production

Настройка Nginx как обратного прокси — Advanced

Nginx сияет как обратный прокси из-за его высокой производительности, низкого объема памяти и богатого набора функций. В многоконтейнерной архитектуре Nginx сидит на краю, принимает запросы клиентов и пересылает их в соответствующий сервис на основе запроса URI, заголовков или даже содержимого тела. Он также может обрабатывать SSL-окончание, кэширование, ограничение скорости и балансировку нагрузки.

Обратный прокси с несколькими восходящими потоками

Вот более полная конфигурация, которая демонстрирует, как разделить трафик между двумя различными приложениями, включая обработку статических активов и WebSockets:

upstream app1_upstream {
 server app1:8000;
}

upstream app2_upstream {
 server app2:8001;
}

server {
 listen 80;
 server_name example.com;
 return 301 https://$server_name$request_uri;
}

server {
 listen 443 ssl http2;
 server_name example.com;

 ssl_certificate /etc/nginx/certs/fullchain.pem;
 ssl_certificate_key /etc/nginx/certs/privkey.pem;

 # Security headers
 add_header X-Frame-Options "SAMEORIGIN" always;
 add_header X-Content-Type-Options "nosniff" always;
 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

 # App 1 – main web frontend
 location / {
 proxy_pass http://app1_upstream;
 proxy_set_header Host $host;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header X-Forwarded-Proto $scheme;
 }

 # App 2 – admin dashboard
 location /admin/ {
 proxy_pass http://app2_upstream/;
 proxy_set_header Host $host;
 proxy_set_header X-Real-IP $remote_addr;
 }

 # WebSocket support (e.g., for live updates)
 location /ws/ {
 proxy_pass http://app1_upstream;
 proxy_http_version 1.1;
 proxy_set_header Upgrade $http_upgrade;
 proxy_set_header Connection "upgrade";
 }

 # Static assets – serve directly for better performance
 location /static/ {
 alias /var/www/static/;
 expires 30d;
 add_header Cache-Control "public, immutable";
 }
}

Обратите внимание на использование блоков . Они позволяют определить группу серверов для балансировки нагрузки. Даже с одним сервером использование группы upstream позволяет легко добавлять больше реплик позже, не касаясь блока сервера.

SSL/TLS с функцией Let’s Encrypt

Для производства, обслуживание HTTPS не является предметом переговоров. Вы можете автоматизировать обновление сертификата с помощью Certbot или acme.sh внутри контейнера коляски. Общим шаблоном является установка сертификатов в качестве объемов в контейнер Nginx. Например:

services:
 nginx:
 image: nginx:alpine
 volumes:
 - ./nginx.conf:/etc/nginx/conf.d/default.conf
 - ./certs:/etc/nginx/certs:ro
 - ./static:/var/www/static:ro
 ports:
 - "80:80"
 - "443:443"
 depends_on:
 - app1
 - app2

Для автоматического обновления вы можете добавить сопутствующий контейнер, такой как или , который выполняет работу cron и перезагружает Nginx при обновлении сертификатов.

Балансировка нагрузки и проверки здоровья

При масштабировании сервиса на несколько реплик Nginx может распределять запросы с помощью круглых ромбов, наименьших соединений или хэша IP. объедините это с опцией Docker Compose :

upstream api_servers {
 least_conn;
 server api:80 max_fails=3 fail_timeout=30s;
}

Для обнаружения нездоровых контейнеров Nginx может быть настроен с помощью (требуется NGINX Plus или использовать из версии с открытым исходным кодом с небольшим обходным путем).

Создание файла Docker Compose — реальный пример

Давайте объединим все в практический файл Compose для веб-приложения, состоящего из API Node.js, интерфейса React, обслуживаемого Nginx, базы данных PostgreSQL и кэша Redis. Контейнер Nginx будет обслуживать статические файлы и запросы прокси-API.

version: "3.9"

services:
 nginx:
 image: nginx:alpine
 container_name: reverse-proxy
 restart: unless-stopped
 volumes:
 - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
 - ./frontend/build:/var/www/frontend:ro
 - ./certs:/etc/nginx/certs:ro
 ports:
 - "80:80"
 - "443:443"
 depends_on:
 - api
 - frontend
 networks:
 - public

 frontend:
 image: my-frontend:latest
 # In production, frontend is built into static files and served by Nginx
 # This container may run a dev server, but Nginx will bypass it for static files
 container_name: frontend-dev
 ports:
 - "3000:3000"
 networks:
 - public
 # healthcheck: ...

 api:
 image: my-api:latest
 container_name: api-server
 restart: unless-stopped
 env_file:
 - ./api/.env.production
 depends_on:
 postgres:
 condition: service_healthy
 redis:
 condition: service_started
 networks:
 - public
 - internal
 healthcheck:
 test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
 interval: 30s
 timeout: 10s
 retries: 3

 postgres:
 image: postgres:16-alpine
 container_name: database
 restart: unless-stopped
 environment:
 POSTGRES_USER: app_user
 POSTGRES_DB: app_db
 POSTGRES_PASSWORD_FILE: /run/secrets/db_password
 volumes:
 - pgdata:/var/lib/postgresql/data
 secrets:
 - db_password
 networks:
 - internal
 healthcheck:
 test: ["CMD-SHELL", "pg_isready -U app_user"]
 interval: 10s
 timeout: 5s
 retries: 5

 redis:
 image: redis:7-alpine
 container_name: cache
 restart: unless-stopped
 volumes:
 - redisdata:/data
 networks:
 - internal
 healthcheck:
 test: ["CMD", "redis-cli", "ping"]
 interval: 10s
 timeout: 3s

volumes:
 pgdata:
 redisdata:

networks:
 public:
 internal:
 internal: true

secrets:
 db_password:
 file: ./secrets/db_password.txt

Ключевые моменты в этом файле:

  • Сеть изоляции: сеть определяется как , то есть только услуги, подключенные к ней, могут общаться. База данных и кэш недоступны из-за пределов наложения Docker.
  • Секреты: Чувствительные данные, такие как пароли, хранятся в виде секретов Docker, смонтированных в виде файлов внутри контейнера. Это позволяет избежать утечки их в переменные среды, которые могут быть раскрыты через .
  • Проверки здоровья: Службы объявляют проверки здоровья, чтобы могли дождаться состояния . Nginx начнется только после того, как API будет здоровым.
  • Статические активы: Выход сборки интерфейса устанавливается непосредственно в Nginx, что позволяет Nginx обслуживать статические файлы, не нажимая на сервер разработчиков интерфейса. Это улучшает производительность и разделяет проблемы.

Развертывание приложения - шаг за шагом

При наличии готового файла Compose и конфигурации Nginx развертывание включает в себя несколько простых команд. Во-первых, убедитесь, что все файлы конфигурации находятся на месте:

project/
├── docker-compose.yml
├── nginx/
│ └── nginx.conf
├── api/
│ └── .env.production
├── frontend/
│ └── build/
├── certs/
│ └── (fullchain.pem, privkey.pem)
└── secrets/
 └── db_password.txt

Тогда беги.

docker compose pull # pull latest images
docker compose up -d # start all services in background
docker compose ps # verify services are running
docker compose logs -f # tail logs for all services

После того, как все будет готово, проверьте приложение, посетив ваш домен. Проверьте журналы Nginx, чтобы подтвердить, что запросы прокси-кодируются правильно. Если вам нужно обновить параметры конфигурации или среды, отредактируйте соответствующие файлы и запустите:

docker compose up -d --no-deps --build nginx # rebuild only nginx if config changed

Для развертывания с нулевым временем простоя рассмотрите возможность использования сине-зеленых или прокрутки обновлений с функцией Compose и проверками здоровья Nginx вверх по течению.

Масштабирование и настройка производительности

Docker Compose делает тривиальным масштабирование сервисов без гражданства, таких как API:

docker compose up -d --scale api=3

Сейчас запущены три реплики API-контейнера. Nginx, сконфигурированный с вышестоящим блоком, будет автоматически распределять между ними запросы. Для дальнейшего повышения производительности:

  • Включить сжатие Gzip в Nginx для текстовых ответов.
  • Установите соответствующие заголовки кэша для статических активов (как показано в примере).
  • Используйте CDN для глобальной доставки статических файлов.
  • Тюне Nginx обрабатывает , чтобы соответствовать ядрам процессора:
  • Увеличить пределы соединения:

Для баз данных убедитесь, что у вас есть объединение соединений в вашем API, и рассмотрите возможность использования PgBouncer в качестве контейнера для коляски.

Устранение общих проблем

Даже при хорошо структурированной установке все может пойти не так. Вот частые подводные камни и как их исправить:

  • Контейнер не может связаться с другой службой по имени: Проверьте, что обе службы находятся в одной сети Docker. Используйте для тестирования.
  • Nginx возвращает 502 Bad Gateway: Контейнер верхнего потока может не работать или не прослушивать ожидаемый порт. Проверьте с . Также убедитесь, что URL-адрес прокси-пропуска включает правильный протокол и порт.
  • Ошибки разрешения с томами: Файлы, смонтированные в контейнеры, наследуют право собственности хоста. Используйте пространства имен пользователей или установите директиву в Nginx и служебном контейнере.
  • Сертификат SSL не продлевает: Если используется Certbot автономно, убедитесь, что порты 80 и 443 не заблокированы Nginx во время обновления.
  • Переменные среды, не загруженные : Проверьте, находится ли файл в правильном месте или используйте директиву .

Лучшие практики безопасности

Работа многоконтейнерных приложений в производстве требует бдительности:

  • Никогда не запускайте контейнеры в качестве корня , если это не необходимо. Используйте директиву или укажите в Dockerfile.
  • Сохраняйте изображения в актуальном состоянии с регулярным сканированием уязвимостей. Используйте официальные базовые изображения и версии с пин-кодами.
  • Ограничение сетевого воздействия: Только выставлять в интернет обратные прокси-порты. Все остальные сервисы должны быть во внутренних сетях.
  • Использовать управление секретами для паролей, ключей API и токенов. Секреты Docker — хорошее начало; для более крупных настроек рассмотрите HashiCorp Vault.
  • Включить Docker Content Trust для проверки подписей изображений.
  • Регулярно регистрируйте и проверяйте контейнерную активность с использованием или централизованного решения для регистрации, такого как стек ELK.
  • Ограничение ресурсов в вашем файле Compose, чтобы предотвратить голод других контейнеров:
services:
 api:
 deploy:
 resources:
 limits:
 cpus: '0.50'
 memory: 256M
 reservations:
 cpus: '0.25'
 memory: 128M

Эти ограничения соблюдаются при использовании роя Docker; для обычного композитного материала они соблюдаются с помощью (хотя лучше использовать рой или K8 для оркестровки производства).

Заключение

Docker Compose и Nginx вместе образуют мощную, масштабируемую и поддерживаемую платформу для развертывания многоконтейнерных веб-приложений. Определяя ваш стек в одном файле YAML, вы достигаете паритета среды от разработки до производства. Nginx, как обратный прокси, обеспечивает центральную точку для SSL терминации, маршрутизации трафика, балансировки нагрузки и кэширования. Мы рассмотрели основные компоненты: определения услуг, сети, объемы, переменные среды, конфигурацию Nginx с оптимизацией безопасности и производительности, советы по масштабированию и устранению неполадок. С помощью этого фундамента вы готовы создавать, развертывать и эксплуатировать современные контейнеризованные приложения с уверенностью. Для дальнейшего чтения обратитесь к документации [FLT: 1] и [FLT: 2]]Nginx [[FLT: 3]] и изучите реальные примеры из таких как [FLT: 4]] Awesome Compose [[FLT: 5]] для вдохновения.