Docker Compose — установка, команды и настройка контейнеров

Docker Compose -Практический пример стека Nginx + Node.js + PostgreSQL с разбором частых ошибок

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

В этой статье — всё что нужно: установка, структура файла, основные команды, сети, volumes и разбор реального примера со стеком Nginx + Node.js + PostgreSQL.


Что такое Docker Compose

Обычный Docker запускает один контейнер одной командой. Реальные приложения состоят из нескольких частей: веб-сервер, бэкенд, база данных, кэш. Запускать их по одному, следить за зависимостями, настраивать сети вручную — боль.

Docker Compose решает это через концепцию services. Каждый сервис — это контейнер (или группа контейнеров). Все сервисы описываются в одном docker-compose.yml файле и управляются как единое целое.

Docker vs Docker Compose — в чём разница

Docker Docker Compose
Один контейнер за раз Несколько контейнеров одновременно
Команды в терминале Конфигурация в YAML-файле
Сети и volumes — вручную Автоматически создаёт сети и volumes
Нет понятия зависимостей depends_on — порядок запуска
Для одиночных задач Для dev-окружения и простых прод-стеков

Когда использовать Compose: локальная разработка, тестовые окружения, небольшие продакшен-стеки на одном сервере.

Когда переходить на Kubernetes: нужна высокая доступность, масштабирование на несколько серверов, сложный оркестратор.


Установка Docker Compose

Есть два варианта Docker Compose — важно не путать:

  • docker-compose (со знаком дефис) — старая standalone-утилита, написана на Python, устарела
  • docker compose (без дефиса) — современный плагин для Docker CLI, написан на Go, рекомендуется

Используйте плагин. Если где-то встречаете docker-compose в инструкциях — это значит статья старая.

Docker Compose установка на Ubuntu

Способ 1 — через официальный репозиторий Docker (рекомендуется)

Устанавливаем Docker Engine вместе с плагином Compose:


# Удалить старые версии
sudo apt remove docker docker-engine docker.io containerd runc

# Установить зависимости
sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release

# Добавить GPG-ключ Docker
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# Добавить репозиторий
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Установить <a title="Установка и настройка SOCKS5 proxy Dante в Docker Compose" href="https://it-apteka.com/ustanovka-i-nastrojka-socks5-proxy-dante-v-docker-compose/" target="_blank" rel="noopener" data-wpil-monitor-id="521">Docker + плагин Compose</a>
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

Способ 2 — через apt (быстро, но не всегда последняя версия)


sudo apt update
sudo apt install -y docker.io docker-compose-plugin

Способ 3 — бинарник (legacy, для старых систем)


# Скачать последний релиз
sudo curl -L \
  "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" \
  -o /usr/local/bin/docker-compose

# Дать права на выполнение
sudo chmod +x /usr/local/bin/docker-compose

⚠️ Этот способ даёт старый docker-compose (с дефисом). Используйте только если нет другого выхода.

Проверка установки:


# Версия плагина (современный способ)
docker compose version

# Версия standalone (если стоит старый)
docker-compose --version

Вывод должен быть примерно таким:


Docker Compose version v2.24.0

Если видите v2.x — всё хорошо. Если v1.x — пора обновляться.


Docker Compose file — структура и синтаксис

Весь стек описывается в файле docker-compose.yml (или docker-compose.yaml — оба варианта работают). Это и есть docker compose yaml конфигурация.

Основные секции файла

[yaml] services: # Контейнеры приложения
volumes: # Постоянное хранилище данных
networks: # Сетевая изоляция
[/yaml]

Директива version в современных версиях Compose не нужна — она устарела начиная с Compose v2. Если встречаете version: "3.8" в старых инструкциях — это нормально, просто наследие.

Полный пример docker-compose.yml

Разворачиваем стек: Nginx → Node.js backend → PostgreSQL:

[yaml] services:

nginx:
image: nginx:alpine
container_name: nginx_proxy
ports:
— "80:80"
— "443:443"
volumes:
— ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
— static_files:/var/www/static
depends_on:
— backend
networks:
— frontend
restart: unless-stopped

backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: node_backend
environment:
— NODE_ENV=production
— DB_HOST=postgres
— DB_PORT=5432
— DB_NAME=myapp
— DB_USER=appuser
— DB_PASS=secretpassword
volumes:
— ./backend:/app
— /app/node_modules
depends_on:
postgres:
condition: service_healthy
networks:
— frontend
— backend
restart: unless-stopped

postgres:
image: postgres:16-alpine
container_name: postgres_db
environment:
POSTGRES_DB: myapp
POSTGRES_USER: appuser
POSTGRES_PASSWORD: secretpassword
volumes:
— postgres_data:/var/lib/postgresql/data
— ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
— backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"] interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped

volumes:
postgres_data:
static_files:

networks:
frontend:
driver: bridge
backend:
driver: bridge
[/yaml]

Разбор секций

services — основная секция. Каждый ключ внутри — отдельный сервис (контейнер). Имя сервиса становится именем хоста внутри сети Compose.

image / build — либо готовый образ из Docker Hub, либо сборка из локального Dockerfile.

ports — проброс портов: "хост:контейнер". Без этого сервис доступен только внутри сети Compose.

environment — переменные окружения. Лучше выносить секреты в .env файл и использовать ${VARIABLE}.

depends_on — порядок запуска. С condition: service_healthy ждёт пока healthcheck не пройдёт.

restart — политика перезапуска: no, always, unless-stopped, on-failure.

volumes — монтирование данных (подробнее ниже).

networks — к каким сетям подключён сервис.


Основные команды Docker Compose

Все docker compose command выполняются из папки, где лежит docker-compose.yml.

docker compose up

Запустить весь стек:


# Запустить в foreground (с выводом логов)
docker compose up

# Запустить в фоне (detached mode)
docker compose up -d

# Пересобрать образы и запустить
docker compose up -d --build

# Запустить только один сервис
docker compose up -d postgres

docker compose down

Остановить и удалить контейнеры:


# Остановить контейнеры
docker compose down

# Остановить и удалить volumes (осторожно - данные удалятся!)
docker compose down -v

# Остановить и удалить образы
docker compose down --rmi all

docker compose ps

Статус контейнеров в стеке:


docker compose ps

# Расширенный вывод
docker compose ps -a

docker compose logs


# Логи всех сервисов
docker compose logs

# Логи конкретного сервиса
docker compose logs backend

# Следить за логами в реальном времени
docker compose logs -f

# Последние 100 строк
docker compose logs --tail=100 postgres

docker compose build


# Собрать все образы
docker compose build

# Собрать без кэша
docker compose build --no-cache

# Собрать конкретный сервис
docker compose build backend

Остальные полезные команды


# Перезапустить сервис
docker compose restart backend

# Выполнить команду внутри контейнера
docker compose exec backend sh
docker compose exec postgres psql -U appuser -d myapp

# Запустить одноразовую команду в новом контейнере
docker compose run --rm backend npm run migrate

# Остановить без удаления
docker compose stop

# Посмотреть конфигурацию после подстановки переменных
docker compose config

# Версия
docker compose version

Docker Compose контейнеры и сервисы

Каждый docker compose service — это шаблон для создания контейнера. По умолчанию из одного сервиса создаётся один docker compose container.

Имя контейнера формируется автоматически: имя_проекта-имя_сервиса-номер. Например: myapp-backend-1. Проект — это имя папки, где лежит docker-compose.yml. Можно задать явно:


docker compose -p myproject up -d

Масштабирование сервисов


# Запустить 3 экземпляра backend
docker compose up -d --scale backend=3

Через файл (современный способ):

[yaml] services:
backend:
image: myapp/backend
deploy:
replicas: 3
[/yaml]

⚠️ При масштабировании нельзя использовать фиксированный container_name и проброс конкретного порта хоста — будет конфликт.


Docker Compose network

