Автоматизация        05 сентября 2026        12         0

Docker Compose на сервере: структура проекта, сети, тома, обновления

Через полгода после запуска сервера на нём обычно обнаруживается пять-шесть контейнеров, запущенных длинными командами docker run. Команды остались в истории оболочки, которую давно вытеснили новые записи. Какой образ какой версии, с какими переменными и куда подключён том с базой, уже никто не помнит. Перенести такой сервер или восстановить его после сбоя можно только раскопками.

Docker Compose заменяет эти команды одним текстовым файлом. В нём записано, какие контейнеры составляют проект, как они связаны и где лежат данные. Файл хранится в системе контроля версий, и сервер поднимается из него одной командой. Ниже схема, которой хватает для одиночного сервера с несколькими проектами. Команды - пример для Ubuntu Server 26.04 LTS, Docker Engine 29.8 и Docker Compose 5.5.

Установка: из репозитория Docker, а не из первого попавшегося источника

Установка: из репозитория Docker, а не из первого попавшегося источникаВ Ubuntu Docker можно поставить тремя способами: пакет docker.io из архива дистрибутива, snap-пакет и официальный репозиторий Docker. Первые два отстают по версиям, а snap к тому же хранит данные в нестандартных каталогах, из-за чего половина инструкций из интернета на нём не работает. Для сервера берите официальный репозиторий. Команды из документации Docker для Ubuntu 26.04:

sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Compose ставится пакетом docker-compose-plugin и вызывается как подкоманда: docker compose, через пробел. Отдельная программа docker-compose через дефис - это первая версия, она давно не развивается. Если руководство использует её, оно устарело. Проверка установки:

docker --version
docker compose version

После установки команды Docker требуют sudo. Обычный совет - добавить себя в группу docker. Перед этим стоит понимать цену: участник этой группы может запустить контейнер с подключённым корнем файловой системы и получить права root без пароля. На личном сервере это приемлемо, на машине с несколькими администраторами разных уровней доступа уже нет.

Если сервер только что получен от хостера, сначала пройдите шаги первоначальной настройки сервера: файрвол и обновления лучше настроить до появления контейнеров.

Структура проекта: один каталог - один проект

Compose считает проектом каталог, в котором лежит файл compose.yaml. Имя каталога становится префиксом для контейнеров, сетей и томов. Отсюда простое правило: каждому проекту свой каталог, все проекты в одном месте. Удобно держать их в /opt или в /srv.

/opt/quotes/
├── compose.yaml
├── .env
├── secrets/
│   └── db_password.txt
└── app/
    ├── Dockerfile
    └── ...

Файл называется compose.yaml. Старое имя docker-compose.yml по-прежнему читается, но в новых проектах используется первое. Каталог secrets закройте от посторонних командой chmod 700, а файлы в нём - chmod 600. В систему контроля версий попадает всё, кроме .env и secrets: их добавляют в .gitignore и хранят отдельно, например в менеджере паролей.

Вот файл для типичной связки «своё приложение и база данных»:

name: quotes

services:
  db:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_DB: quotes
      POSTGRES_USER: quotes
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - backend
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U quotes -d quotes"]
      interval: 10s
      timeout: 5s
      retries: 5

  app:
    build: ./app
    restart: unless-stopped
    env_file: .env
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "127.0.0.1:8000:8000"
    networks:
      - backend

volumes:
  db_data:

networks:
  backend:

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

Строки version: "3.8" в начале файла нет, и это не забывчивость. Современный Compose её не использует и при встрече пишет предупреждение: the attribute version is obsolete, it will be ignored, please remove it to avoid potential confusion. Наличие этой строки в примере из интернета подсказывает, что примеру несколько лет.

Перед запуском файл проверяют командой docker compose config. Она подставляет переменные, раскрывает сокращения и печатает итоговую конфигурацию либо сообщает об ошибке с номером строки. С ключом -q команда молчит при успехе, так её удобно вызывать в скриптах.

Что в этом файле защищает от неприятностей

