MAATRIX / Блог / Dokploy: деплой падает — причины и решение

Dokploy: деплой падает — причины и решение

Dokploy: деплой падает — причины и решение

MAATRIX

Вы нажали Deploy, лог пробежал зелёным, а карточка приложения стала красной без объяснений. Или наоборот: деплой отмечен успешным, а домен отдаёт 502. Жалоба «dokploy деплой не работает» распадается на три разных сбоя — на клонировании, на сборке или в момент, когда Swarm запускает задачу. Ниже — как понять, какая стадия упала у вас, с точными текстами ошибок и командами.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Где именно падает деплой: три стадии и как их различить

Dokploy выкатывает в три шага: клонирует репозиторий в /etc/dokploy/applications/<имя>/code, собирает образ (Nixpacks, ваш Dockerfile, buildpacks или Compose) и отдаёт результат Docker Swarm, который сводит сервис к нужному числу реплик. Веб-лог показывает один сплошной поток, и настоящая ошибка уезжает вверх за строками установки пакетов, поэтому смотрите не только в браузер:

docker service ls --filter name=myapp
docker service ps myapp --no-trunc | head -5
docker service logs --since 10m myapp 2>&1 | tail -40

Главный индикатор — колонка REPLICAS: 1/1 значит, что задача запущена и живёт, 0/1 — Swarm либо не смог её разместить, либо она падает по кругу. Флаг --no-trunc обязателен: без него ошибка обрезается ровно там, где начинается смысл. Сборку выполняет сама панель (в неё проброшен /var/run/docker.sock), её логи — docker logs dokploy --since 15m, сохранённые логи деплоев лежат в /etc/dokploy/logs/.

Что видно в логеСтадияРаздел
Permission denied (publickey)clone2
exit code: 137, no space left on devicebuild3
no suitable node, task: non-zero exit (1)deploy4
Деплой зелёный, браузер даёт 502 или 404после deploy5

Стадия clone: ключи, ветка и вебхук, который не долетел

Лог обрывается на первых строках — до сборки дело не дошло. Три типовых сюжета.

fatal: could not read Username for 'https://github.com': No such device or address — приватный репозиторий тянется по HTTPS без учётных данных, а спросить их интерактивно git в контейнере не может. Подключите провайдера в разделе Git панели или переведите источник на SSH.

git@github.com: Permission denied (publickey). — ключ есть, но репозиторий его не знает. Ключи создаются в разделе SSH Keys, публичную часть кладут в Settings → Deploy keys репозитория, а не в личный профиль. Проверка — ssh -i /etc/dokploy/ssh-keys/<имя>/id_rsa -T git@github.com, рабочий ответ начинается с Hi user/repo! You've successfully authenticated. Соседняя ошибка Host key verification failed. бывает со своим Gitea или GitLab на нестандартном порту: ssh-keyscan -p 2222 git.example.com >> ~/.ssh/known_hosts.

fatal: Remote branch develop not found in upstream origin — ветку переименовали в main, а в карточке приложения осталось старое имя. Рядом вторая мелочь: Build Path для монорепозитория. Сервис лежит в apps/web, а путь оставлен / — получите failed to read dockerfile: open Dockerfile: no such file or directory.

Если автодеплой не срабатывает вовсе, откройте Settings → Webhooks → Recent Deliveries. Код 200 значит, что виновата настройка приложения: выключен тумблер Autodeploy или ветка вебхука не та. Ошибка соединения или таймаут — панель недоступна снаружи: она слушает 3000, и трафик гит-платформы должен до неё доходить (ufw allow 3000/tcp, а лучше домен с HTTPS перед ней).

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть Dokploy

Стадия build: exit code 137 — это память, а не ваш код

Самая частая причина, по которой Dokploy деплой не работает на исправном проекте:

#14 ERROR: process "/bin/sh -c npm run build" did not complete successfully: exit code: 137

137 — это 128 + 9, то есть SIGKILL: своей волей сборщик так не завершается, его убило ядро. Подтверждает dmesg -T | grep -iE 'out of memory|killed process' | tail -3:

[Wed Aug 26 03:11:42 2026] Out of memory: Killed process 44123 (node)
total-vm:5142204kB, anon-rss:2374112kB, file-rss:0kB, shmem-rss:0kB, UID:0

Соседний случай — FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory: это лимит кучи V8, а не память машины, лечится переменной NODE_OPTIONS=--max-old-space-size=3072. Выше физической памяти кучу поднимать бессмысленно: внятная ошибка Node сменится глухим exit code: 137.

Замеры пика RSS на 2 vCPU и Node 22: Vite с React на трёхстах модулях просит 0,8–1,0 ГБ, Next.js 15 среднего сайта — 2,2–2,6 ГБ, Nuxt 3 и Angular 18 — до 3 ГБ. Прибавьте сам Dokploy: панель с Postgres, Redis и Traefik в простое занимает около 330 МБ (сколько RAM нужно для Dokploy). Быстрая подушка — swap, которого на VPS обычно нет:

