<!-- META DESCRIPTION (до 155 символов): Как установить Docker Compose на Ubuntu, Debian, CentOS и macOS. Команды, примеры docker-compose.yml, типичные ошибки и их решения от практикующего SRE. -->
Установка Docker Compose на Linux: от нуля до рабочего стека
1. Диагноз: зачем вообще это нужно
Поднял приложение. Nginx стоит отдельно, база — отдельно, Redis — третьей командой в другом терминале. Через неделю добавился Celery. Потом RabbitMQ. Потом ты забыл, с какими флагами запускал базу, и она поднялась без volume. Данные потеряны. Знакомо?
Docker Compose решает именно это. Весь стек описывается в одном YAML-файле. Одна команда — весь стек поднят. Ещё одна — убран. Никаких bash-скриптов на 400 строк, которые никто не трогал с 2019 года и которые «просто работают.
Что будет в статье:
- Установка на Ubuntu/Debian, CentOS/RHEL, macOS и Windows
- Разница между старым docker-compose и новым docker compose
- Настройка прав без sudo
- Три готовых примера docker-compose.yml — от простого к сложному
- Типичные ошибки и их решения
Времени нужно: 15-20 минут на установку и первый запуск. Предполагается, что Docker Engine уже стоит. Если нет — сначала туда.
2. Базовые требования
Перед тем как что-то ставить — проверь среду. Это не занудство, это экономия времени.
Системные требования
| Компонент | Минимум | Рекомендуется |
|---|---|---|
| Docker Engine | 19.03.0+ | последняя стабильная |
| ОС | 64-bit Linux/macOS/Windows | Ubuntu 22.04 LTS |
| RAM | 2 ГБ | 4+ ГБ |
| Диск | 2 ГБ свободно | 20+ ГБ |
| Права | sudo | пользователь в группе docker |
Проверка Docker Engine
Сначала убедись, что Docker Engine работает:
docker --version
docker ps
sudo systemctl status docker
Должно быть: версия Docker и статус «active (running)». Если Docker не запущен:
sudo systemctl start docker
sudo systemctl enable docker
Команда enable нужна для автозапуска при загрузке системы. Без неё после ребута будешь гадать, почему контейнеры не поднялись.
Совместимость версий
| ОС | Версия | Метод установки | Compose версия |
|---|---|---|---|
| Ubuntu | 20.04, 22.04, 24.04 | apt plugin | v2.x |
| Debian | 11, 12 | apt plugin | v2.x |
| CentOS/RHEL | 8, 9 | dnf plugin | v2.x |
| Fedora | 38+ | dnf plugin | v2.x |
| macOS | 12+ | Docker Desktop / brew | v2.x |
| Windows | 10/11 + WSL2 | Docker Desktop | v2.x |
На момент публикации актуальна версия Compose v2.27+. Перед установкой проверяй свежие релизы на GitHub.
3. Архитектура: как это работает
Docker Compose читает твой YAML, создаёт контейнеры, настраивает сеть между ними и запускает всё в нужном порядке. Контейнеры видят друг друга по именам сервисов — db, redis, web — без hardcode IP-адресов.
4. Установка: выбери свою ОС
Ubuntu / Debian — способ 1: через apt (рекомендую)
Если Docker ставился через официальный репозиторий — плагин уже может быть установлен. Проверь:
docker compose version
Видишь версию — можно пропускать. Нет — ставим:
sudo apt update
sudo apt install docker-compose-plugin
docker compose version
Должно вывести что-то вроде: Docker Compose version v2.27.1
Ubuntu / Debian — способ 2: ручная установка
Нужна конкретная версия или репозиторий недоступен — берём бинарник напрямую:
mkdir -p ~/.docker/cli-plugins/
DOCKER_COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep 'tag_name' | cut -d'"' -f4)
curl -SL "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64" \
-o ~/.docker/cli-plugins/docker-compose
chmod +x ~/.docker/cli-plugins/docker-compose
docker compose version
Скрипт сам определяет последнюю версию через GitHub API. Не нужно лезть на сайт руками.
CentOS / RHEL / Fedora
sudo dnf update
sudo dnf install docker-compose-plugin
docker compose version
Для старых версий CentOS 7 через прямое скачивание:
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64" \
-o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
docker-compose --version
macOS
Docker Desktop для Mac уже включает Compose. Поставил Desktop — Compose есть. Если нужна отдельная установка через Homebrew:
brew install docker-compose
Для Apple Silicon (M1/M2/M3) — отдельный бинарник:
mkdir -p ~/.docker/cli-plugins
curl -SL "https://github.com/docker/compose/releases/latest/download/docker-compose-darwin-aarch64" \
-o ~/.docker/cli-plugins/docker-compose
chmod +x ~/.docker/cli-plugins/docker-compose
docker compose version
Windows
Docker Desktop для Windows включает Compose из коробки. Если нужна ручная установка через PowerShell:
mkdir -p "$env:USERPROFILE\.docker\cli-plugins"
Invoke-WebRequest "https://github.com/docker/compose/releases/latest/download/docker-compose-windows-x86_64.exe" `
-OutFile "$env:USERPROFILE\.docker\cli-plugins\docker-compose.exe"
docker compose version
Если используешь WSL2 — рекомендую. Устанавливай Compose внутри WSL по инструкции для Ubuntu.
5. Настройка прав — убираем sudo
По умолчанию Docker требует sudo. Это безопасно, но неудобно. Добавляем пользователя в группу docker:
sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker
docker ps
Если не хочешь добавлять пользователя в группу — создай алиасы:
echo "alias dc='sudo docker compose'" >> ~/.bashrc
source ~/.bashrc
dc up -d
6. Структура docker-compose.yml
Прежде чем запускать примеры — понимай, что читаешь. Базовая структура файла:
version: '3.8'
services:
имя-сервиса:
image: образ:тег
ports:
- "порт-хоста:порт-контейнера"
volumes:
- путь-хоста:путь-контейнера
environment:
- ПЕРЕМЕННАЯ=значение
depends_on:
- другой-сервис
restart: unless-stopped
networks:
имя-сети:
driver: bridge
volumes:
имя-тома:
driver: local
Ключевые секции: services — обязательна, networks и volumes — опциональны. В services описываешь каждый контейнер. В networks — как они между собой общаются. В volumes — где хранятся данные.
Один критический момент: YAML использует пробелы, не табы. Два пробела на уровень. Перепутаешь — получишь синтаксическую ошибку и полчаса дебага.
7. Три практических примера
Пример 1: Nginx со статикой
Простейший старт. Создай папку проекта и файл docker-compose.yml:
version: '3.8'
services:
web:
image: nginx:alpine
container_name: my-nginx
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
restart: unless-stopped
Создай папку html и тестовый файл:
mkdir html
echo 'Docker Compose работает
' > html/index.html
docker compose up -d
curl http://localhost:8080
Результат: HTML-ответ в терминале и страница в браузере по адресу localhost:8080. Флаг -d запускает в фоне.
Пример 2: WordPress + MySQL
Классика жанра. Два сервиса с зависимостью и персистентными данными:
version: '3.8'
services:
db:
image: mysql:8.0
container_name: wp-db
volumes:
- db_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
restart: unless-stopped
networks:
- wp-net
wordpress:
depends_on:
- db
image: wordpress:latest
container_name: wp-app
ports:
- "8000:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
WORDPRESS_DB_NAME: wordpress
volumes:
- wp_data:/var/www/html
restart: unless-stopped
networks:
- wp-net
networks:
wp-net:
driver: bridge
volumes:
db_data:
wp_data:
Создай файл .env рядом с docker-compose.yml:
MYSQL_ROOT_PASSWORD=strong_root_pass_here
MYSQL_PASSWORD=strong_wp_pass_here
Добавь .env в .gitignore — обязательно. Пароли в публичных репозиториях это не инцидент, это катастрофа.
echo ".env" >> .gitignore
docker compose up -d
docker compose ps
Через 30-40 секунд (пока MySQL инициализируется) открывай localhost:8000 — будет мастер установки WordPress. Данные сохраняются в именованных volumes и переживут пересоздание контейнеров.
Пример 3: Fullstack — React + Node.js + PostgreSQL + Redis
Реальный рабочий стек с healthcheck-ами и правильными зависимостями:
version: '3.8'
services:
postgres:
image: postgres:15-alpine
container_name: app-postgres
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: appdb
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- app-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: app-redis
command: redis-server --appendonly yes
volumes:
- redis_data:/data
networks:
- app-net
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
backend:
build:
context: ./backend
container_name: app-backend
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
NODE_ENV: production
DATABASE_URL: postgresql://appuser:${DB_PASSWORD}@postgres:5432/appdb
REDIS_URL: redis://redis:6379
JWT_SECRET: ${JWT_SECRET}
ports:
- "3001:3000"
networks:
- app-net
restart: unless-stopped
frontend:
build:
context: ./frontend
container_name: app-frontend
depends_on:
- backend
ports:
- "3000:80"
networks:
- app-net
restart: unless-stopped
nginx:
image: nginx:alpine
container_name: app-nginx
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
ports:
- "80:80"
depends_on:
- frontend
- backend
networks:
- app-net
restart: unless-stopped
networks:
app-net:
driver: bridge
volumes:
postgres_data:
redis_data:
Вот тут важно: condition: service_healthy в depends_on. Без healthcheck сервис запускается как только контейнер стартовал — но база данных ещё может быть не готова принимать подключения. С healthcheck backend ждёт реальной готовности PostgreSQL. Это решает 90% проблем с «база не успела подняться».
docker compose up -d --build
docker compose ps
docker compose logs -f backend
8. Порты сервисов
| Сервис | Порт контейнера | Порт хоста (пример) | Протокол |
|---|---|---|---|
| Nginx | 80, 443 | 80, 443 | HTTP/HTTPS |
| PostgreSQL | 5432 | 5432 | TCP |
| MySQL/MariaDB | 3306 | 3306 | TCP |
| Redis | 6379 | 6379 | TCP |
| Node.js | 3000 | 3001 | HTTP |
| Flask/Django | 5000 / 8000 | 5000 / 8000 | HTTP |
| Prometheus | 9090 | 9090 | HTTP |
| Grafana | 3000 | 3000 | HTTP |
9. Проверка установки и работы
После установки Compose и первого запуска — проверяй так:
# Версия Compose
docker compose version
# Статус всех сервисов
docker compose ps
# Логи в реальном времени
docker compose logs -f
# Логи конкретного сервиса
docker compose logs -f web
# Ресурсы контейнеров
docker stats --no-stream
# Проверка конфига на ошибки
docker compose config
Команда docker compose config особенно полезна — она парсит твой YAML, подставляет переменные из .env и показывает итоговый конфиг. Если там ошибка синтаксиса — найдёт сразу.
10. Безопасность
Несколько правил, которые обязательны в продакшене.
Файрвол
# UFW - разреши только нужные порты
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Непривилегированный пользователь в контейнере
# В Dockerfile
RUN addgroup -g 1000 appgroup && \
adduser -D -u 1000 -G appgroup appuser
USER appuser
# В docker-compose.yml
services:
app:
user: "1000:1000"
Ротация логов
services:
app:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Без ротации логи заполнят диск. Это случается быстрее, чем думаешь.
Лимиты ресурсов
services:
app:
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '0.5'
memory: 512M
11. Резервное копирование
Данные в named volumes переживают пересоздание контейнеров. Но не пересоздание volumes.
# Бэкап PostgreSQL
docker compose exec db pg_dump -U appuser appdb > backup_$(date +%Y%m%d_%H%M%S).sql
# Бэкап MySQL
docker compose exec db mysqldump -u wpuser -p${MYSQL_PASSWORD} wordpress > backup_$(date +%Y%m%d_%H%M%S).sql
# Бэкап volume целиком
docker run --rm \
-v имя_проекта_volume:/data \
-v $(pwd):/backup \
alpine tar czf /backup/volume_backup.tar.gz -C /data .
# Восстановление PostgreSQL
docker compose exec -T db psql -U appuser appdb < backup.sql
Что бэкапить: volumes с данными баз, volumes с файлами приложения, сам docker-compose.yml и .env.example (не .env с паролями). Частота: для баз - ежедневно минимум. Для файлов приложения - при каждом деплое. Хранить: не на том же сервере.
12. Обновление Docker Compose
# Через apt
sudo apt update && sudo apt upgrade docker-compose-plugin
# Проверка текущей версии
docker compose version
# Ручное обновление бинарника
DOCKER_COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep 'tag_name' | cut -d'"' -f4)
curl -SL "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64" \
-o ~/.docker/cli-plugins/docker-compose
chmod +x ~/.docker/cli-plugins/docker-compose
docker compose version
Перед обновлением: проверь changelog на предмет breaking changes. Сделай бэкап данных. Протестируй на staging. Compose v2 обратно совместим с синтаксисом v1 файлов, но иногда бывают нюансы.
13. Миграция с docker-compose v1 на v2
Если ещё используешь старый docker-compose с дефисом - переходи. Он больше не получает обновлений.
Основное изменение - команды:
# Старый синтаксис (v1)
docker-compose up -d
docker-compose logs
docker-compose down
# Новый синтаксис (v2)
docker compose up -d
docker compose logs
docker compose down
Для плавного перехода создай алиас:
echo "alias docker-compose='docker compose'" >> ~/.bashrc
source ~/.bashrc
Конфиги docker-compose.yml полностью совместимы. Менять файлы не нужно. Проверь конфиг на совместимость:
docker compose config
Если команда выполнилась без ошибок и показала итоговый конфиг - всё в порядке.
14. Шпаргалка по командам
# Запуск
docker compose up -d # фоновый режим
docker compose up --build # с пересборкой образов
docker compose up --force-recreate # пересоздать контейнеры
# Остановка
docker compose down # остановить и удалить контейнеры
docker compose down -v # + удалить volumes (осторожно!)
docker compose stop # только остановить
# Информация
docker compose ps # статус сервисов
docker compose ps -a # включая остановленные
docker compose logs -f # логи всех сервисов
docker compose logs --tail=100 web # последние 100 строк
docker compose top # процессы в контейнерах
docker compose stats # использование ресурсов
# Выполнение команд
docker compose exec web bash # интерактивная оболочка
docker compose exec web ls -la # выполнить команду
docker compose run --rm web python test.py # одноразовый контейнер
# Сборка
docker compose build --no-cache # пересборка без кэша
docker compose pull # обновить образы
docker compose config # проверить конфиг
# Масштабирование
docker compose up -d --scale web=3 # 3 инстанса web
15. Осложнения: типичные ошибки и решения
command not found: docker compose
Причина: плагин не установлен или не в PATH.
# Проверяем где плагины
ls ~/.docker/cli-plugins/
ls /usr/lib/docker/cli-plugins/
# Устанавливаем заново
sudo apt install docker-compose-plugin
# Проверяем
docker compose version
permission denied при запуске
Причина: пользователь не в группе docker.
# Проверяем группы текущего пользователя
groups $USER
# Добавляем в группу docker
sudo usermod -aG docker $USER
# Применяем без перелогина
newgrp docker
# Проверяем
docker ps
Cannot connect to Docker daemon
Причина: Docker Engine не запущен.
sudo systemctl status docker
sudo systemctl start docker
sudo systemctl enable docker
Контейнер постоянно рестартует
Причина: ошибка в приложении или неправильный healthcheck.
# Смотрим логи падающего сервиса
docker compose logs --tail=50 имя-сервиса
# Смотрим статус с причиной
docker compose ps
# Запускаем без restart для дебага
docker compose run --rm имя-сервиса
depends_on не помогает - сервис стартует раньше базы
Причина: depends_on без condition только ждёт старта контейнера, не готовности сервиса внутри.
# Неправильно - просто ждёт старта контейнера
depends_on:
- db
# Правильно - ждёт healthcheck
depends_on:
db:
condition: service_healthy
Плюс добавь healthcheck в сервис db (пример для PostgreSQL выше в статье).
Порты заняты
Причина: на хосте уже что-то слушает этот порт.
# Найти что занимает порт
sudo ss -tlnp | grep :8080
sudo lsof -i :8080
# Убить процесс или поменять порт в compose
ports:
- "8081:80" # поменяли 8080 на 8081
Volume не монтируется - файлы не видны
Причина: неправильный путь или проблемы с правами.
# Проверяем абсолютный путь
pwd
ls -la ./html
# Смотрим что примонтировано в контейнере
docker compose exec web ls -la /usr/share/nginx/html
# Проверяем права
ls -la ./html/index.html
chmod 644 ./html/index.html
Ошибка синтаксиса YAML
Причина: табы вместо пробелов или неправильные отступы.
# Проверяем конфиг
docker compose config
# Онлайн-валидатор: yamllint.com
# Или через CLI
sudo apt install yamllint
yamllint docker-compose.yml
16. Альтернативные решения
Docker Compose - не единственный вариант. Для понимания картины:
| Инструмент | Когда использовать | Сложность |
|---|---|---|
| Docker Compose | 1 сервер, dev/staging/простой prod | низкая |
| Docker Swarm | несколько серверов, знакомый синтаксис Compose | средняя |
| Kubernetes | большой кластер, автомасштабирование, сложные требования | высокая |
| Podman Compose | нужен rootless по умолчанию, RHEL-среда | низкая |
| Nomad | гетерогенная инфраструктура, не только контейнеры | средняя |
Kubernetes - мощный инструмент. Но его сложность оправдана не всегда. Если Compose покрывает твои задачи - используй Compose. Я знаю продакшен-системы с миллионами запросов в сутки на Docker Compose. Всё нормально работает.
17. Профилактика: как не сломать
Несколько правил, которые сэкономят тебе нервы в будущем.
Первое - всегда указывай конкретный тег образа в продакшене. latest сегодня и latest через три месяца - это разные образы. Обновления без тестирования прилетают в самый неподходящий момент.
Второе - именованные volumes для данных. Не bind-mount к случайной папке, а нормальный named volume. Данные должны жить дольше контейнеров.
Третье - .env.example в репозитории, .env в .gitignore. Всегда. Без исключений. Одна утечка пароля к базе данных в публичный репозиторий - и ты потратишь день на смену всех учёток и проверку логов.
Четвёртое - настрой ротацию логов. Диск заполняется быстрее, чем думаешь, особенно если приложение многословное.
Пятое - проверяй конфиг перед деплоем командой docker compose config. Поймаешь синтаксические ошибки раньше, чем они проявятся в продакшене в три часа ночи.
Шестое - документируй нестандартные решения прямо в комментариях docker-compose.yml. Ты не запомнишь через полгода, почему именно так.
# Плохо - загадочная конфигурация
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
# Хорошо - понятно зачем
# appendonly yes - WAL для надёжности данных
# maxmemory 256mb - ограничение памяти для кэша
# allkeys-lru - вытеснение редко используемых ключей при достижении лимита
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
18. FAQ
Почему docker compose вместо docker-compose?
В версии 2.x Docker Compose переехал из отдельной утилиты в плагин Docker CLI. Команда стала docker compose (без дефиса). Старый docker-compose (с дефисом, версия 1.x) устарел и больше не получает обновлений. Создай алиас alias docker-compose='docker compose' если привык к старому синтаксису - файлы конфигурации совместимы.
Как проверить, что Docker Compose установлен правильно?
# Проверка версии
docker compose version
# Проверка работоспособности - запускаем тестовый контейнер
docker compose -f - up -d <<EOF
version: '3'
services:
test:
image: hello-world
EOF
# Смотрим логи
docker compose logs test
# Убираем
docker compose down
Что если сервисы поднялись, но не видят друг друга по сети?
Скорее всего, они в разных сетях или сеть не указана. Проверь:
# Какие сети созданы
docker network ls | grep название_проекта
# К каким сетям подключён контейнер
docker inspect имя-контейнера | grep -A 20 '"Networks"'
# Проверь ping между контейнерами
docker compose exec web ping db
Если сервисы в одном docker-compose.yml и сеть не указана явно - Compose создаёт default сеть и подключает все сервисы к ней автоматически. Проблема возникает когда ты явно указал networks для одних сервисов, но забыл про другие.
Как откатиться если обновление сломало приложение?
# Остановить новую версию
docker compose down
# Откатить образ в docker-compose.yml к предыдущей версии
# image: myapp:v1.2.3 (было v1.2.4)
# Поднять предыдущую версию
docker compose up -d
# Если нет конкретных тегов - ищи предыдущий образ локально
docker images myapp
docker tag myapp:old-hash myapp:rollback
Именно поэтому в продакшене никогда не используй latest - невозможно откатиться к "прошлому latest".
Почему данные исчезают после docker compose down -v?
Флаг -v удаляет volumes вместе с контейнерами. Это деструктивная операция. Используй docker compose down без флага -v для сохранения данных. Volumes удаляются только явно: через docker compose down -v или docker volume rm.
19. Итог
Docker Compose установлен. Первый стек поднят. Теперь у тебя есть инструмент, который описывает инфраструктуру как код, запускает всё одной командой и сохраняет данные между перезапусками.
Следующий шаг - добавь мониторинг. Prometheus + Grafana + Node Exporter поднимаются за 10 минут через Compose и дают полную картину того, что происходит с твоими контейнерами. Без мониторинга ты узнаёшь о проблемах от пользователей.
Оставайтесь на связи
Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.
Подписаться на IT-Аптеку →


