Docker Compose на домашнем сервере: первый сервис и его обновление
Ставим Docker из официального репозитория, ограничиваем логи, поднимаем nginx через compose.yaml и обновляем его одной командой. И главный подвох: Docker открывает порты в обход ufw — проверено вживую.
Чтобы сервисы было удобно сопровождать и бэкапить, их на домашнем сервере лучше сразу ставить в контейнеры. Тогда каждый сервис живёт в своей коробке со своими зависимостями, не мешает соседям, обновляется одной командой и переносится на другую машину вместе с одним каталогом.
Это вторая часть цикла «Домашний сервер без боли». В первой мы навели базовую гигиену. Теперь ставим Docker и поднимаем первый сервис через Docker Compose — так, как его удобно будет сопровождать годами.
Что получим и что нужно заранее
Результат: Docker из официального репозитория и первый сервис — веб-сервер nginx, описанный одним файлом. Данные лежат в понятном каталоге, логи не съедают диск, а порт не торчит наружу в обход брандмауэра.
Нужно: Debian 13 или 12 после первой части цикла: пользователь с sudo, включённый ufw. Все команды ниже проверены на чистом Debian 13 (на Debian 12 они те же).
Что тут происходит: контейнер и Compose
Контейнер — это изолированный процесс со своей файловой системой. Внутри — программа и всё, что ей нужно для работы, снаружи — только то, что вы явно пробросили: порт, каталог с данными, переменные. Образ — заготовка, из которой контейнер создаётся.
Docker Compose — способ описать контейнеры текстом. Вместо длинных команд с десятком ключей у вас файл compose.yaml: какой образ, какие порты, какие каталоги. Этот файл — документация к сервису, которую невозможно забыть обновить.
Шаг 1. Ставим Docker из официального репозитория
В Debian есть свой пакет docker.io, но он заметно отстаёт от актуальных версий, а современный Compose (команда docker compose) идёт как дополнение из репозитория самого Docker. Ставим оттуда.
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
Ставим утилиты для HTTPS, создаём каталог для ключей и скачиваем ключ, которым Docker подписывает свои пакеты.
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
Добавляем репозиторий Docker для вашей версии Debian. Команда сама подставит архитектуру и кодовое имя системы.
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Ставим сам Docker и дополнение Compose.
sudo docker run --rm hello-world
Проверяем: Docker скачает крошечный тестовый образ, запустит его и выведет приветствие. Ключ --rm удаляет контейнер сразу после работы.
Про группу docker. Во многих инструкциях советуют добавить себя в неё, чтобы не писать sudo. Знайте цену: участник группы docker фактически получает права root на всей машине. На домашнем сервере, где вы один, это допустимо, но решение должно быть осознанным.
Шаг 2. Логи — сразу с ограничением
По умолчанию Docker хранит логи контейнеров без ограничения размера. Болтливый сервис за полгода может забить ими диск. Ограничиваем заранее:
sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
EOF
sudo systemctl restart docker
Каждый контейнер хранит не больше трёх файлов логов по 10 МБ. Настройка действует для контейнеров, созданных после перезапуска Docker, поэтому делаем её до первых сервисов.
Шаг 3. Первый сервис
Заведём для сервисов общий каталог — каждому свой подкаталог. Это пригодится и для бэкапа: всё, что нужно сохранить, будет лежать в /srv.
sudo mkdir -p /srv/stacks/web/html
echo '<h1>Сервер работает</h1>' | sudo tee /srv/stacks/web/html/index.html
cd /srv/stacks/web
Каталог сервиса и тестовая страница, которую будет отдавать nginx.
sudo tee compose.yaml >/dev/null <<'EOF'
services:
web:
image: nginx:1.30-alpine
restart: unless-stopped
ports:
- "127.0.0.1:8080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
EOF
Описываем сервис. Разберём по строкам:
image: nginx:1.30-alpine— образ с конкретной версией, а неlatest: вы сами решаете, когда переходить на новую. 1.30 — стабильная ветка на момент публикации; актуальную смотрите на странице образа nginx в Docker Hub.restart: unless-stopped— контейнер поднимется после перезагрузки сервера, если вы не остановили его вручную.127.0.0.1:8080:80— порт 80 контейнера доступен на порту 8080 только с самого сервера. Почему не просто8080:80— ниже, это важно../html:...:ro— каталог с сервера подключается внутрь контейнера только для чтения.
sudo docker compose up -d
sudo docker compose ps
curl http://127.0.0.1:8080
Запускаем в фоне, смотрим состояние и проверяем: должна прийти наша страница.
Подвох: Docker обходит брандмауэр
Вот грабли, на которые наступает почти каждый. Если написать в ports просто "8080:80", Docker откроет порт на всех сетевых интерфейсах — и сделает это в обход ufw. Брандмауэр будет показывать «входящие запрещены», а сервис при этом будет доступен из сети.
Что тут происходит: Docker сам прописывает правила в брандмауэр ядра, и трафик к контейнерам перенаправляется раньше, чем до него доходят правила ufw. Это задокументированное поведение, а не ошибка. Мы проверили его на чистых Debian 12 и 13: при запрете всех входящих в ufw порт 8080:80 отвечал снаружи, а порт 127.0.0.1:8080:80 — нет.
Отсюда правило: публикуйте порт на 127.0.0.1, если сервисом пользуется только сам сервер или он стоит за обратным прокси. Открывайте порт наружу осознанно и только тот, который действительно нужен. Для тонкой настройки у Docker есть отдельная цепочка правил DOCKER-USER — это тема для отдельного разговора.
Шаг 4. Обновление сервиса
cd /srv/stacks/web
sudo docker compose pull
sudo docker compose up -d
sudo docker image prune -f
Скачиваем свежие образы для указанной версии, пересоздаём контейнеры, если образ изменился, и удаляем ставшие ненужными старые образы.
Чтобы перейти на новую версию программы, поменяйте тег в compose.yaml и повторите те же команды. Перед этим прочитайте список изменений: у серьёзных сервисов при переходе между крупными версиями бывают шаги миграции.
Шаг 5. Где смотреть, что происходит
sudo docker compose logs --tail 50 -f
Последние 50 строк журнала сервиса и дальше в реальном времени. Выход — Ctrl+C, контейнер продолжит работать.
sudo docker compose down
Остановить и удалить контейнеры сервиса. Данные в /srv/stacks/web/html останутся на месте — они на сервере, а не в контейнере.
Если что-то пошло не так
- «port is already allocated». Порт занят другим сервисом. Посмотрите, кем:
sudo ss -ltnp | grep 8080, — и выберите другой порт слева в строкеports. - Контейнер всё время перезапускается. Статус
Restartingвdocker compose ps— смотритеdocker compose logs: почти всегда причина написана в последних строках. - «permission denied … docker.sock». Вы запускаете без
sudoи не состоите в группеdocker. - Сервис доступен снаружи, хотя ufw всё закрыл. Это тот самый обход брандмауэра — замените
"8080:80"на"127.0.0.1:8080:80"и выполнитеdocker compose up -d.
Теперь у вас есть понятная схема: каждый сервис — каталог в /srv/stacks с файлом compose.yaml и данными рядом. Именно этот каталог сохраняет бэкап из третьей части цикла. А в четвёртой научим сервер самому сообщать о проблемах.
Это вторая часть цикла «Домашний сервер без боли». Предыдущая — «Первые 30 минут после установки». Все части цикла — на странице цикла.