К основному содержимому

Docker Compose: запуск стека приложений

VDS / VPS · 11.02.2025 · 5 мин чтения
Иллюстрация к статье «Docker Compose: запуск стека приложений»

Структура docker-compose.yml

version: '3.9'

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./www:/var/www/html:ro
    depends_on:
      php:
        condition: service_healthy
    networks:
      - app-net

  php:
    image: php:8.2-fpm-alpine
    volumes:
      - ./www:/var/www/html
    healthcheck:
      test: ["CMD", "php-fpm", "-t"]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - app-net

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASS}
    volumes:
      - mysql-data:/var/lib/mysql
    networks:
      - app-net

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASS}
    networks:
      - app-net

volumes:
  mysql-data:

networks:
  app-net:

Файл .env для переменных

DB_ROOT_PASS=supersecret
DB_NAME=myapp
DB_USER=appuser
DB_PASS=dbpassword
REDIS_PASS=redispass

Команды управления

docker compose up -d        # Запустить в фоне
docker compose down         # Остановить
docker compose logs -f      # Логи
docker compose ps           # Статус сервисов
docker compose exec php sh  # Войти в контейнер
💡 Используйте именованные тома (volumes) для персистентных данных — они сохраняются при docker compose down.

Разбор типичных ошибок

Ошибка Bind for 0.0.0.0:80 failed: port is already allocated означает, что порт 80 уже занят — либо другим контейнером, либо системным Nginx/Apache, запущенным напрямую на хосте.

Error response from daemon: driver failed programming external connectivity on endpoint nginx:
Bind for 0.0.0.0:80 failed: port is already allocated

Найдите, кто держит порт: ss -tlnp | grep :80. Если это системный Nginx — остановите и отключите его автозапуск (systemctl disable --now nginx) перед тем, как поднимать контейнерный стек с тем же портом, иначе конфликт повторится после каждой перезагрузки сервера.

Контейнер php постоянно перезапускается (Restarting (1) X seconds ago) — прежде чем гадать, посмотрите логи самого контейнера, а не только статус:

docker compose logs php --tail=50

Частая причина для PHP-FPM — синтаксическая ошибка в примонтированном коде или отсутствующее расширение, которого нет в базовом образе php:8.2-fpm-alpine; Alpine-образы минимальны и не включают многие расширения по умолчанию, их нужно ставить явно через отдельный Dockerfile.

Сравнение способов передачи секретов

Пример выше хранит пароли в .env — это приемлемо для разработки, но для продакшена такой файл обязан быть вне git и иметь права 600, иначе любой доступ к диску сервера равносилен утечке всех паролей сразу.

СпособГде хранитсяРиск
Пароли прямо в docker-compose.ymlВ самом файле, часто в gitВысокий — секрет утекает вместе с репозиторием
Файл .env рядом с composeОтдельный файл, не в образеСредний — нужен .gitignore, но секрет всё ещё plaintext на диске
Docker secrets (Swarm)Зашифровано в Raft-логе кластераНизкий, но требует режима Swarm

Когда Docker Compose не подходит

  • Нужно распределить сервисы между несколькими физическими серверами — Compose управляет контейнерами только на одном хосте, для кластера нужен Docker Swarm или Kubernetes.
  • Требуется автоматическое масштабирование сервиса под нагрузкой — Compose запускает фиксированное число реплик, которое нужно менять вручную командой --scale, авто-скейлинга здесь нет.
  • Продакшен с жёсткими требованиями к отказоустойчивости — при падении хоста все контейнеры Compose падают вместе с ним, без оркестратора с несколькими узлами восстановление полностью ручное.
  • Нужен zero-downtime деплой новой версии сервиса — docker compose up -d при обновлении образа пересоздаёт контейнер с коротким простоем, для действительно бесшовного обновления нужен отдельный механизм вроде rolling update в Swarm или Kubernetes.

Проверка результата

  1. Убедитесь, что все сервисы запущены и здоровы: docker compose ps должен показывать статус running, а у php — дополнительно (healthy).
  2. Проверьте, что сайт реально отвечает через Nginx: curl -I http://localhost должен вернуть 200 или ожидаемый код редиректа.
  3. Проверьте, что данные MySQL переживают пересоздание контейнеров: docker compose down && docker compose up -d, затем убедитесь, что таблицы на месте — благодаря именованному тому mysql-data.
  4. Просмотрите логи на ошибки сразу после старта: docker compose logs --tail=100 не должен содержать повторяющихся трейсов исключений.

Для более глубокой начальной настройки самого Docker на VDS — статья про установку и настройку Docker. Если нужен собственный registry для приватных образов вместо Docker Hub — см. статью про приватный Docker registry.

Частые вопросы

Что делать, если порт 80 уже занят системным Nginx, когда поднимаю стек через Docker Compose?

Нужно решить, какой сервис будет слушать порт: либо остановить и отключить автозапуск системного Nginx перед запуском контейнеров, либо изменить проброс портов в compose-файле на другой внешний порт. Если просто перезапустить стек без этого решения, конфликт портов вернётся при каждой перезагрузке хоста, потому что системная служба снова поднимется раньше контейнеров.

Достаточно ли хранить пароли в файле .env для продакшена?

Формально работает, но с оговорками: файл должен лежать вне git и иметь строгие права доступа, иначе любой, кто получит доступ к диску сервера, получает сразу все секреты в открытом виде. Для более серьёзной защиты существует режим Docker Swarm с зашифрованным хранением секретов — но он требует перехода в этот режим, а не просто добавления файла .env рядом с compose.

Может ли Docker Compose сам увеличивать число контейнеров сервиса под нагрузкой?

Нет, Compose запускает фиксированное число реплик, заданное в конфиге или переданное флагом масштабирования вручную, и само не реагирует на рост нагрузки. Для автоматического масштабирования под реальной нагрузкой нужен оркестратор вроде Kubernetes или режим Swarm с соответствующими правилами — обычный Compose для этого не предназначен.

Как обновить образ сервиса в работающем стеке без простоя для пользователей сайта?

Стандартная команда для обновления образа пересоздаёт контейнер с коротким перерывом в обслуживании — это не настоящий zero-downtime деплой. Для действительно бесшовного обновления нужен отдельный механизм вроде rolling update, доступный в режиме Swarm или в Kubernetes, где старый контейнер снимается с балансировки только после того, как новый полностью готов принимать трафик.

Статья помогла?
← Назад в базу знаний Задать вопрос поддержке