Напоминаю, что мы идем к тому, чтобы получить безопасную, легко воспроизводимую и покрытую мониторингом self-hosted инфраструктуру для любых наших сервисов.
В нашей схеме это будет работать так. VPS:
- выполняет роль прокси благодаря Caddy и перенаправляет запросы на домашний сервер (выступающий в роли бэкенд-ноды) по подсети headscale.
- пуллит любые метрики и визуализирует дашборды в Grafana.
- агрегирует логи через VictoriaLogs (будет в следующих постах).
- в моем кейсе - хостит у себя блог. В таком случае VPS уже не чистый прокси, но переносить блог в бэкенд я не вижу смысла - нагрузка на VPS минимальная.
Бэкенд-сервер - недоступен извне, подключен только по headscale (ну, можно еще оставить открытым 22 порт со всеми мерами безопасности, если хотите) и хостит нужные вам сервисы.
В предыдущих частях (1, 2, 3, 4) мы делали все на одном VPS - и сервисы, и наблюдаемость. В пятой части мы готовим бэкенд-ноду (эта часть, 5.1), а VPS становится edge-нодой (реверс-прокси и координатор tailnet-сети, будет в части 5.2). Большинство шагов опциональны и, возможно, вам не потребуются.
Я исхожу из того, что линукс у вас уже установлен. В статье все примеры основаны на Debian 13 trixie. В качестве железа - ThinkPad X270, i5-7200U, 16 GB RAM, SSD 256 GB, 2 батареи.
Содержание:
- Диагностика б/у-железа: SMART, memtest, батареи
- Обновление BIOS через fwupd
- Пост-инсталл: sudo, hostname/timezone/locale, SSH-ключи
- Fail2ban: почему не нужен
- Сеть: ifupdown со статикой
- TLP с порогами заряда 40/50 для двух батарей
- Долговечность SSD: fstrim, journald, swap, vm.swappiness
- Игнор крышки, маскирование сна, thermald
- Автообновления
- Брандмауэр - нужен или нет?
- Прочее: отключение Bluetooth
- Docker для будущих сервисов
Главное
- Значительная часть здесь ссылается на содержание предыдущих частей: часть 1 и часть 2. Основы безопасности, Docker, Caddy и observability мы здесь не разбираем.
- Мы используем подержанное железо, поэтому нам важны метрики, особенно батареи.
Чеклист
Железо и BIOS
-
nvme smart-log:critical_warning=0,media_errors=0,available_spare=100% - Memtest86+ прошел минимум 1 полный круг без ошибок
-
Здоровье обеих батарей в порядке (
capacityвupower -i) -
Тепловой стресс-тест 5 минут: пик < 90°C на
Package id 0 -
BIOS обновлен через
fwupdmgr updateдо актуальной версии
Пост-инсталл
-
sudoработает для основного пользователя -
hostname, timezone, locale (
en_US.UTF-8) настроены -
SSH-ключ скопирован через
ssh-copy-id -
/etc/ssh/sshd_config.d/99-ssh.confприменен по части 1
Сеть
- Статический IP или DHCP reservation; вне DHCP-пула роутера
-
ping deb.debian.orgотвечает (DNS работает)
Долговечность диска
-
fstrim.timerenabled, ручной прогонfstrim.serviceбез ошибок -
journald лимит 1G через
/etc/systemd/journald.conf.d/00-server.conf -
swap присутствует (
swapon --show) -
vm.swappiness=10применен (sysctl --system) -
man-db.timerdisabled
Питание и режим работы
- TLP: пороги 40/50 на BAT0 и BAT1
-
logind:
HandleLidSwitch*=ignore(все три),HandlePowerKey=poweroff,IdleAction=ignore -
sleep.target,suspend.target,hibernate.target,hybrid-sleep.target,suspend-then-hibernate.targetзамаскированы
Безопасность платформы
-
unattended-upgradesбазовая настройка по части 1 -
Automatic-Reboot "true"+Automatic-Reboot-Time "04:00"добавлены в52unattended-upgrades-local - (опционально) UFW разрешает SSH только из локальной подсети
Прочее
- Модули Bluetooth отключены
Docker
- Docker + docker-compose установлены
1. Диагностика железа
Этот шаг, как и большинство остальных, опционален. Если с железом у вас все ок или его состояние для вас не критично - проматывайте до следующего раздела.
Первое, что нужно выяснить перед использованием - состояние железа. Грузимся с live-образа.
1.1. SMART NVMe
nvme-cli- утилита для работы с nvme-дисками.smartmontools- диагностика SMART.
sudo apt update && sudo apt install -y nvme-cli smartmontools
sudo nvme smart-log /dev/nvme0n1
smartctl (smartmontools) дает более подробный отчет, чем nvme smart-log:
sudo smartctl -a /dev/nvme0n1 # полный отчет
sudo smartctl -H /dev/nvme0n1 # health check: PASSED или FAILED
sudo smartctl -t short /dev/nvme0n1 # короткий self-test (2 минуты)
sudo smartctl -l selftest /dev/nvme0n1 # результаты прошлых self-test'ов
Если интересно, дока по утилите nvme (см. трактовку вывода в таблице на странице 206): https://nvmexpress.org/wp-content/uploads/NVM-Express-Base-Specification-Revision-2.2-2025.03.11-Ratified.pdf
На что смотреть сейчас:
critical_warning- должно быть 0. Если что-то другое, дальнейшая диагностика не имеет смысла, разбираемся по доке и гуглим.available_spare- доступный резервный запас емкости диска. SSD имеет определенное количество резервных ячеек, которые подменяются, когда ломаются основные. Должен быть >=available_spare_threshold. 100% - идеал, тревога - когда значение приближается кavailable_spare_threshold.available_spare_threshold- порог, при достижении которого диск считается изношенным и должен быть заменен.media_errors- должно быть 0.percentage_used- оценка износа в процентах.unsafe_shutdowns- счетчик аварийных отключений диска.
В целом, метрики важны в динамике, поэтому дальше мы и настроим мониторинг.
Пример реального вывода с X270 (Toshiba 256 GB, 12220 часов работы):
Smart Log for NVME device:nvme0 namespace-id:ffffffff
critical_warning : 0
temperature : 122 °F (323 K)
available_spare : 100%
available_spare_threshold : 10%
percentage_used : 41%
endurance group critical warning summary: 0
Data Units Read : 51561369 (26.40 TB)
Data Units Written : 50877300 (26.05 TB)
host_read_commands : 1091082675
host_write_commands : 798348055
controller_busy_time : 10074
power_cycles : 3335
power_on_hours : 12220
unsafe_shutdowns : 312
media_errors : 0
num_err_log_entries : 0
Warning Temperature Time : 8
Critical Composite Temperature Time : 0
Temperature Sensor 1 : 122 °F (323 K)
Thermal Management T1 Trans Count : 0
Thermal Management T2 Trans Count : 0
Thermal Management T1 Total Time : 0
Thermal Management T2 Total Time : 01.2. Memtest86+
Это опенсорс-утилита для теста состояния RAM. Идем на memtest.org, качаем “Linux ISO w/ GRUB (64 bits)” - скачается zip-архивом.
Распаковываем, пишем образ на флешку, ребутаемся:
unzip mt86plus_*_64.grub.iso.zip # в архиве лежит .iso
lsblk # ищем флешку, скорее всего вида sdX
sudo dd if=mt86plus_*_64.grub.iso of=/dev/sdX bs=4M status=progress oflag=sync
Можно запустить один полный проход (будет идти минут 30-50).
1.3. Состояние батарей
Ставим upower, потом:
upower -e # будет список батарей
upower -i /org/freedesktop/UPower/devices/battery_BAT0 # информация по конкретной батарее
Из вывода важны:
capacity: 85.1% - здоровье батареи
charge-cycles: 453 - циклы перезарядки1.4. Стресс-тест CPU
sudo apt install -y lm-sensors stress-ng
sudo sensors-detect --auto
sudo modprobe coretemp
echo 'coretemp' | sudo tee -a /etc/modules
sensors
lm-sensors- читает данные с аппаратных датчиков.stress-ng- генератор искусственной нагрузки.sensors-detect --auto- сканит чипы на устройстве.- Грузим модуль
coretemp, прописываем в/etc/modulesдля автозагрузки. - Запускаем мониторинг
sensors.
В выводе sensors смотрим температуру CPU не под нагрузкой:
coretemp-isa-0000
Adapter: ISA adapter
Package id 0: +35.0°C (high = +100.0°C, crit = +100.0°C)
Можно запустить стресс-тест на 5 минут.
В одной сессии запуск (--cpu 0 - использовать все доступные CPU):
stress-ng --cpu 0 --timeout 300
Во второй - наблюдение:
watch -n 2 sensors
2. Обновление BIOS
После диагностики с live-образа ставим ОС на диск, дальше BIOS обновляем уже из установленной системы.
Текущую версию BIOS до обновления можно узнать так:
cat /sys/class/dmi/id/bios_version
cat /sys/class/dmi/id/bios_date
После установки ставим fwupd - утилиту, которая качает прошивки из репозитория LVFS (Linux Vendor Firmware Service). Перед запуском проверьте, что ноут заряжается от сети, и не выключайте его на время обновления, иначе можно окирпичить.
sudo apt update
sudo apt install -y fwupd
sudo fwupdmgr refresh --force
sudo fwupdmgr get-devices
sudo fwupdmgr get-updates
sudo fwupdmgr update # обновление прошивок
3. Шаги после установки
Узнаем IP и коннектимся по ssh:
ip a
ssh root@IP # IP из предыдущего вывода
На свежем Debian вход под root по SSH-паролю обычно закрыт (
PermitRootLogin prohibit-password- дефолт OpenSSH). Если при установке вы оставляли пароль root пустым, инсталлятор уже добавил первого пользователя вsudo- заходите под ним и шаг с установкой sudo ниже пропускаете. Если root-пароль задан - root доступен только по ключу или с физической консоли.
3.1. Sudo
Под root:
apt update && apt install -y sudo
usermod -aG sudo youruser
sudo apt upgrade -y # заодно обновляемся
exit
Перезаходим в SSH под обычным юзером.
3.2. Hostname, timezone, locale
Делаем как в части 1, раздел 7, плюс генерируем локаль (на минимальном Debian ее обычно нет, прогоняем dpkg-reconfigure locales вручную; инсталлятор Ubuntu обычно делает это сам по выбранному при установке языку):
sudo hostnamectl set-hostname mio
sudo timedatectl set-timezone Europe/Moscow
sudo dpkg-reconfigure locales # отметить en_US.UTF-8, остальное снять3.3. SSH-ключи
Базовая настройка SSH - в части 1, раздел 2.
Не закрывайте текущую сессию - сначала откройте новую и убедитесь, что вход работает.
Доставка ключа:
ssh-keygen -t ed25519 # на клиенте, откуда подключаемся
ssh-copy-id -i ~/.ssh/id_ed25519.pub youruser@192.168.0.NNN # переносим публичный ключ на сервер
4. Fail2ban: почему не нужен
В части 1, раздел 4 на VPS мы ставили fail2ban.
Домашний сервер находится за NAT, связан через tailnet, ssh только по ключам - публичного входа нет, fail2ban здесь не нужен. Если не доверяете локальной сети и хотите доп. защиту от перебора SSH с других устройств в той же подсети - имеет смысл включить.
5. Сеть
5.1. Ethernet или Wi-Fi
Для сервера предпочтительно проводное соединение (раздел 5.5). Но я выбрал Wi-Fi.
В Debian 13 без DE сеть управляется через ifupdown. Конфиг в /etc/network/interfaces.
5.2. Имена интерфейсов
ip link show5.3. Выбор IP для статики
Смотрим на своем роутере DHCP-пул адресов. Выбираем за пределами этого пула, иначе может быть конфликт адресов.
Смотрим, какой интерфейс включен в локальной сети:
ip route show default # на самом сервере (Linux)
route -n get default | grep interface # если смотрите с мака
Проверяем с другого устройства в той же сети (например, с мака), что адрес свободен. Сначала поставьте `arp-scan` (на маке `brew install arp-scan`, на linux-клиенте `sudo apt install -y arp-scan`) и подставьте интерфейс этого устройства:
```bash
sudo arp-scan --interface=en0 --localnet # en0 - пример для мака; видим занятые адреса5.4. Wi-Fi со статическим IP
sudo apt update
sudo apt install -y wpasupplicant resolvconf
resolvconf нужен отдельно: строка dns-nameservers в /etc/network/interfaces парсится не самим ifupdown, а пакетом resolvconf. Без него она просто игнорируется, /etc/resolv.conf остается пустым, DNS не работает.
Генерим конфиг с хешем пароля:
wpa_passphrase "Имя сети" "Пароль сети" | sudo tee /etc/wpa_supplicant/wpa_supplicant.conf
sudo chmod 600 /etc/wpa_supplicant/wpa_supplicant.conf
В сгенерированном блоке wpa_passphrase оставляет исходный пароль строкой-комментарием #psk="..." - удалите ее, чтобы пароль не лежал открытым. chmod 600 закрывает файл от чтения остальными. Пароль также остается в истории шелла (передан аргументом) - при желании почистите ее.
/etc/network/interfaces:
auto lo
iface lo inet loopback
auto wlp4s0
iface wlp4s0 inet static
address 192.168.0.5/24
gateway 192.168.0.1
dns-nameservers 1.1.1.1 9.9.9.9
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf
auto- поднимать интерфейс при загрузке./24это маска255.255.255.0, стандарт для домашних сетей.1.1.1.1и9.9.9.9- Cloudflare и Quad9.
5.5. Ethernet со статическим IP (если у вас проводное соединение)
Аналогично, без wpa-conf:
auto enp0s31f6
iface enp0s31f6 inet static
address 192.168.0.5/24
gateway 192.168.0.1
dns-nameservers 1.1.1.1 9.9.9.95.6. Применить
Рестартим сервис и коннектимся заново:
systemctl restart networking
Если у вас на клиенте уже есть конфиг для коннекта к этому серверу, не забудьте там обновить IP: .ssh/config.
5.7. Проверка
ip -4 addr show
cat /etc/resolv.conf # должны быть nameserver-строки
ping -c 3 1.1.1.1
ping -c 3 deb.debian.org5.8. Частые проблемы
Если при apt update получаете Temporary failure resolving, значит DNS не настроен.
Смотрим конфиг cat /etc/resolv.conf - если пусто, не установлен resolvconf.
Временно добавьте в resolv.conf запись nameserver 1.1.1.1 руками и поставьте resolvconf.
Если Wi-Fi не подключается: sudo journalctl -u networking -b. Возможно, опечатались в SSID или пароле в wpa_supplicant.conf.
6. TLP - управление зарядом батареи
TLP - это утилита для управления питанием на ноутбуках. Согласно их доке, если ноут большую часть времени подключен к сети, продлить жизнь батареи можно, держа заряд в диапазоне 40-50%:
If the laptop is plugged most of the time and rarely unplugged, maximizing battery lifetime at the cost of a greatly reduced runtime may be acceptable, with values like starting charge at 40% and stopping at 50%.
Ставим и включаем службу:
sudo apt install -y tlp
sudo systemctl enable --now tlp6.1. Конфигурация
Нужные нам параметры: START/STOP_CHARGE_THRESH_BAT0/BAT1, дока: https://linrunner.de/tlp/settings/battery.html
sudo nano /etc/tlp.d/01-server.conf
Ставим пороги:
START_CHARGE_THRESH_BAT0=40
STOP_CHARGE_THRESH_BAT0=50
START_CHARGE_THRESH_BAT1=40
STOP_CHARGE_THRESH_BAT1=50
START_CHARGE_THRESH_BATX=40 / STOP_CHARGE_THRESH_BATX=50 - зарядка начинается, если уровень опустился ниже 40%, и останавливается на 50%.
Применяем конфиг:
sudo systemctl restart tlp6.2. Проверка
cat /sys/class/power_supply/BAT0/charge_control_start_threshold
cat /sys/class/power_supply/BAT0/charge_control_end_threshold
cat /sys/class/power_supply/BAT1/charge_control_start_threshold
cat /sys/class/power_supply/BAT1/charge_control_end_threshold
Должно вернуть 40 и 50. Полная диагностика:
sudo tlp-stat -b
Главное что проверить в выводе:
natacpi (thinkpad_acpi) = active- если active, модуль загружен.charge_control_start/end_threshold = 40 / 50- применилось.Capacity- здоровье батареи (energy_full / energy_full_design).
7. Поддержание скорости SSD с помощью TRIM
SSD не умеет перезаписывать блок напрямую - может только записать в пустой. TRIM сообщает SSD список пустых блоков, чтобы он их заблаговременно очистил.
7.1. fstrim.timer
Проверка, что TRIM включен:
systemctl is-enabled fstrim.timer # ожидаем enabled
# если disabled - включить таймер и сразу прогнать один раз
sudo systemctl enable --now fstrim.timer
sudo systemctl start fstrim.service
sudo journalctl -u fstrim.service -n 20 --no-pager
Пример с моего X270:
fstrim[5406]: /boot/efi: 965.3 MiB (1012195328 bytes) trimmed on /dev/nvme0n1p1
fstrim[5406]: /: 219 GiB (235143614464 bytes) trimmed on /dev/nvme0n1p2
systemd[1]: Finished fstrim.service.7.2. journald
В части 1, раздел 14 мы ставили лимит 500M. На домашнем сервере места больше:
sudo mkdir -p /etc/systemd/journald.conf.d
sudo nano /etc/systemd/journald.conf.d/00-server.conf[Journal]
SystemMaxUse=1G
MaxRetentionSec=2weeks
SystemMaxUse=1G- верхний лимит общего размера логов.MaxRetentionSec=2weeks- удалять записи старше двух недель, даже если есть место.
Применить и подрезать накопившееся:
sudo systemctl restart systemd-journald
sudo journalctl --vacuum-size=1G7.3. Swap и vm.swappiness
Swap-файл создается как в части 1, раздел 8.
Проверяем своп:
sudo swapon --show
Если пусто - делаем как в части 1: fallocate -l 4G /swapfile, chmod 600, mkswap, swapon, строчка в /etc/fstab.
Что добавляем для сервера на ноуте: vm.swappiness.
По умолчанию ядро выгружает страницы памяти в swap даже при свободной RAM (vm.swappiness=60). Для серверов рекомендуемое значение - 10-20 (использовать своп только в крайнем случае). Это уменьшает запись на SSD и снижает нагрузку на диск.
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-server.conf
sudo sysctl --system # будет vm.swappiness = 107.4. man-db.timer
man-db.timer - ежедневный таймер, обновляющий базу man-страниц. Мелочь, но я man не использую, поэтому отключаю.
sudo systemctl disable --now man-db.timer
8. Отключаем закрытую крышку и настраиваем сон
8.1. Игнорировать закрытую крышку
systemd-logind управляет реакцией на аппаратные события: закрытие крышки, кнопка питания, бездействие. По умолчанию закрытие крышки -> sleep. У нас сервер, поэтому отключаем.
sudo mkdir -p /etc/systemd/logind.conf.d
sudo nano /etc/systemd/logind.conf.d/00-server.conf[Login]
HandleLidSwitch=ignore # закрыли крышку на батарее - не уходить в сон
HandleLidSwitchDocked=ignore # то же, если в док-станции
HandleLidSwitchExternalPower=ignore # то же, если работаем от сети
HandlePowerKey=poweroff # кнопка питания - ставим выключение
IdleAction=ignore # на длительный idle не реагироватьsudo systemctl restart systemd-logind
Проверка:
systemd-analyze cat-config systemd/logind.conf | grep -E 'HandleLid|IdleAction'8.2. Маскировать sleep
Выше мы отключили сон на закрытие крышки. Но его могут вызвать и другие причины - например, другие сервисы или скрипты. Сейчас мы явно запретим сон.
За сон в линуксе отвечают 5 таргетов: sleep.target, suspend.target, hibernate.target (suspend-to-disk), hybrid-sleep.target, suspend-then-hibernate.target.
Подробнее: https://wiki.archlinux.org/title/Power_management/Suspend_and_hibernate
Чтобы заблокировать все пять путей, используем systemctl mask (mask создает symlink на /dev/null):
sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target suspend-then-hibernate.target
Откатить, если когда-нибудь понадобится:
sudo systemctl unmask sleep.target suspend.target hibernate.target hybrid-sleep.target suspend-then-hibernate.target8.3. Тепловой контроль (thermald)
Если у вас Intel, можно поставить thermald для предотвращения перегрева. Если CPU не греется и ноут нормально вентилируется, смысла нет. Я себе поставил, поэтому упомяну:
sudo apt install -y thermald
sudo systemctl enable --now thermald
Работает как демон: при перегреве ограничивает частоту и мощность CPU (троттлинг), удерживая температуру в норме. Вентилятором он не управляет - это делает встроенный контроллер.
9. Автообновления
Базовая настройка unattended-upgrades (security-патчи + чистка старых ядер) - в части 1, раздел 5.
Для домашнего сервера я добавил автоматическую перезагрузку в 4 утра.
needrestart - если не стоит, ставим. После обновления он показывает, какие запущенные процессы используют старые версии библиотек (полезно знать и до ребута):
sudo apt install -y needrestart
Ставим ту же опцию, что в части 1, но добавляем еще две:
Automatic-Reboot "true"- если установленное обновление требует ребута - перезагрузиться.Automatic-Reboot-Time "04:00"- ребут в 04:00.
sudo nano /etc/apt/apt.conf.d/52unattended-upgrades-localUnattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
10. Брандмауэр
В части 1, раздел 3 на VPS мы UFW включали - и это правильно, потому что у VPS публичный IP, без firewall он открыт всем.
Домашний сервер за NAT, поэтому по умолчанию UFW не нужен. NAT не пропускает входящие, tailnet шифрован.
Аргумент “за” тот же, что и в разделе 4 с fail2ban - если не доверяете локальной сети. Минимальный UFW на этот случай:
sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from 192.168.0.0/24 to any port 22 proto tcp
sudo ufw enable
sudo ufw statusStatus: active
To Action From
-- ------ ----
22/tcp ALLOW 192.168.0.0/24
11. Прочее
11.1. Выключаем Bluetooth
Bluetooth не нужен для сервера:
sudo systemctl disable --now bluetooth.service
sudo apt purge -y bluez
После apt purge bluez в журнале при загрузке может появиться:
kernel: Bluetooth: hci0: Reading supported features failed (-16)
Исправляем, чтобы Bluetooth-модули не грузились вообще:
sudo tee /etc/modprobe.d/no-bluetooth.conf <<'EOF'
blacklist btusb
blacklist bluetooth
blacklist btintel
blacklist btbcm
EOF
sudo update-initramfs -u
sudo reboot
update-initramfs -u обновляет initramfs, чтобы blacklist применился.
Проверка после ребута:
lsmod | grep -iE 'bluetooth|btusb' # пусто
sudo journalctl -p err -b # строка про hci0 исчезла
12. Self-hosted сервисы
Правила для добавления описаны в части 2, раздел 13 - Памятка для добавления нового сервиса.
Отличие локального сервера от VPS - у него нет публичного IP, поэтому абсолютного требования “только loopback” тут нет. По умолчанию сервисы держим на 127.0.0.1, а то, что должно слушать шире (ssh, а в части 5.2 - экспортеры node_exporter/cAdvisor в host-режиме на 0.0.0.0 ради скрейпа с VPS), закрываем UFW (default deny incoming, пускаем только tailnet и SSH из локалки) - детали в части 5.2, раздел 12.2.
12.1. Docker + docker-compose
По части 1, раздел 9, но для Debian.
Документация docker: https://docs.docker.com/engine/install/debian/
# Add Docker's official GPG key:
sudo apt update
sudo apt install -y 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
# Add the repository to Apt sources:
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER
# logout/login или перезагрузка
Проверка:
docker run --rm hello-world
docker compose version
Что дальше
В следующей части (5.2) настраиваем VPS как edge-ноду: поднимаем headscale-координатор за Caddy, связываем домашний сервер с VPS по tailnet и собираем его метрики (node_exporter, cAdvisor) в Grafana с дашбордами и алертами на координатор.