Что в этом файле защищает от неприятностейПолитика перезапуска. restart: unless-stopped поднимает контейнер после сбоя и после перезагрузки сервера, но не трогает его, если вы остановили его сами. Служба Docker стартует при загрузке системы и запускает контейнеры с такой политикой, поэтому оборачивать проект службой systemd не нужно. Unit-файл остаётся инструментом для программ, которые работают без контейнера.

Порядок запуска. Простой depends_on гарантирует только то, что контейнер базы запущен раньше контейнера приложения. Готова ли база принимать подключения, он не проверяет, а PostgreSQL после старта ещё несколько секунд инициализируется. Приложение успевает обратиться к базе, получает отказ и падает. Связка healthcheck у базы и condition: service_healthy у приложения устраняет эту гонку: приложение стартует только после успешного ответа pg_isready.

Порты. Запись "127.0.0.1:8000:8000" публикует порт только на локальном адресе сервера. Без адреса, в виде "8000:8000", порт открывается на всех интерфейсах, и файрвол ufw его не закроет: документация Docker прямо предупреждает, что опубликованные порты контейнеров обходят правила ufw. Это самая частая причина, по которой панели администрирования и базы оказываются в открытом доступе. Наружу должен смотреть только обратный прокси на портах 80 и 443, а приложения - слушать локальный адрес. У базы в примере секции ports нет вовсе: приложение обращается к ней по имени db внутри сети backend, и с хоста она недоступна.

Данные. Всё, что контейнер пишет в свою файловую систему, исчезает при его пересоздании. Постоянные данные живут в томах. В примере использован именованный том db_data: Docker сам хранит его в /var/lib/docker/volumes/. Второй вариант - подключить каталог хоста записью вида ./data:/var/lib/postgresql/data. Именованный том избавляет от проблем с правами доступа, каталог хоста проще увидеть и скопировать. Для баз данных надёжнее первый вариант, для конфигурационных файлов удобнее второй.

Секреты. Пароль базы лежит в отдельном файле и попадает в контейнер как /run/secrets/db_password, а образ PostgreSQL читает его через переменную с суффиксом _FILE. В переменных окружения пароля нет, поэтому он не всплывёт в выводе docker inspect и в логах приложения, которое при ошибке печатает своё окружение.

Отдельно о двух файлах с переменными, которые постоянно путают. Файл .env рядом с compose.yaml Compose читает сам и использует для подстановки в текст файла, например image: postgres:${PG_TAG}. В контейнер эти переменные не попадают. Директива env_file делает другое: передаёт переменные из указанного файла внутрь контейнера. В примере один файл .env работает в обеих ролях, и это допустимо, пока вы помните о разнице.

Предупреждение: порты, опубликованные контейнером, обходят правила ufw. Запись "8080:8080" в compose.yaml открывает порт всему интернету даже при включённом файрволе с политикой «запретить входящие». Чтобы сервис был доступен только с самого сервера, пишите "127.0.0.1:8080:8080". Источник: документация Docker, раздел об установке Docker Engine на Ubuntu.

Повседневная работа и обновления

Команды выполняются из каталога проекта.

Таблица 1. Команды Docker Compose на каждый день

Задача Команда
Запустить или применить изменения docker compose up -d
Состояние сервисов docker compose ps
Логи одного сервиса docker compose logs -f app
Команда внутри контейнера docker compose exec db psql -U quotes
Остановить и удалить контейнеры docker compose down
Скачать новые образы docker compose pull

up -d можно выполнять сколько угодно раз. Compose сравнивает файл с тем, что запущено, и пересоздаёт только изменившиеся сервисы. down удаляет контейнеры и сети, но тома оставляет. А вот down -v удаляет и тома вместе с базой. Этот ключ встречается в инструкциях по «полной очистке», и набирать его на рабочем сервере по привычке нельзя.

Обновление состоит из трёх шагов: резервная копия, новые образы, пересоздание контейнеров.

cd /opt/quotes
docker compose exec -T db pg_dump -U quotes quotes > backup-$(date +%F).sql
docker compose pull
docker compose build --pull app
docker compose up -d
docker image prune -f

