Compose-структура для домашнего сервера
Когда сервисов в docker compose становится больше двух-трёх, общий compose.yml превращается в простыню, в которой неудобно работать. Зайти в папку сервиса, поднять или обновить только его, не дёргая остальные — удобнее, чем лазить по корневому compose с грепом. Поэтому раскладываю каждый сервис в отдельную папку.
Структура
В /opt/services/ лежит так:
/opt/services/
├── vaultwarden/
│ ├── compose.yml
│ ├── .env
│ └── data/ # bind mount, БД и аттачи
├── memos/
│ ├── compose.yml
│ ├── .env
│ └── data/
└── paperless/
├── compose.yml
├── .env
├── consume/ # папка для входящих документов
└── data/
Имя папки = логическое имя сервиса. По названию сразу видно, где лежат данные конкретного приложения — удобно при бэкапе и при ответе на вопрос «куда оно вообще пишет».
Bind mounts vs named volumes
Bind mounts (./data:/data) против named volumes — холивар на пустом месте, но я выбрал bind mounts. Причины:
du -sh /opt/services/<service>/dataпоказывает размер БД мгновенно. Через named volume пришлось бы лезть вdocker volume inspect.- Бэкап тривиален:
tar,rsync, что угодно — путь известен. - Если docker engine упал или его снесли при апгрейде, данные на месте, а не в
/var/lib/docker/volumes/.
Минус один — UID/GID. Контейнер часто бежит от UID 1000, а папку создал root. Permission denied при первом запуске. Лечится одной командой перед up -d:
mkdir -p data && chown 1000:1000 data
Шаблон compose.yml
services:
app:
image: vaultwarden/server:1.32.5
container_name: vaultwarden
restart: unless-stopped
ports:
- "127.0.0.1:8080:80"
env_file: .env
volumes:
- ./data:/data
Несколько мелочей, которые делаю одинаково везде.
restart: unless-stopped вместо always. Разница в одной ситуации: я остановил сервис вручную (под бэкап, под отладку), потом перезагрузился сервер. С always сервис поднимется сам — это плохо. С unless-stopped не поднимется.
ports: "127.0.0.1:8080:80" — bind на loopback, не на 0.0.0.0. Дефолтный синтаксис "8080:80" биндит на все интерфейсы, и сервис оказывается в публичном интернете. Доступ извне — отдельным шагом, через reverse proxy или ssh-туннель.
Версия образа конкретная (:1.32.5), не :latest. С :latest следующий docker compose pull может затащить мажор с breaking changes, и сервис ляжет, когда не ждёшь. Обновляю руками: открыл changelog, поднял тег, перезапустил.
container_name задан явно — иначе будет vaultwarden-app-1, что неудобно в docker ps и docker logs.
Грабли с переименованием папки
Compose v2 берёт имя проекта из имени папки. Если переименую vaultwarden/ в vw/ и сделаю docker compose down — старый контейнер останется в namespace vaultwarden, а команда будет смотреть на vw. Лечится явным -p:
docker compose -p vaultwarden down
Сейчас наткнулся на одну штуку — лучше папки не переименовывать. Если очень надо — сначала down старым namespace, потом переименование, потом up.
Дальше
База готова, можно наполнять /opt/services/ сервисами. Начну с Vaultwarden — давно хочется съехать с облачного Bitwarden.