Docker Compose на VPS: несколько сервисов в одном файле

Как описать в одном docker-compose.yml сайт, приложение и базу данных, связать их внутренней сетью, дождаться готовности базы и не открывать лишние порты наружу.

Команда EGN 8 мин чтения
Docker Compose на VPS: несколько сервисов в одном файле
Содержание 21
  1. Коротко
  2. Что такое Compose и зачем он нужен
  3. Структура проекта
  4. Шаг 1. Опишите сервисы
  5. Шаг 2. Проверьте файл
  6. Шаг 3. Запустите
  7. Шаг 4. Смотрите логи
  8. Шаг 5. Обновляйте аккуратно
  9. Остановка и удаление
  10. Как это связано с панелью и кабинетом EGN
  11. Симптом → причина → решение
  12. Пример: несколько проектов на одном VPS
  13. Пример: перезапуск после перезагрузки
  14. Чек-лист перед переездом на новый VPS
  15. Частые ошибки
  16. Итог
  17. Вопросы и ответы
  18. Нужен ли отдельный VPS для каждого контейнера?
  19. Чем Compose отличается от docker run?
  20. Как обновить приложение без простоя?
  21. Почему docker compose config так важен?

Один контейнер на VPS — это просто. Когда появляются сайт, приложение, база и бот, ручной запуск каждого превращается в ежедневную головную боль. Docker Compose описывает все сервисы в одном файле: какие образы нужны, как они связаны, какие порты открыты и где хранятся данные. Команда docker compose up -d поднимает весь набор, а docker compose down его останавливает.

Коротко

Опишите сервисы в одном файле docker-compose.yml: веб-сервер, приложение и базу. Наружу публикуйте только порты веб-сервера, а базе задайте healthcheck и depends_on с condition: service_healthy, чтобы приложение стартовало после готовности базы. Запуск — docker compose up -d, проверка — docker compose ps и docker compose logs -f.

Что такое Compose и зачем он нужен

Compose — это инструмент Docker для описания нескольких контейнеров как одного приложения. Вместо десяти команд docker run с длинными списками портов и переменных вы храните настройку в файле, который можно хранить в git и переносить на другой VPS.

Compose не заменяет оркестраторы вроде Kubernetes, и для одного VPS он подходит лучше всего. Если сервисов больше десятка или нужна работа на нескольких машинах, тогда смотрите в сторону других инструментов. Для связки «сайт, приложение, база» Compose даёт всё необходимое.

Установите Docker по официальной инструкции Docker для вашей версии Ubuntu, затем проверьте:

Терминал
docker --version
docker compose version

Команда docker compose с пробелом — это текущий плагин Compose. Старый docker-compose с дефисом больше не нужен.

Структура проекта

Создайте каталог проекта и поместите в него три файла: docker-compose.yml, .env с секретами и каталог приложения с Dockerfile:

Текст
/opt/shop/
├── docker-compose.yml
├── .env
└── app/
    ├── Dockerfile
    └── main.py

Файл .env Compose читает автоматически, и значения из него подставляются в docker-compose.yml через ${ИМЯ}. Так пароли не попадают в сам файл конфигурации.

Шаг 1. Опишите сервисы

Создайте docker-compose.yml. Имя проекта задаётся верхним уровнем name, поле version больше не нужно:

YAML
name: shop

services:
  web:
    image: nginx:stable
    ports:
      - "80:80"
    depends_on:
      - app

  app:
    build: ./app
    environment:
      DATABASE_URL: postgresql://app:${DB_PASSWORD}@db:5432/app
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD:?задайте DB_PASSWORD в .env}
      POSTGRES_DB: app
    volumes:
      - dbdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 3s
      retries: 10
    restart: unless-stopped

volumes:
  dbdata:

Обратите внимание на три вещи. У db нет блока ports: база не публикуется наружу и доступна только внутри сети Compose по имени db. У app depends_on ждёт не просто запуска базы, а её healthcheck. У dbdata есть именованный том, поэтому данные переживают docker compose down и пересоздание контейнера.