Ключ -T у exec отключает выделение терминала, без него вывод в файл может быть испорчен. Последняя команда удаляет старые образы, которые после обновления остались без дела и занимают место.

Версии образов нужно указывать явно. Тег latest означает «то, что было свежим в момент скачивания», и после pull вы получите неизвестно что. С postgres:17 обновление подтянет исправления внутри семнадцатой версии, а переход на следующую мажорную произойдёт только тогда, когда вы сами измените цифру. Для баз данных это критично: PostgreSQL не запускается на каталоге данных от другой мажорной версии. Смена 17 на 18 в файле без переноса данных через дамп закончится контейнером, который не стартует.

В эксплуатации Compose-проекты чаще всего ломаются из-за закончившегося места. Виноваты обычно логи: драйвер json-file, который Docker использует по умолчанию, не ограничивает размер файлов, и разговорчивое приложение за несколько месяцев заполняет диск. Лимит задаётся в /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

После правки нужен sudo systemctl restart docker. Настройка действует на контейнеры, созданные после неё, поэтому существующие проекты придётся пересоздать командой docker compose up -d --force-recreate. Что именно занимает место, показывает docker system df.

Где Compose хватает, а где нет

Compose рассчитан на один сервер. Проекты, которые ставятся на свою машину, обычно распространяются именно в таком виде. Хороший пример - установка n8n в Docker: тот же принцип с каталогом проекта, томом для данных и портом на локальном адресе.

Пределы у инструмента такие. Он не распределяет контейнеры по нескольким серверам и не переносит их при отказе машины. Он не обновляет сервис без короткого простоя: при up -d старый контейнер останавливается раньше, чем новый начинает отвечать. И он не следит за обновлениями сам. Для первого нужны оркестраторы вроде Kubernetes или Docker Swarm. Второе решается двумя экземплярами за балансировщиком. Третье закрывают сторонние программы автообновления, но на рабочем сервере автоматическая замена образов без резервной копии и проверки опаснее, чем обновление руками раз в месяц.

Каталог /opt/quotes сам по себе ещё не резервная копия проекта. В нём лежат описание и секреты, а данные хранятся в томах. Полная копия состоит из трёх частей: каталог проекта, дамп базы и содержимое томов с файлами. Восстановление на чистом сервере нужно хотя бы раз проделать руками. Только так выясняется, что пароль от базы хранился в единственном экземпляре на той самой машине.

Короткий пример: посмотреть, что на самом деле запустит Compose после подстановки всех переменных, можно командой docker compose config. Она же покажет ошибку в отступах или неизвестный параметр с номером строки. Источник: документация Docker Compose; команда проверена запуском на версии 5.5.1.

 

Проверка команд:

  • Запущено в тестовой среде (Docker 29.8.2, Docker Compose v5.5.1): файл compose.yaml из статьи проверен командой docker compose config -q без ошибок; список сервисов и томов совпал с ожидаемым. Предупреждение про атрибут version - настоящий вывод Compose 5.5.1 (в оригинале слово version взято в обратные апострофы).
  • Не запускалось (в тестовой среде нет демона Docker и доступа к реестру образов): up -d, pull, exec, pg_dump, healthcheck в работе, daemon.json. Сверено с документацией.

⚠️ Требует проверки:

  • Поднять пример целиком на Ubuntu 26.04 с любым простым приложением в каталоге app и пройти раздел обновления, включая pg_dump.
  • Проверить на Docker Hub, что тег postgres:17 актуален, и решить, не заменить ли пример на более новую мажорную версию. Внимание: начиная с образа postgres:18 путь тома изменён на /var/lib/postgresql, при замене версии строку volumes нужно поправить. Это утверждение по моим знаниям, сверьте с описанием образа.
  • Драйвер журналов по умолчанию (json-file) и отсутствие ротации в Docker 29 я не проверял по документации в этой сессии. Проверка: docker info --format '{{.LoggingDriver}}'.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.