Uncloud на VPS: один Compose-файл сразу на два сервера
Между «всё на одном сервере через Docker Compose» и «поднимаю Kubernetes» долго не было ничего подходящего. Uncloud занимает ровно это место: он соединяет ваши VPS в закрытую сеть и раскатывает по ним тот же самый compose.yaml, к которому вы привыкли. Ни мастер-ноды, ни кворума, ни новых языков конфигурации.
Проблема: один сервер — это одна кнопка выключения
Обычная история: приложение, база и Nginx живут на одном VPS в одном docker-compose.yml. Пока всё работает, схема прекрасна. Ломается она в четырёх местах, и все четыре случаются рано или поздно.
Перезагрузка сервера = простой сайта
Провайдер переносит виртуалку или обновляет гипервизор — сайт недоступен несколько минут. Обновление ядра на самом сервере — то же самое.
Обновление приложения тоже простой
docker compose up -d останавливает старый контейнер и запускает новый. Между этими событиями сайт отдаёт 502.
База и приложение дерутся за память
Разнести PostgreSQL и приложение по разным серверам логично, но тогда база должна ходить по интернету с паролем — и её порт придётся открыть наружу.
Второй сервер не помогает без оркестратора
Купить вторую машину легко. Дальше нужно вручную синхронизировать конфиги, копировать образы, править Nginx на обеих и следить, чтобы версии не разъехались.
Классические ответы на это — Docker Swarm или Kubernetes. Swarm жив, но фактически заморожен: заметных новых возможностей в нём не появлялось годами, а его схема с менеджерами и кворумом требует трёх машин, чтобы иметь смысл. Kubernetes даже в лёгком варианте вроде k3s — это отдельная профессия: манифесты, ингрессы, контроллеры, своя терминология.
Uncloud — попытка закрыть промежуток. Он появился в 2024 году, а за образец взяты Kamal (деплой-инструмент из 37signals), Tailscale и Fly.io — это видно в каждом решении. К августу 2026-го проект набрал 5,4 тысячи звёзд на GitHub и дошёл до версии 0.20.0. Лицензия Apache 2.0, написан на Go.
Как это устроено: три вещи, которые нужно понять
1. Серверы связаны шифрованной сетью, и контейнеры видят друг друга напрямую
При добавлении машины Uncloud сам создаёт WireGuard-туннель до остальных: ключи, адреса и обход NAT настраиваются без вашего участия. Каждой машине выдаётся своя подсеть (первой — 10.210.0.0/24, второй — 10.210.1.0/24), и контейнеры получают адреса из неё. Контейнер на первом сервере обращается к базе на втором по адресу db.internal — как будто они на одной машине. Наружу при этом не открыт ни один порт базы.
2. Главного сервера нет — состояние есть у всех
Список сервисов, контейнеров и томов хранится в Corrosion — распределённой SQLite от Fly.io. Копия лежит на каждой машине, и машины синхронизируют её напрямую. Отсюда следствие, которое приятно ощущается на практике: подключаться можно к любой машине, и она умеет управлять всем кластером. Когда мы выключили первый сервер, команда uc ls сама переключилась на второй и продолжила работать.
3. Caddy стоит на каждой машине и знает про все контейнеры
Обратный прокси Caddy разворачивается по одной копии на каждый сервер, слушает порты 80 и 443 и сам получает сертификаты Let's Encrypt. Его конфиг Uncloud генерирует автоматически: список адресов контейнеров обновляется при каждом запуске, остановке или смене состояния здоровья. Важная деталь — конфиг одинаковый на обеих машинах и содержит адреса контейнеров с обеих. Значит, запрос, пришедший на первый сервер, может быть обслужен контейнером на втором.
Чем управляют. Команда uc ставится на ваш компьютер, а не на сервер. Она ходит на машины по SSH и командует демоном uncloudd, который там установлен. Никакой веб-панели у Uncloud нет — это принципиально консольный инструмент.
Uncloud или что-то другое
| Инструмент | Когда он лучше |
|---|---|
| Просто Docker Compose | Один сервер, простой сайт, простой в минуту при обновлении никого не расстроит. Пока это так — не усложняйте. |
| Docker Swarm | Он у вас уже работает и всех устраивает. Начинать новый проект на Swarm в 2026 году смысла мало: развития нет, а для надёжного кворума нужны три менеджера. |
| k3s и Kubernetes | Нужны автомасштабирование, операторы, сетевые тома, десятки сервисов или требование «как в проде на работе». Ценой изучения целого мира понятий. |
| Coolify | Хочется кнопок в браузере и каталога готовых сервисов. Coolify — это панель, Uncloud — инструмент командной строки под Git и CI. |
| Uncloud | Два-три сервера, привычный Compose, нужны обновления без простоя, приватная сеть между машинами и HTTPS без возни. Не хочется учить новый язык конфигурации. |
Что понадобится
- Два VPS с Ubuntu 22.04/24.04 или Debian 11/12, архитектура x86-64. Минимум по документации — 1 ядро и 512 МБ памяти, но это без учёта вашего приложения: для связки «приложение + PostgreSQL» берите от 2 ГБ.
- Чистые серверы. Порты 80 и 443 должны быть свободны — если там уже висит Nginx или Apache, Caddy не запустится.
- Вход по SSH-ключу под
rootили под пользователем сsudoбез пароля. По паролю Uncloud подключаться не умеет. - Домен, у которого вы можете править DNS-записи.
- Свой компьютер с macOS или Linux. На Windows работает через WSL — отдельной сборки под Windows нет.
Почему чистые серверы важнее, чем кажется. Если Docker на сервере ещё не установлен, Uncloud поставит его сам и настроит /etc/docker/daemon.json под себя — включит хранилище образов containerd и live-restore. Если Docker уже стоит, конфиг не трогается: скрипт лишь выведет предупреждение. Менять эту настройку на работающем сервере рискованно — после переключения существующие образы и контейнеры временно перестают быть видны Docker.
Шаг 1. Ставим uc на свой компьютер
Одна команда. Скрипт определит систему, скачает бинарник с GitHub и положит его в /usr/local/bin/uc — для этого он спросит пароль sudo.
curl -fsS https://get.uncloud.run/install.sh | shПроверяем, что всё встало:
uc versionДолжна быть версия 0.20.0 или новее. На macOS удобнее через Homebrew: brew install psviderski/tap/uncloud — тогда обновляться будет привычной командой brew upgrade uncloud.
Шаг 2. Первый сервер
Одна команда превращает обычный VPS в машину кластера. Подставьте IP своего сервера и путь к своему SSH-ключу.
uc machine init root@203.0.113.10 -i ~/.ssh/id_ed25519 -n vps1Флаг -n vps1 задаёт человеческое имя машины — иначе будет что-то вроде machine-dc3c, и через месяц вы не вспомните, где что. Команда займёт минуту-две и за это время:
- установит Docker, если его нет;
- создаст системного пользователя
uncloudи положит демон в/usr/local/bin/uncloudd; - создаст и запустит службу
/etc/systemd/system/uncloud.service; - поднимет WireGuard-интерфейс и запустит контейнер с базой состояния кластера;
- развернёт Caddy на портах 80 и 443;
- выдаст кластеру бесплатное имя вида
abc123.uncld.devи направит его на IP сервера.
Последний пункт — приятная мелочь: сервисы сразу доступны по адресу имя-сервиса.abc123.uncld.dev с настоящим HTTPS, ещё до того как вы займётесь своим доменом. Если бесплатный поддомен вам не нужен и обращаться к чужому сервису не хочется, добавьте --no-dns.
Адреса машин и путь к ключу сохранятся на вашем компьютере в ~/.config/uncloud/config.yaml. Файл небольшой, но потерять его неприятно: восстанавливать придётся вручную. Копия рядом с остальными ключами не помешает.
uc machine lsNAME STATE ADDRESS PUBLIC IP OS DOCKER VERSION
vps1 Up 10.210.0.1/24 203.0.113.10 Ubuntu 22.04.5 LTS 29.7.2 0.20.0Шаг 3. Первый сервис одной командой
Прежде чем писать Compose-файл, полезно убедиться, что кластер живой. Запустим крошечное тестовое приложение traefik/whoami: оно отвечает текстом, в котором указано имя контейнера, — идеально, чтобы видеть, какой сервер обработал запрос. Домен для этого пока не нужен, хватит бесплатного имени кластера.
uc run --name hello -p 80/https traefik/whoamiЗапись 80/https читается так: «порт 80 внутри контейнера показать наружу по HTTPS». Раз домен не указан, Uncloud возьмёт имя сервиса и имя кластера:
hello endpoints:
• https://hello.abc123.uncld.dev → :80Откройте этот адрес в браузере. Сертификат Caddy получает при первом обращении, поэтому если страница не открылась с первого раза — подождите полминуты и обновите. Если вы ставили кластер с флагом --no-dns, команда откажется работать со словами cluster domain must be reserved — тогда укажите свой домен явно: -p hello.example.com:80/https.
Убрать тестовый сервис:
uc rm helloШаг 4. Добавляем второй сервер
uc machine add root@203.0.113.20 -i ~/.ssh/id_ed25519 -n vps2Происходит то же самое, плюс обмен ключами WireGuard с первой машиной. В конце Uncloud спросит, развернуть ли Caddy и на новом сервере, — отвечайте y. Без этого второй сервер не сможет принимать запросы из интернета напрямую.
Проверить, что туннель между машинами поднялся:
uc wg showPEER ENDPOINT HANDSHAKE RTT ALLOWED IPS
vps2 203.0.113.20:51820 30s ago 0ms 10.210.1.0/24Если в колонке HANDSHAKE прочерк, а не «столько-то секунд назад» — почти наверняка закрыт UDP-порт 51820. Про файрвол будет отдельный раздел ниже.
Шаг 5. Compose-файл на два сервера
Теперь главное — обещанный обычный Compose. Создайте на своём компьютере папку проекта и в ней файл compose.yaml:
services:
app:
image: traefik/whoami
x-ports:
- app.example.com:80/https
deploy:
replicas: 2
db:
image: postgres:18-alpine
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: 8Xk2mQ7vRb4nZs
volumes:
- db-data:/var/lib/postgresql
x-machines: vps2
volumes:
db-data:Здесь всё стандартно, кроме двух ключей, которых нет в обычном Compose:
x-ports— какой порт контейнера показать наружу и на каком домене. Привычныйportsдля сайтов в Uncloud не используется. Если же нужно открыть не-HTTP порт (например, чтобы зайти в базу с самого сервера), пишут его тоже вx-ports, но с пометкой@host:127.0.0.1:5432:5432@host.x-machines— на каких машинах разрешено запускать сервис. Для базы это обязательно: том с данными создаётся на конкретной машине, и без явного указания Uncloud выберет её сам, а вы потом будете гадать где.
У app никаких ограничений нет, и две копии Uncloud раскидает по разным серверам сам — он старается разносить реплики, а не складывать в кучу.
Пароль в файле, который поедет в Git, — плохая идея. С версии 0.20 Uncloud умеет подставлять секреты при развёртывании: значение читается у вас на компьютере и попадает сразу в контейнер, минуя compose.yaml.
services:
db:
environment:
POSTGRES_PASSWORD: secret://db_password
# и в самом конце файла, на верхнем уровне:
secrets:
db_password:
# файл рядом с compose.yaml, добавленный в .gitignore
file: ./db_password.txt
# либо вывод любой команды:
# x-command: pass show projects/appdbРазворачиваем из папки с файлом:
uc deployСначала Uncloud покажет план и дождётся подтверждения — это лучшая его черта: вы всегда видите, что именно произойдёт, до того как оно произойдёт.
Deployment plan
+ create volume db-data on vps2
+ create service app
│ image: traefik/whoami:latest
│ replicas: 2
│
├── + run container app on vps1
╰── + run container app on vps2
+ create service db
│ image: postgres:18-alpine
│
╰── + run container db on vps2
────────────────────────────
4 create · across 2 machines
Proceed with deployment? [y/N]Отвечаем y. Пара минут на скачивание образов — и всё запущено. Смотрим результат:
uc lsNAME MODE REPLICAS IMAGE ENDPOINTS
app replicated 2 traefik/whoami:latest https://app.example.com → :80
caddy global 2 caddy:2.11.4
db replicated 1 postgres:18-alpineОбратите внимание: caddy — такой же сервис, как ваши. Режим globalозначает «одна копия на каждой машине». Кто на какой машине оказался, показывает uc ps.
Имена сервисов в кластере общие. В отличие от Docker Compose, Uncloud не приписывает к именам название проекта. Сервис db из одного Compose-файла и сервис db из другого — это один и тот же сервис, и второй деплой перезапишет первый. Называйте сервисы уникально: shop-db, blog-db.
Шаг 6. Домен и HTTPS: две A-записи вместо одной
Чтобы сайт пережил падение сервера, домен должен указывать на обе машины. Создайте у своего регистратора две A-записи с одинаковым именем:
| Имя | Тип | Значение |
|---|---|---|
| app | A | 203.0.113.10 |
| app | A | 203.0.113.20 |
Браузер получит два адреса и, если первый не отвечает, сам попробует второй. Это не мгновенное переключение — некоторые браузеры и клиенты ждут таймаута несколько секунд, — но сайт остаётся доступным без вашего участия.
Сертификаты Let's Encrypt каждая машина получает себе сама, и копировать их между серверами не нужно. Но именно здесь у Uncloud есть слабое место, о котором лучше знать заранее.
Сертификаты на двух машинах выдаются не сразу.Хранилище сертификатов у каждой машины своё, а Let's Encrypt при проверке домена приходит на один из двух IP — какой попадётся. Половина проверок прилетает «не той» машине и не проходит. Caddy повторяет попытки сам, так что в итоге сертификаты получают обе, но на это уходят не секунды, а минуты. Это известное ограничение — в трекере проекта открыта задача про общее хранилище сертификатов.
Из этого следует практическое правило: не ставьте домен за проксирующий CDN (то самое «оранжевое облако» Cloudflare). Тогда проверка может не доходить до второй машины неделями, и она так и останется без сертификата. Нужен CDN — выпускайте сертификат через DNS-проверку, о ней ниже.
Если ждать и надеяться не хочется, есть надёжный путь: заказать один общий сертификат на всё поддоменное пространство через DNS-проверку. Тогда Let's Encrypt проверяет не сервер, а TXT-запись в вашей зоне, и число машин перестаёт иметь значение. Нужна сборка Caddy с плагином вашего DNS-провайдера — например, готовый образ для Cloudflare. Сам Caddy — обычный сервис Uncloud, так что описывается он таким же Compose-файлом:
services:
caddy:
image: caddybuilds/caddy-cloudflare:2.11.4
command: caddy run -c /config/Caddyfile
environment:
CADDY_ADMIN: unix//run/caddy/admin.sock
env_file:
# файл рядом, одна строка: CLOUDFLARE_API_TOKEN=...
- .env.secrets
volumes:
- /var/lib/uncloud/caddy:/data
- /var/lib/uncloud/caddy:/config
- /run/uncloud/caddy:/run/caddy
x-ports:
- 80:80@host
- 443:443@host
- 443:443/udp@host
x-caddy: Caddyfile
deploy:
mode: global*.example.com {
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
respond "No host matched" 404
}Разворачивается обычным uc deploy из этой папки. Именно такой способ, а не флаг --caddyfile у uc caddy deploy: в версии 0.20 глобальный конфиг, переданный флагом, теряется при первой же перегенерации, а заданный через x-caddy хранится в состоянии кластера и переживает и перезапуск контейнера, и деплой других сервисов — проверено. Для одной машины всё это не нужно: там обычная проверка по 80-му порту работает без нареканий.
Посмотреть, что именно Caddy сейчас раздаёт, можно так:
uc caddy confighttps://app.example.com {
reverse_proxy 10.210.0.5:80 10.210.1.5:80 {
import common_proxy
}
log
}Два адреса в reverse_proxy — это контейнеры на разных машинах. И такой конфиг лежит на обеих: любой сервер умеет обслужить запрос контейнером соседа.
Как приложение находит базу на другом сервере
Никакой отдельной настройки не нужно: внутри кластера работает свой DNS. Имя сервиса — это и есть адрес. Тестовый whoami из примера базу, конечно, не использует, но когда вы замените его на своё приложение, строка подключения будет выглядеть так:
environment:
DATABASE_URL: postgres://appuser:${DB_PASSWORD}@db.internal:5432/appdbЗначение DB_PASSWORD Uncloud подставит из файла .env, лежащего рядом с compose.yaml на вашем компьютере, — как это делает обычный Docker Compose. Вставить сюда secret:// из прошлого раздела не получится: секрет подставляется, только если он занимает всё значение переменной целиком. Мы проверили — внутри строки он останется текстом secret://db_password.
Мы это проверили на живом кластере: контейнер на первой машине подключился к PostgreSQL на второй по имени db.internal и получил ответ с адресом сервера 10.210.1.3 — то есть трафик шёл по WireGuard-туннелю. Порт 5432 при этом наружу не открыт вообще.
Если у сервиса несколько копий, имя вернёт адреса всех — получается простая балансировка силами DNS. Работает и короткая форма без суффикса: db вместо db.internal.
Что происходит, когда сервер падает
Это самый важный раздел, и здесь мы не пересказываем документацию: мы выключили вторую машину живого кластера и посмотрели, что будет.
| Что было | Что стало |
|---|---|
| Сайт в двух копиях | Работает. Все запросы через выжившую машину получили ответ — Caddy повторяет неудачный запрос на другом контейнере, до трёх раз |
| База данных на упавшей машине | Недоступна. Сервис исчезает из uc ls, на живой сервер не переезжает |
| Управление | Работает. uc сам переключается на живую машину |
| Попытка выкатить обновление | uc deploy прерывается с ошибкой, если в файле есть сервис, привязанный к упавшей машине |
| Машина вернулась в строй | Всё поднимается само за полминуты, данные в томе на месте |
Вывод простой: Uncloud даёт отказоустойчивость приложения, но не данных. Сайт переживает потерю сервера, база — нет.
Главный подвох: тома живут на своей машине
Docker-тома в Uncloud — обычные локальные тома Docker. Том db-data создан на машине vps2 и существует только там. Никакого общего хранилища между серверами нет, и Uncloud его не изображает.
Что важнее — он честен на этот счёт. Мы попробовали развернуть сервис с томом, пока его машина выключена, и получили не тихое создание пустой базы на соседнем сервере, а остановку с внятной ошибкой:
Error: plan deployment: schedule volumes: schedule service 'db':
no machines available that satisfy all constraintsЭто правильное поведение: молча подсунуть приложению пустую базу было бы намного хуже. Но планировать сохранность данных всё равно придётся самостоятельно. Три рабочих варианта:
Бэкапы — обязательный минимум
Регулярный pg_dump в объектное хранилище. Это нужно делать в любом случае, независимо от количества серверов.
Репликация PostgreSQL
Вторая копия базы в режиме реплики на другой машине. Uncloud тут ничем не мешает: обе базы — обычные сервисы, привязанные к своим машинам через x-machines, а видят друг друга по внутренним именам.
База у провайдера
Managed PostgreSQL снаружи кластера. Тогда в Uncloud живут только приложения без состояния, и он раскрывается лучше всего.
Посмотреть, где какой том лежит, можно командой uc volume ls — она показывает колонку с именем машины.
Обновление приложения без простоя
Меняете образ или настройки в compose.yaml, запускаете uc deploy — дальше Uncloud заменяет контейнеры по одному. По умолчанию используется порядок сначала запустить новый, потом убрать старый: пока новая копия поднимается, трафик обслуживает старая.
После запуска каждого контейнера Uncloud 5 секунд наблюдает, не падает ли он. Мы выкатили заведомо сломанную версию и посмотрели, что будет: деплой остановился на первом же контейнере, сломанный остался лежать остановленным «для вскрытия», старые копии продолжили работать, а последние 10 строк лога упали прямо в терминал:
Container rolltest-99y0 on vps1 Unhealthy (Restarting (1) Less than a second ago)
Last 10 log lines from failed container:
... BROKEN-VERSION
Error: deploy services: new container 'rolltest/f7844c5cdbd6' failed to become healthy:
container is restarting after monitor period (5s): exit_code=1.
It's stopped and available for inspection.То есть неудачный деплой не роняет сервис: пользователи продолжают ходить на старую версию, пока вы разбираетесь. Исправили — запускаете uc deploy снова, и он доделает начатое, не трогая уже обновлённые контейнеры.
Исключение — сервисы с томом в одной копии. Для них Uncloud сам переключается на обратный порядок: сначала остановить старый контейнер, потом запустить новый. Иначе два процесса одновременно писали бы в одни и те же файлы. Это значит короткий простой базы при каждом обновлении — в плане деплоя такие контейнеры помечены как stop-first.
Пять секунд наблюдения — грубая проверка. Точнее и быстрее работает настоящая проверка здоровья: тогда Uncloud перейдёт к следующему контейнеру сразу, как только новый отчитается, что готов.
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 5s
retries: 3
start_period: 10sЕсли приложение вы собираете сами, Uncloud умеет ещё один приятный трюк: при наличии секции build он собирает образ вашим локальным Docker и заливает его прямо на машины кластера, передавая только недостающие слои. Реестр образов заводить не нужно.
Файрвол: какие порты открыть
Uncloud не трогает ваш файрвол — это ваша забота. Мы посмотрели, что демон реально слушает на сервере, и вот итог.
| Порт | Зачем |
|---|---|
| 22/tcp | SSH — через него uc управляет машиной |
| 80, 443/tcp | Caddy: сайты и получение сертификатов |
| 443/udp | HTTP/3, необязательно, но лучше открыть |
| 51820/udp | WireGuard между машинами. Без него кластер не соберётся |
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443
sudo ufw allow 51820/udp
sudo ufw enableКоманда ufw allow 443 без указания протокола открывает и TCP, и UDP — это как раз то, что нужно для HTTP/3.
Из всего списка по-настоящему критичен 51820/udp. Порты 80 и 443 публикует сам Docker, а он идёт в обход UFW — сайт откроется, даже если вы забыли про эти правила. А вот WireGuard слушает порт на самом сервере, и UFW его закроет: без явного разрешения вторая машина просто не подключится к первой.
Хорошая новость про остальное. Всё внутреннее — DNS кластера, управляющий интерфейс демона, метрики, приём образов — привязано к адресу машины внутри WireGuard-сети (10.210.X.1) и из интернета недоступно в принципе. Даже если вы вовсе не настроите файрвол, эти службы наружу не торчат. Общие правила усиления сервера от этого не отменяются — про них есть отдельная статья.
Сколько это ест
Замеры на живом кластере из двух машин с Ubuntu 22.04 и Uncloud 0.20.0, в покое:
| Что | Память |
|---|---|
Демон uncloudd | 57 МБ |
| Контейнер состояния кластера | 17 МБ |
| Контейнер Caddy | 14 МБ |
| Итого сверх Docker | ≈ 90 МБ на машину |
Диск: данные Uncloud в /var/lib/uncloud заняли 1,9 МБ, бинарник демона весит 54 МБ. Сам Docker (демон плюс containerd) съел ещё около 185 МБ — но он бы понадобился и без Uncloud. На сервере с 1 ГБ памяти после ОС, Docker и Uncloud под приложение остаётся примерно 600 МБ; на 2 ГБ уже комфортно.
Команды, которые пригодятся
| Команда | Что делает |
|---|---|
| uc ls | Все сервисы кластера и их публичные адреса |
| uc ps | Все контейнеры с указанием машины |
| uc inspect app | Подробности одного сервиса: контейнеры, IP, состояние |
| uc logs app -f | Логи сервиса со всех машин сразу, в реальном времени |
| uc exec app -- sh | Оболочка внутри контейнера сервиса |
| uc scale app 4 | Изменить число копий на лету |
| uc machine ls | Машины: состояние, версии ОС, Docker и демона |
| uc volume ls | Тома и машины, на которых они лежат |
| uc proxy db 5432 | Пробросить порт сервиса на свой компьютер |
| uc rm app | Удалить сервис вместе с контейнерами |
Про uc scale есть нюанс: он меняет число копий прямо сейчас, но не трогает ваш compose.yaml. Следующий uc deploy вернёт то количество, которое записано в файле. Удобно для быстрой реакции на наплыв, но постоянное значение правьте в файле.
Чего в Compose-файле не будет работать
Uncloud поддерживает большую часть спецификации Compose, но не всю. Самое частое, обо что спотыкаются при переносе готового файла:
networks— не поддерживаются, тоже с предупреждением. Все контейнеры и так в одной сети кластера, разделять их на подсети нельзя.labels— не поддерживаются.uc deployнапечатает предупреждение и просто их отбросит. Если вы вешали на контейнеры метки для Traefik, эта схема здесь не работает: маршрутизация задаётся черезx-ports.ports— только в длинной записи сmode: host. Короткая"8081:80"из обычного Compose вызовет ошибку. Проще писать порты вx-ports: для сайтов в видедомен:80/https, для остального — с пометкой@host.depends_on— влияет только на порядок развёртывания. Дождаться, пока сервис отработает и завершится, так нельзя.restart_policy— не поддерживается, всегда используетсяunless-stopped. На практике это то, что нужно: контейнеры сами поднимаются после перезагрузки сервера.secrets— только в виде переменных окружения, монтировать секрет файлом пока нельзя.
Зато то, что нужно чаще всего, на месте: environment, env_file, volumes, healthcheck, command, configs, ограничения памяти и процессора, build и deploy.replicas.
Частые проблемы
Caddy не запускается: port is already allocated
Полный текст ошибки — Bind for 0.0.0.0:80 failed: port is already allocated. На сервере уже кто-то слушает 80-й или 443-й порт: обычно это Nginx, Apache или оставшийся контейнер от прошлой жизни. Найдите виновника и уберите:
sudo ss -tulpn | grep -E ':80|:443'
sudo systemctl disable --now nginxЗатем на своём компьютере повторите uc caddy deploy.
uc machine init падает на «Job for uncloud.service failed because a timeout was exceeded»
При первом запуске демон скачивает образ базы состояния кластера, а systemd даёт ему на старт всего 15 секунд. На медленном канале этого не хватает, и установка обрывается. Ничего страшного не произошло: служба настроена на автоперезапуск и через несколько секунд поднимется сама. Убедитесь в этом и повторите команду — второй раз образ уже скачан, и всё пройдёт быстро. Повтор здесь безопасен: до кластера дело не дошло, машине нечего терять. Если же Uncloud вдруг спросит про сброс машины — значит, она всё-таки успела войти в кластер, и лучше сначала посмотреть uc machine ls.
sudo systemctl status uncloud.service
sudo journalctl -u uncloud.service -n 30Старый Compose-файл не разворачивается: unsupported protocol for ingress port
Самая частая ошибка при переезде с обычного Compose. Виновата привычная короткая запись портов вида "8081:80" — Uncloud её не принимает. Порт сайта переносится в x-ports, а если порт нужен именно на хосте — либо туда же с пометкой @host, либо в длинной записи:
x-ports:
- 8081:80/tcp@host
# или так
ports:
- target: 80
published: "8081"
protocol: tcp
mode: hostА вот labels и networks деплой не ломают: Uncloud напечатает про них предупреждение и продолжит работу.
Вторая машина в состоянии Down, хотя сервер работает
Почти всегда это закрытый UDP-порт 51820: машины не могут построить WireGuard-туннель и не видят друг друга. Проверьте uc wg show — в колонке HANDSHAKEдолжно быть время, а не прочерк. Откройте порт на обеих машинах (sudo ufw allow 51820/udp) и, если у провайдера есть внешний сетевой экран в панели, не забудьте про него: у некоторых российских хостеров UDP по умолчанию прикрыт. После добавления машины дайте кластеру полминуты — состояние обновляется не мгновенно.
Сертификат не выдаётся, сайт открывается с ошибкой безопасности
Первое: Let's Encrypt проверяет домен по 80-му порту, поэтому закрывать его «раз у нас всё равно HTTPS» нельзя. Второе: если A-записи ведут на два сервера, проверка приходит то на одну машину, то на другую, и часть попыток проваливается — подождите несколько минут, Caddy повторит. Что именно происходит, видно в логах:
uc caddy logsИщите строки со словом obtain. Если проверки проваливаются больше получаса подряд, домен наверняка стоит за проксирующим CDN — переключайтесь на DNS-проверку из раздела про домен.
После обновления uc команды перестали работать
Uncloud сверяет версии и отказывается выполнять запрос, если uc на вашем компьютере и демон на сервере несовместимы. Так было при переходе на 0.20: смешанный кластер из 0.19 и 0.20 не работает вообще. Лечение — обновить демон на каждой машине и перезапустить службу:
ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
curl -fsSL -o uncloudd.tar.gz \
https://github.com/psviderski/uncloud/releases/download/v0.20.0/uncloudd_linux_${ARCH}.tar.gz
tar -xf uncloudd.tar.gz
sudo install uncloudd /usr/local/bin/uncloudd
rm uncloudd uncloudd.tar.gz
sudo systemctl restart uncloudПовторно запускать uc machine init для этого нельзя: команда увидит, что машина уже в кластере, и предложит сбросить её — то есть удалить все контейнеры. Правило простое: раз проект до 1.0, перед обновлением читайте примечания к релизу и обновляйте CLI и все машины разом.
Хочется убрать Uncloud с сервера начисто
На сервере есть готовый скрипт удаления. Он снесёт службу, данные, все контейнеры под управлением Uncloud, его сеть Docker и WireGuard-интерфейс. Docker останется.
sudo uncloud-uninstallКоротко: порядок действий
- Два чистых VPS с Ubuntu 22.04/24.04, свободные порты 80 и 443, вход по ключу
- На обоих:
ufw allow 22/tcp,80/tcp,443,51820/udp - На своём компьютере:
curl -fsS https://get.uncloud.run/install.sh | sh uc machine init root@IP-первого -i ~/.ssh/id_ed25519 -n vps1uc machine add root@IP-второго -i ~/.ssh/id_ed25519 -n vps2, на вопрос про Caddy ответитьy- Проверить туннель:
uc wg show compose.yamlсx-portsдля сайта иx-machinesдля базыuc deploy, прочитать план, ответитьy- Две A-записи домена — на оба IP; сертификаты на обеих машинах появятся за несколько минут, это нормально
- Приложение ходит в базу по имени
db.internal, порт наружу не открывать - Настроить бэкапы базы: Uncloud данные не подстрахует
- Обновление: правим
compose.yamlи сноваuc deploy
Для кластера нужны два одинаковых сервера с Docker и свободными портами 80 и 443 — подобрали тарифы, на которых это работает без сюрпризов
VPS для Docker 2026 →