Конструкция ${DB_PASSWORD:?...} останавливает запуск с понятной ошибкой, если переменная не задана. Файл .env рядом с ним:

server.properties
DB_PASSWORD=замените-на-длинный-пароль

Пароль для строки подключения делайте из букв и цифр: символы вроде @, / или : в postgresql:// придётся экранировать.

Терминал
chmod 600 /opt/shop/.env

Образ nginx:stable без своего конфига отдаёт стандартную страницу. Для реального сайта подключите свой конфиг через том или соберите образ из собственного Dockerfile. Пример показывает схему связей, а не готовый сайт.

Шаг 2. Проверьте файл

Перед запуском проверьте, что Compose правильно подставил переменные и связи:

Терминал
cd /opt/shop
docker compose config

Команда выведет итоговую конфигурацию с подставленными значениями. Если переменная не задана, Compose предупредит об этом и подставит пустую строку (для переменных с :? остановится с ошибкой). Пароль появится в выводе, поэтому не копируйте его в чат и не сохраняйте в файлы логов.

Шаг 3. Запустите

Терминал
docker compose up -d --build
docker compose ps

Флаг --build пересобирает образ приложения из Dockerfile. Колонка STATUS в выводе ps показывает, что база стала healthy, а приложение running.

Схема: наружу открыт только web, остальные сервисы общаются по именам внутри сети Compose
Схема: наружу открыт только web, остальные сервисы общаются по именам внутри сети Compose

Порядок старта при depends_on с проверкой здоровья выглядит так:

Порядок запуска: база, проверка здоровья, приложение, веб-сервер
Порядок запуска: база, проверка здоровья, приложение, веб-сервер

Шаг 4. Смотрите логи

Логи всех сервисов выводятся командой:

Терминал
docker compose logs -f

Чтобы посмотреть только приложение, добавьте имя сервиса: docker compose logs -f app. Для последних строк без режима реального времени используйте --tail 100.

Шаг 5. Обновляйте аккуратно

Обновление образа делается тремя командами: скачать новый образ, пересоздать контейнеры, почистить старые образы.

Терминал
docker compose pull
docker compose up -d
docker image prune -f

Перед обновлением базы сделайте дамп, как описано в статье База данных на VPS: MySQL или PostgreSQL без утечек в интернет. Смена мажорной версии PostgreSQL, например с 15 на 16, требует миграции данных, а не просто замены образа.

Остановка и удаление

Есть два похожих, но разных действия. docker compose down останавливает и удаляет контейнеры и сеть, но тома сохраняются. docker compose down -v удаляет и тома, то есть данные базы. Вторую команду используйте только если точно знаете, что данные не нужны.

Терминал
docker compose down

Как это связано с панелью и кабинетом EGN

В карточке VPS в кабинете EGN есть вкладка «Консоль», которая открывает терминал в браузере. Если SSH-клиент недоступен, через неё можно выполнить docker compose ps и docker compose logs. Вкладка «Мониторинг» показывает нагрузку сервера, чтобы понять, не упёрся ли он в процессор или память. Публикуя порт командой Docker, помните про вкладку «Фаервол»: облачный фаервол в режиме «Свои правила» режет входящие подключения до сервера и поэтому работает и для опубликованных портов Docker.

Переустановка VPS стирает и контейнеры, и тома. Поэтому перед переустановкой сделайте копии, как описано в статье Бэкапы VPS: снапшоты, S3 и restic.

Симптом → причина → решение

