Серверная под лестницей

Что я тут поднимаю и роняю

Compose-структура для домашнего сервера

4 мая 2026

Когда сервисов в 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. Причины:

Минус один — 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.