VPSРейтинг
Docker и деплой24 августа 2026 · 22 мин

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.

на своём компьютере (macOS, Linux или WSL)
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 ls
вывод, сокращённый по ширине
NAME   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 show
вывод
PEER   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:

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.

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

Разворачиваем из папки с файлом:

в папке с compose.yaml
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 ls
вывод
NAME    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-записи с одинаковым именем:

ИмяТипЗначение
appA203.0.113.10
appA203.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-файлом:

caddy/compose.yaml
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
caddy/Caddyfile — рядом с compose.yaml
*.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 config
фрагмент вывода
https://app.example.com {
	reverse_proxy 10.210.0.5:80 10.210.1.5:80 {
		import common_proxy
	}
	log
}

Два адреса в reverse_proxy — это контейнеры на разных машинах. И такой конфиг лежит на обеих: любой сервер умеет обслужить запрос контейнером соседа.

Как приложение находит базу на другом сервере

Никакой отдельной настройки не нужно: внутри кластера работает свой DNS. Имя сервиса — это и есть адрес. Тестовый whoami из примера базу, конечно, не использует, но когда вы замените его на своё приложение, строка подключения будет выглядеть так:

фрагмент compose.yaml
    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 его не изображает.

Что важнее — он честен на этот счёт. Мы попробовали развернуть сервис с томом, пока его машина выключена, и получили не тихое создание пустой базы на соседнем сервере, а остановку с внятной ошибкой:

вывод uc deploy при выключенной машине
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 строк лога упали прямо в терминал:

вывод uc deploy
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 перейдёт к следующему контейнеру сразу, как только новый отчитается, что готов.

фрагмент compose.yaml
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 5s
      retries: 3
      start_period: 10s

Если приложение вы собираете сами, Uncloud умеет ещё один приятный трюк: при наличии секции build он собирает образ вашим локальным Docker и заливает его прямо на машины кластера, передавая только недостающие слои. Реестр образов заводить не нужно.

Файрвол: какие порты открыть

Uncloud не трогает ваш файрвол — это ваша забота. Мы посмотрели, что демон реально слушает на сервере, и вот итог.

ПортЗачем
22/tcpSSH — через него uc управляет машиной
80, 443/tcpCaddy: сайты и получение сертификатов
443/udpHTTP/3, необязательно, но лучше открыть
51820/udpWireGuard между машинами. Без него кластер не соберётся
на каждом сервере
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, в покое:

ЧтоПамять
Демон uncloudd57 МБ
Контейнер состояния кластера17 МБ
Контейнер Caddy14 МБ
Итого сверх 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, либо в длинной записи:

два рабочих варианта вместо «8081:80»
    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

Коротко: порядок действий

  1. Два чистых VPS с Ubuntu 22.04/24.04, свободные порты 80 и 443, вход по ключу
  2. На обоих: ufw allow 22/tcp, 80/tcp, 443, 51820/udp
  3. На своём компьютере: curl -fsS https://get.uncloud.run/install.sh | sh
  4. uc machine init root@IP-первого -i ~/.ssh/id_ed25519 -n vps1
  5. uc machine add root@IP-второго -i ~/.ssh/id_ed25519 -n vps2, на вопрос про Caddy ответить y
  6. Проверить туннель: uc wg show
  7. compose.yaml с x-ports для сайта и x-machines для базы
  8. uc deploy, прочитать план, ответить y
  9. Две A-записи домена — на оба IP; сертификаты на обеих машинах появятся за несколько минут, это нормально
  10. Приложение ходит в базу по имени db.internal, порт наружу не открывать
  11. Настроить бэкапы базы: Uncloud данные не подстрахует
  12. Обновление: правим compose.yaml и снова uc deploy

Для кластера нужны два одинаковых сервера с Docker и свободными портами 80 и 443 — подобрали тарифы, на которых это работает без сюрпризов

VPS для Docker 2026 →