swapon --show                                    # пусто — swap отсутствует
fallocate -l 4G /swapfile && chmod 600 /swapfile
mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

Честно про цену: swap спасает сборку от смерти, но не ускоряет её. Та же сборка Next.js на 4 ГБ занимала 3 минуты 40 секунд, на 2 ГБ со swap — около 11 минут. Это страховка, а не конфигурация для прода.

Вторая беда стадии build — диск: failed to prepare extraction snapshot: write /var/lib/docker/overlay2/8f3a.../diff: no space left on device.

$ docker system df                      # и df -i: иногда кончаются inode, а не байты
TYPE            TOTAL   ACTIVE   SIZE      RECLAIMABLE
Images          38      6        21.4GB    17.8GB (83%)
Build Cache     412     0        9.1GB     9.1GB

Через месяц работы 40-гигабайтный диск забивается слоями и кешем без единого «тяжёлого» приложения. Чистка — docker builder prune -af --filter until=168h и docker image prune -af. Но docker system prune -a на узле Swarm бездумно не запускайте: он снесёт образы, не занятые работающими сервисами, включая тот, на который вы собирались откатиться (откат в Dokploy — это запуск предыдущего тега образа). Остальные поломки панели — в статье частые ошибки Dokploy.

Стадия deploy: образ собран, а Swarm не сводит сервис

Сборка успешна, а приложение красное или бесконечно «деплоится» — вся правда в одной команде:

$ docker service ps myapp --no-trunc
NAME      DESIRED STATE   CURRENT STATE           ERROR
myapp.1   Ready           Pending 4 minutes ago   "no suitable node (insufficient resources on 1 node)"

no suitable node (insufficient resources on 1 node). На вкладке Advanced в блоке Resources два разных поля. Memory Reservation — заявка, Swarm вычитает её из ёмкости узла ещё до запуска; Memory Limit — потолок контейнера. Вписали резервацию 4096 на машине с 4 ГБ — планировщик не найдёт места, и задача останется в Pending навсегда: деплой выглядит зависшим, хотя работает как задумано. Держите резервацию в пределах 256M512M, ограничивайте лимитом.

task: non-zero exit (1). Образ запустился и умер сам, причина в логах приложения: docker service logs myapp --since 5m. Классика — старт раньше базы и ECONNREFUSED 10.0.1.7:5432 либо отсутствующая переменная. А exit (137) здесь значит другое, чем при сборке: контейнер вылез за Memory Limit.

Bind for 0.0.0.0:5432 failed: port is already allocated. Порт опубликован во вкладке Advanced → Ports, а он уже занят. Внутри одного сервера публиковать порты приложениям не нужно вовсе: они общаются через overlay-сеть по имени сервиса.

Обновление встало на полпути. Проверьте статус rolling update:

docker service inspect myapp --format '{{.UpdateStatus.State}}: {{.UpdateStatus.Message}}'
# paused: update paused due to failure or early termination of task

Так бывает, когда в Dockerfile есть HEALTHCHECK, а приложение поднимается дольше, чем даёт проверка: Swarm считает задачу неудачной и замирает, оставив работать старую версию. Лечится параметром start_period: 60s и повторным docker service update --force myapp.

Деплой зелёный, а домен отдаёт 502 или 404

Панель рапортует об успехе, docker service ls показывает 1/1, а браузер — нет: виноват маршрут, а не деплой. 404 page not found от Traefik значит, что роутера для домена нет:

docker service inspect myapp --format '{{json .Spec.Labels}}' | jq
docker logs dokploy-traefik --since 10m 2>&1 | tail -20

Меток traefik.http.routers.* нет — домен не добавлен во вкладке Domains либо добавлен другому приложению. Метки есть, а 404 остаётся — проверьте A-запись: dig +short app.example.com должен вернуть IP этого сервера, а не CDN.

502 Bad Gateway — роутер есть, бэкенд недостижим, и обе причины про порт. Первая: в карточке домена указан не тот Container Port, Dokploy его не угадывает. Вторая коварнее: приложение слушает 127.0.0.1 — внутри контейнера работает, из соседнего нет.

docker exec -it $(docker ps -q -f name=myapp) sh -c 'ss -ltnp || netstat -ltnp'
LISTEN 0 511 127.0.0.1:3000 0.0.0.0:*  users:(("node",pid=1,fd=21))

Адрес 127.0.0.1:3000 вместо 0.0.0.0:3000 — вот и весь 502. Правится в приложении: app.listen(3000, '0.0.0.0'), next start -H 0.0.0.0, vite preview --host 0.0.0.0, 0.0.0.0:8000 для Gunicorn и Uvicorn.

Третья причина — сеть. Traefik и приложения должны быть в одной overlay-сети dokploy-network. Созданные через панель попадают в неё сами, а при деплое своего docker-compose.yml сеть подключают руками — этот шаг забывают чаще прочих:

services:
  web:
    image: myapp:latest
    networks: [dokploy-network]
networks:
  dokploy-network:
    external: true

И последнее: стоящий на хосте Nginx или Apache держит 80 и 443, Traefik не стартует, и 404 получают все приложения разом. Проверка — ss -ltnp | grep -E ':80 |:443 '.