По умолчанию Compose создаёт одну сеть для всего стека — все сервисы в ней видят друг друга по имени сервиса. Это и есть DNS-resolution внутри Compose.


# Из контейнера backend обращаемся к postgres вот так:
# host: postgres (имя сервиса, не IP)
# port: 5432 (внутренний порт, не пробрасываемый)

Кастомные сети

Разделение на сети — best practice для изоляции. В нашем примере выше:

  • frontend — nginx и backend: фронт видит бэк
  • backend — backend и postgres: бэк видит базу
  • nginx не видит postgres напрямую — изоляция
[yaml] networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # Нет доступа в интернет из этой сети
[/yaml]

Подключение к внешней сети

[yaml] networks:
shared_network:
external: true # Сеть создана вне Compose, подключаемся к ней
[/yaml]

# Создать сеть заранее
docker network create shared_network

Docker Compose volumes

Без volumes данные живут только пока живёт контейнер. Удалили контейнер — данные исчезли. Docker compose volumes решают эту проблему.

Типы монтирования

Named volume — для данных БД и постоянного хранилища:

[yaml] services:
postgres:
volumes:
— postgres_data:/var/lib/postgresql/data

volumes:
postgres_data: # Объявляем именованный volume
[/yaml]

Данные хранятся в /var/lib/docker/volumes/имя_проекта_postgres_data/. Docker управляет этим сам.

Bind mount — для разработки, когда нужно монтировать локальную папку:

[yaml] services:
backend:
volumes:
— ./backend:/app # Локальная папка → внутрь контейнера
— /app/node_modules # Анонимный volume для node_modules
[/yaml]

Bind mount позволяет видеть изменения кода в реальном времени без пересборки образа.

Read-only mount — для конфигов:

[yaml] volumes:
— ./nginx.conf:/etc/nginx/nginx.conf:ro
[/yaml]

Как не потерять данные БД

Три правила:

  • Всегда используйте named volume для данных PostgreSQL, MySQL, MongoDB
  • Никогда не делайте docker compose down -v в продакшене без бэкапа
  • Регулярно бэкапьте volume отдельно от бэкапа контейнера

# Бэкап PostgreSQL из Compose
docker compose exec postgres pg_dump -U appuser myapp > backup.sql

# Восстановление
docker compose exec -T postgres psql -U appuser myapp < backup.sql

Практический пример — разворачиваем стек Nginx + Node.js + PostgreSQL

Поднимаем продакшен-стек с нуля. Структура проекта:


myapp/
├── docker-compose.yml
├── docker-compose.dev.yml
├── .env
├── nginx/
│   └── nginx.conf
└── backend/
    └── Dockerfile

Шаг 1 — Создаём .env файл с секретами:


# .env
POSTGRES_DB=myapp
POSTGRES_USER=appuser
POSTGRES_PASSWORD=supersecret
NODE_ENV=production

Шаг 2 — Production docker-compose.yml:

[yaml] services:
nginx:
image: nginx:alpine
ports:
— "80:80"
volumes:
— ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
— backend
networks:
— frontend
restart: unless-stopped

backend:
build: ./backend
environment:
— NODE_ENV=${NODE_ENV}
— DB_HOST=postgres
— DB_NAME=${POSTGRES_DB}
— DB_USER=${POSTGRES_USER}
— DB_PASS=${POSTGRES_PASSWORD}
depends_on:
postgres:
condition: service_healthy
networks:
— frontend
— backend
restart: unless-stopped

postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
— postgres_data:/var/lib/postgresql/data
networks:
— backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] interval: 10s
retries: 5
restart: unless-stopped

volumes:
postgres_data:

networks:
frontend:
backend:
[/yaml]

Шаг 3 — Dev-конфигурация docker-compose.dev.yml (override):

[yaml] services:
backend:
build:
context: ./backend
target: development
environment:
— NODE_ENV=development
volumes:
— ./backend:/app
— /app/node_modules
command: npm run dev

postgres:
ports:
— "5432:5432" # В dev пробрасываем порт наружу для подключения из IDE
[/yaml]