Симптом Частая причина Что сделать
app падает при старте с ошибкой подключения к базе depends_on без condition Добавьте condition: service_healthy и healthcheck у базы
Контейнер db в статусе unhealthy Неверная команда в test или база ещё не инициализировалась Проверьте docker compose logs db, увеличьте retries
port is already allocated Порт 80 занят другим процессом Найдите его через sudo ss -tlnp | grep ':80' и освободите
Данные пропали после down Использован down -v или не задан том Проверьте docker volume ls, восстановите из копии
Переменная пустая в контейнере Нет .env или опечатка в имени Запустите docker compose config и сравните значения
Образ не обновляется Используется тег без pull Выполните docker compose pull перед up

Пример: несколько проектов на одном VPS

Каждый проект живёт в своём каталоге с собственным docker-compose.yml. Compose по умолчанию берёт имя проекта из имени каталога, а в примере имя задано явно через name: shop. Так два проекта не смешают контейнеры и тома. Порты для них должны быть разными: если оба публикуют 80, второй не запустится. Один из проектов можно поставить за nginx на другом порту, как в статье Сайт на VPS с nginx и Let’s Encrypt.

Пример: перезапуск после перезагрузки

Параметр restart: unless-stopped возвращает контейнер в работу после перезагрузки VPS, если вы сами его не останавливали. Проверьте это, перезагрузив сервер командой sudo reboot и выполнив docker compose ps после входа. Если сервис не поднялся, проверьте, что служба Docker включена: sudo systemctl is-enabled docker.

Чек-лист перед переездом на новый VPS

Если проект переезжает на другой сервер, Compose делает это почти механически, если соблюдены три условия:

  1. Файлы docker-compose.yml и каталог приложения лежат в git или в резервной копии.
  2. .env перенесён отдельно и не попадает в репозиторий. Его храните в защищённом месте.
  3. Тома скопированы или восстановлены из дампа. Для PostgreSQL это pg_dump до переезда и psql после. Копирование каталога данных при работающей базе ненадёжно.

После переезда выполните docker compose up -d, проверьте docker compose ps и один запрос к приложению. Только после этого переключайте DNS.

Частые ошибки

  • Публикуют порт базы. ports: - "5432:5432" открывает базу на все адреса, и ufw её не закроет. Не публикуйте её вовсе или указывайте 127.0.0.1:.
  • Пароли в docker-compose.yml. Вынесите их в .env с правами 600.
  • Нет healthcheck. Без него depends_on ждёт только запуска контейнера, а база ещё может инициализироваться.
  • down -v по привычке. Эта команда удаляет данные. Запомните разницу с down.
  • Образы без тегов. image: postgres без версии обновит базу непредсказуемо. Указывайте postgres:16.
  • Логи растут без предела. По умолчанию Docker хранит логи контейнеров в json-файлах без ограничения. Задайте лимит для сервиса:
YAML
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Этот блок добавляется внутрь описания сервиса на уровне image или restart.

Итог

Docker Compose на одном VPS даёт порядок: один файл описывает все сервисы, внутренняя сеть закрывает базу, healthcheck задаёт правильный порядок запуска, а .env держит секреты отдельно. Публикуйте наружу только веб-сервер, делайте копии томов и проверяйте восстановление. Если к сервисам добавятся игровой сервер и бот, посмотрите статью Discord-бот на VPS: запуск через systemd, она описывает, как держать бота вне контейнеров.

Вопросы и ответы

Нужен ли отдельный VPS для каждого контейнера?

Нет. На одном VPS можно держать несколько сервисов в Compose, главное — не публиковать лишние порты и следить за ресурсами.

Чем Compose отличается от docker run?

docker run запускает один контейнер с параметрами в командной строке. Compose описывает весь набор в файле, его удобно хранить в git и переносить.

Как обновить приложение без простоя?

Простой короткий, так как up -d пересоздаёт контейнер. Для настоящего нулевого простоя нужен второй экземпляр и балансировщик, это уже другой уровень.

Почему docker compose config так важен?

Он показывает итоговую конфигурацию после подстановки переменных. Ошибку в имени переменной или отступе проще найти там, чем в работающем контейнере.

Сервер под игры за пару минут — egnetwork.ru

Читать далее

Вся рубрика →