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

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

- Рубрика: Гайды по играм
- Игра: VPS
- Опубликовано: 2026-11-13T21:20:00+03:00
- Обновлено: 2026-11-13T21:20:00+03:00
- Адрес: https://blog.egnetwork.ru/vps-docker-compose-neskolko-servisov
- Автор: Команда EGN

Один контейнер на 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, затем проверьте:

```bash
docker --version
docker compose version
```

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

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

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

```text
/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` рядом с ним:

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

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

```bash
chmod 600 /opt/shop/.env
```

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

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

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

```bash
cd /opt/shop
docker compose config
```

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

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

```bash
docker compose up -d --build
docker compose ps
```

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

![Схема: наружу открыт только web, остальные сервисы общаются по именам внутри сети Compose](https://blog.egnetwork.ru/media/616)

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

![Порядок запуска: база, проверка здоровья, приложение, веб-сервер](https://blog.egnetwork.ru/media/617)

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

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

```bash
docker compose logs -f
```

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

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

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

```bash
docker compose pull
docker compose up -d
docker image prune -f
```

Перед обновлением базы сделайте дамп, как описано в статье [База данных на VPS: MySQL или PostgreSQL без утечек в интернет](https://blog.egnetwork.ru/vps-baza-dannykh-mysql-postgresql). Смена мажорной версии PostgreSQL, например с 15 на 16, требует миграции данных, а не просто замены образа.

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

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

```bash
docker compose down
```

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

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

Переустановка VPS стирает и контейнеры, и тома. Поэтому перед переустановкой сделайте копии, как описано в статье [Бэкапы VPS: снапшоты, S3 и restic](https://blog.egnetwork.ru/vps-bekapy-snapshot-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](https://blog.egnetwork.ru/vps-sait-nginx-letsencrypt).

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

Параметр `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](https://blog.egnetwork.ru/vps-discord-bot-systemd), она описывает, как держать бота вне контейнеров.

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

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

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

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

### Почему `docker compose config` так важен?
Он показывает итоговую конфигурацию после подстановки переменных. Ошибку в имени переменной или отступе проще найти там, чем в работающем контейнере.

Сервер под игры за пару минут — [egnetwork.ru](https://egnetwork.ru/)

---

Материал EGN (blog.egnetwork.ru), обновлено 13 ноября 2026; при цитировании указывайте ссылку.