Шаг 4 — Запуск:


# Продакшен
docker compose up -d

# Разработка (объединяет оба файла)
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d

Шаг 5 — Проверка:


# Статус контейнеров
docker compose ps

# Логи
docker compose logs -f

# Проверить что база поднялась
docker compose exec postgres pg_isready -U appuser

Шаг 6 — Перезапуск после обновления кода:


# Пересобрать и перезапустить только backend
docker compose up -d --build backend

Шаг 7 — Полная остановка:


# Остановить без потери данных
docker compose down

# Остановить и удалить всё включая volumes (осторожно!)
docker compose down -v

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

Порт уже занят


Error: Bind for 0.0.0.0:80 failed: port is already allocated

# Найти что занимает порт
sudo ss -tlnp | grep :80
# Или
sudo lsof -i :80

# Остановить процесс или изменить порт в docker-compose.yml

network not found


Error: network myapp_frontend declared as external, but could not be found

# Создать сеть вручную
docker network create myapp_frontend

# Или убрать external: true из конфига

Неправильный YAML — самая частая причина «ничего не работает»


# Проверить синтаксис файла
docker compose config

# Типичные ошибки:
# - Табы вместо пробелов
# - Неправильные отступы
# - Кавычки не закрыты

permission denied при bind mount


# Проблема: контейнер запускается под другим пользователем
# Решение 1: задать uid/gid
services:
  backend:
    user: "1000:1000"

# Решение 2: изменить права на хосте
sudo chown -R 1000:1000 ./backend

Контейнер перезапускается в цикле


# Посмотреть почему падает
docker compose logs backend

# Проверить exit code
docker compose ps -a

depends_on не помогает — сервис стартовал, но не готов

depends_on по умолчанию ждёт только запуска контейнера, не его готовности. Используйте healthcheck + condition: service_healthy — пример в файле выше.

Конфликт версий образа


# Всегда указывайте тег образа явно
image: postgres:16-alpine   # Хорошо
image: postgres              # Плохо - может обновиться и сломать всё

# Обновить образы принудительно
docker compose pull
docker compose up -d

Чек-лист Docker Compose

  • ☐ Установлен Docker Compose v2 (docker compose version)
  • ☐ Секреты вынесены в .env файл, не захардкожены в yml
  • .env добавлен в .gitignore
  • ☐ Для БД используется named volume, не bind mount
  • ☐ Настроен healthcheck для БД
  • depends_on с condition: service_healthy для зависимых сервисов
  • ☐ Образы указаны с явным тегом (postgres:16, не postgres:latest)
  • ☐ Настроен restart: unless-stopped для продакшена
  • ☐ Проверен конфиг командой docker compose config
  • ☐ Сети разделены для изоляции сервисов
  • ☐ Настроен бэкап данных из volumes

Заключение

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

Используйте Docker Compose когда: одна машина, несколько сервисов, нужна простота и скорость.

Переходите на Kubernetes когда: нужна высокая доступность, несколько серверов, сложный auto-scaling или вы уже чувствуете потолок Compose.

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

Андрей Анатольевич
Author: Андрей Анатольевич

Руководитель ИТ / Кризис-менеджер 25 лет в IT: от инженера в МегаФоне до руководителя отдела. Знаю, как выглядит бардак: нестабильные сети, устаревшая инфраструктура, конфликты в команде, раздутые сроки. Помогаю бизнесу выходить из кризиса: навожу порядок в легаси, стабилизирую то, что разваливается, выстраиваю прогнозируемые процессы. Не раз возвращал к жизни ИТ-структуры — знаю цену хаосу. 📍 Ищу проект для полной реорганизации / стабилизации. 📬 Telegram: @over_dude ✉️ mail@it-apteka.com

Оставайтесь на связи

Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.

Подписаться на IT-Аптеку →

Мы ВКонтакте

IT-Аптека — советы, новости и помощь рядом.

Вступить в группу ВКонтакте →
Поделитесь:

Оставьте комментарий

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

Прокрутить вверх