Переменные окружения, которые ломают деплой молча

Здесь деплой обычно проходит успешно, а результат оказывается неправильным.

Build-time против runtime. Nixpacks отдаёт переменные и в сборку, а сборка по вашему Dockerfile — нет: туда значения приходят только как ARG. Отсюда классика — next build, падающий на prisma generate с Error: Environment variable not found: DATABASE_URL. Объявите аргумент с заглушкой, настоящее значение отдайте в рантайме:

ARG DATABASE_URL="postgresql://build:build@localhost:5432/build"
ENV DATABASE_URL=$DATABASE_URL
RUN npx prisma generate && npm run build

Значение вшито в бандл. Всё, что начинается с NEXT_PUBLIC_ или VITE_, попадает во фронтенд на этапе сборки. Поменяли переменную, нажали Deploy — а в браузере старый адрес API: слой со сборкой взялся из кеша. Нужен Redeploy с отключённым кешем либо docker builder prune -af перед выкатом.

Кавычки, CRLF и доллар. Редактор переменных сохраняет строку как есть, поэтому DATABASE_URL="postgres://user:pass@db:5432/app" даёт значение, начинающееся с символа ", а драйвер отвечает невнятным invalid URI query parameter. Проверка — docker exec $(docker ps -q -f name=myapp) printenv DATABASE_URL | od -c | tail -2: ищите " в начале и \r в конце (копирование из Windows). А при деплое через Compose Docker раскрывает $: пароль p$w0rd станет p плюс пустая подстановка, экранируйте как p$w0rd.

Общие переменные проекта. Ссылка ${{project.DATABASE_URL}} на несуществующую переменную не роняет деплой: она разворачивается в пустоту, и приложение падает уже в рантайме с сообщением, по которому причину не угадать. После правки общих переменных перевыкатывайте все приложения проекта.

Какой сервер под Dokploy взять в MAATRIX

Главный вывод разбора: сама панель почти ничего не потребляет, деплои падают из-за сборок. Считайте ресурсы по пику сборки, а не по потреблению работающего приложения.

Минимум: 2 vCPU, 4 ГБ RAM, 60 ГБ NVMe. Панель со службами занимает около 330 МБ, ещё 200–400 МБ — работающее приложение, остальное уходит сборке. Next.js и Nuxt собираются впритык, swap на 4 ГБ обязателен. Честное ограничение: во время сборки боевое приложение на этом же сервере заметно тормозит. Для одного-двух проектов с редкими выкатами нормально, для десятка деплоев в день — нет.

Комфортный вариант: 4 vCPU, 8 ГБ RAM, 100–160 ГБ NVMe. Сборка перестаёт задевать swap и укладывается в 2–4 минуты, рядом спокойно живут Postgres, Redis и три-четыре приложения, места хватает на кеш слоёв и историю образов для отката. Приложений больше десяти — берите 16 ГБ либо выносите сборку на отдельный узел Swarm. Диск не экономьте: 2–4 ГБ на образ плюс 10–15 ГБ кеша; именно место, а не память, чаще всего роняет деплой на второй месяц жизни сервера.

Локация — Великобритания, Лондон. RTT до панели чувствуется каждым кликом: из Москвы до Лондона 45–55 мс против 120–140 мс до Восточного побережья США. Плюс короткий маршрут до GitHub, npm и Docker Hub: сборка тянет сотни мегабайт зависимостей, и канал здесь заметнее, чем на отдаче страниц. Если аудитория российская и есть персональные данные — локация RU и 152-ФЗ; если приложение ходит в зарубежные ИИ-API — US.

Dokploy есть в каталоге apps.maatrix.io и при заказе сервера разворачивается автоматически — вставлять установочный скрипт не нужно. Автоустановка работает на Ubuntu и Debian; адрес панели, логин и пароль появляются в личном кабинете в разделе «Доступ», дальше вы сразу добавляете репозиторий и домен. Оплата — картой российского банка, по СБП, криптовалютой или токеном MAAT. Если ещё выбираете панель, посмотрите Coolify против Dokploy; нужен разбор с нуля — установка Dokploy на VPS.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть Dokploy

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Частые вопросы

Деплой падает с exit code: 137, хотя локально всё собирается.

Локально у вас 16 ГБ, на VPS — 2. Код 137 значит, что сборку убил OOM killer: подтвердите через dmesg -T | grep -i 'killed process', добавьте swap на 4 ГБ и переходите на 4–8 ГБ RAM.

Сервис висит в 0/1, в логах ничего нет. Что смотреть?

docker service ps myapp --no-trunc. Строка no suitable node (insufficient resources on 1 node) означает завышенное поле Memory Reservation на вкладке Advanced. Снизьте резервацию до 256–512 МБ.

Деплой успешен, но домен отдаёт 502. Куда копать?

В девяти случаях из десяти — порт: в карточке домена указан не тот Container Port либо приложение слушает 127.0.0.1 вместо 0.0.0.0. Проверьте изнутри контейнера через ss -ltnp.

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.