mysurik.ru

Как я подружил Docker Compose и перестал запускать контейнеры руками

Docker Compose оркестрация контейнеров

Сколько можно писать docker run

Когда я только начал использовать Docker, каждый контейнер я запускал руками через docker run с кучей флагов. Для nginx это выглядело так:

docker run -d --name nginx -p 80:80 -v /data/nginx:/etc/nginx nginx:alpine

Вроде несложно. Но когда у тебя 10 контейнеров, а сервер перезагружается — вспоминать все команды и флаги тот ещё квест. Особенно если контейнер с MySQL требует проброса портов, путей, переменных окружения и паролей.

Docker Compose я долго игнорировал. Думал, это ещё одна прослойка, которая будет жрать ресурсы и усложнять жизнь. Как же я ошибался.

Первый Compose-файл

Начал с малого: nginx + PHP-FPM + MySQL. В докер-компоузе это выглядит как три сервиса в одном файле. Написал docker-compose.yml, запустил docker compose up -d — и всё поднялось само. Никаких длинных команд, ничего не надо вспоминать.

Я прямо почувствовал облегчение. Все настройки в одном файле, который можно закоммитить в git. Перезагрузил сервер — запустил docker compose up -d и все контейнеры на месте.

Как я строю Compose-файлы сейчас

Давай покажу, как выглядит мой типичный docker-compose.yml. Не какой-то абстрактный, а реальный, с которым я работаю каждый день:

version: '3.8'

services:
app:
build: .
restart: unless-stopped
ports:
- "3000:3000"
depends_on:
db:
condition: service_healthy
env_file:
- .env
networks:
- app-net

db:
image: postgres:16-alpine
restart: unless-stopped
volumes:
- pgdata:/var/lib/postgresql/data
env_file:
- .env
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-net

redis:
image: redis:7-alpine
restart: unless-stopped
networks:
- app-net

volumes:
pgdata:

networks:
app-net:

Пару моментов, до которых я дошёл своим горбом. restart: unless-stopped — обязательно. Без этого контейнер после перезагрузки сервера не стартанёт, и ты узнаешь об этом только когда сайт упадёт. Было такое, да.

healthcheck на базе данных — спасение. Раньше приложение стартовало, пыталось коннектнуться к БД, а postgres ещё не поднялся. Падало с ошибкой. Docker Compose ждал depends_on, но не ждал, что БД готова принимать соединения. Healthcheck решает это — приложение стартует только когда postgres ответил на pg_isready.

Сети — это магия, которую я не оценил сразу

Когда я запускал контейнеры через docker run, каждый контейнер был сам по себе. Чтобы nginx видел PHP-FPM, я вручную создавал сеть и подключал контейнеры. Забывал — и гадал, почему 502 Bad Gateway.

В Compose каждый проект получает свою сеть автоматически. Контейнеры видят друг друга по имени сервиса. Не по IP, который может измениться, а по имени. То есть из контейнера app я стучусь к БД как postgres://db:5432/myapp. Никаких IP, никаких --link, всё из коробки.

Если нужно несколько сетей — пожалуйста. Базу данных я прячу во внутреннюю сеть, а nginx выставляю наружу. Две сети в одном compose-файле, и микросервисы изолированы как надо.

Переменные окружения и секреты

Пароли и ключи я сначала писал прямо в docker-compose.yml. Потом закоммитил в git — и понял, что пароли утекли в репозиторий. Пришлось переезжать на .env файлы. Теперь все секреты в .env, а .env добавлен в .gitignore.

Но есть нюанс. .env файл подхватывается автоматически только если лежит рядом с docker-compose.yml. Если проект лежит в подпапке или ты запускаешь compose из другого каталога — переменные не подтянутся. Я обжёгся на этом, когда перенёс проект на сервер и полчаса тупил, почему пароль от БД не читается.

Теперь я в docker-compose.yml явно указываю env_file: .env в каждом сервисе, которому нужны секреты. Или использую docker compose --env-file .env.prod up -d для разных окружений. Да, можно держать .env.dev, .env.prod и переключаться флагом. Очень удобно, когда на сервере пароли от БД настоящие, а локально — тестовые.

Volumes — мусорка, которую надо контролировать

Если удалить контейнер через docker compose down без флага -v, volume остаются висеть мёртвым грузом. Через полгода я нашёл у себя десяток безымянных томов на 20 гигов — пришлось чистить вручную.

Теперь я раз в месяц запускаю docker system df чтобы посмотреть, сколько места занято. Потом docker volume prune — и гигиена наведена.

Но тут тоже грабли. Если у тебя volume именованный (как pgdata в примере выше) — prune его не тронет, потому что на него ссылается compose-файл. А если безымянный — docker compose down -v его удалит вместе с контейнерами. Надо чётко понимать, какие volume тебе нужны после удаления проекта, а какие — нет.

Profiles — то, чего мне не хватало

У меня есть проект, где в одном compose-файле описаны сервисы для разработки и продакшена. Redis, БД, само приложение — и плюс adminer, mailhog, jaeger для локальной разработки. Запускать их на проде не нужно.

Раньше я держал два файла: docker-compose.yml и docker-compose.dev.yml. Запускал docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d. Работало, но неудобно.

Потом открыл для себя profiles. Теперь adminer и mailhog помечены profiles: ["dev"], и они запускаются только если я явно укажу docker compose --profile dev up -d. На проде эти сервисы просто игнорируются, хотя всё лежит в одном файле. Красота.

Что я теперь храню в Compose

Каждый проект у меня теперь начинается с docker-compose.yml. Там описаны все сервисы, их зависимости, сети, volumes. Если проект использует БД — она стартует до основного приложения, потому что depends_on.

Отдельный Compose-файл для мониторинга: Prometheus + Grafana + Node Exporter. Описал один раз — запускаю на любом сервере одной командой.

WordPress тоже через Compose. Сам WP, MySQL, phpMyAdmin. Три сервиса, один файл, одна команда для запуска.

Docker Compose v1 vs v2 — боль

На старом Docker Compose v1 команда была docker-compose (с дефисом). На новом v2 — docker compose (без дефиса). Я долго тупил, почему на одном сервере команда работает, а на другом нет.

На Ubuntu 22.04+ docker-compose v1 надо ставить отдельно, а v2 уже идёт вместе с Docker Engine. Но alias по умолчанию не стоит. Я прописал в ~/.bashrc: alias docker-compose='docker compose' и забыл про проблему.

Ещё из нового — docker compose v2 поддерживает docker compose watch. Это как hot-reload для контейнеров. Меняешь код на хосте — compose пересобирает образ или копирует файлы в контейнер. Для разработки — имба.

Мой вердикт

Я теперь не представляю работу с Docker без Compose. Даже один контейнер проще описать в YAML, чем каждый раз вспоминать флаги docker run. А если проект разрастается до 5-10 сервисов — Compose единственный адекватный способ не сойти с ума.

Один файл на проект. Закоммитил в git — и любой разработчик поднимет окружение одной командой. Сервер упал — docker compose up -d и всё работает. Переезжаешь на другой хост — скопировал папку, запустил, готово.

Сейчас я запускаю Docker Compose раз в неделю только чтобы накатить обновления. Не ввожу команды по 10 минут, не вспоминаю порты и пути. Просто docker compose pull && docker compose up -d. Минута — и всё обновлено.

Ваш комментарий

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