Dokploy на VPS: разворачиваем приложение из GitHub с доменом и HTTPS

Приложение собирается без ошибок, Dokploy показывает успешный деплой, но по домену открывается Bad Gateway. Или сайт уже работает, а после следующего обновления пропадают загруженные файлы. Обычно причина находится в конкретной настройке: внутреннем порте контейнера, адресе прослушивания, DNS, переменных окружения или хранилище данных.

В этой инструкции пройдём весь путь: подготовим VPS, установим Dokploy, подключим GitHub, запустим приложение и откроем его по HTTPS. Затем проверим обновление из репозитория и разберём ошибки, которые мешают запуску.

Основной сценарий — один VPS с Ubuntu 24.04 LTS и приложение, которое собирается из Dockerfile. Для первого запуска используем небольшой пример на Node.js. Если у вас уже есть проект с рабочим Dockerfile, используйте его и пропустите создание учебного приложения.

В примерах: 203.0.113.10 — условный IP сервера, panel.example.com — адрес панели Dokploy, app.example.com — адрес приложения. Замените их своими значениями. Команды подготовки сервера выполняются на VPS по SSH; создание файлов и коммитов — в вашем репозитории.

Инструкция подготовлена по документации, доступной на 17 сентября 2026 года. Названия отдельных пунктов интерфейса могут отличаться между версиями.

Содержание

Какой VPS нужен для Dokploy

В документации Dokploy указаны минимальные требования: 2 ГБ оперативной памяти и 30 ГБ диска. Это отправная точка для установки. На том же сервере будут работать ваше приложение, служебная база Dokploy, Docker и обратный прокси Traefik. Во время сборки потребление ресурсов может заметно вырасти.

Для небольшого приложения разумно начать оценку с 2 vCPU, 4 ГБ RAM и 40–60 ГБ SSD или NVMe. Это практический ориентир, а не универсальное требование. Если на VPS будут PostgreSQL, несколько сервисов или тяжёлая сборка Next.js, рассматривайте 8 ГБ RAM либо перенос сборки на отдельный сервер или в CI.

Смотрите на расход памяти во время деплоя. Приложению может хватать нескольких сотен мегабайт после запуска, а установка зависимостей и сборка фронтенда будут требовать значительно больше.

Под такой сценарий можно подобрать NVMe VDS QCKL. При выборе конфигурации учитывайте память для сборки, размер базы и место под Docker-образы. Минимальный тариф стоит выбирать после проверки своего проекта.

Также понадобятся:

  • доступ к VPS по SSH с правами root или sudo;
  • Linux с поддержкой Docker и Docker Swarm;
  • публичный IP, на который можно направить домен;
  • доступ к DNS-зоне домена;
  • аккаунт GitHub и права на подключаемый репозиторий.

Для первой установки удобнее чистый VPS. Если на сервере уже работают HestiaCP, cPanel, Nginx, Apache или другая платформа развёртывания, сначала проверьте занятые порты. Останавливать работающий веб-сервер только ради установки Dokploy нельзя: вместе с ним перестанут открываться существующие сайты.

Подготовка сервера и установка Dokploy

Проверьте ресурсы и занятые порты

Подключитесь к VPS. Вместо ubuntu укажите своего пользователя:

ssh ubuntu@203.0.113.10

Посмотрите сведения о системе, память, диск и слушающие порты:

cat /etc/os-release
free -h
df -h
sudo ss -lntup

Перед установкой порты 80, 443 и 3000 должны быть свободны. Если Docker уже установлен, дополнительно проверьте опубликованные порты контейнеров:

sudo docker ps --format "table {{.Names}}\t{{.Ports}}"
Порт Назначение Какой доступ нужен
22/TCP или ваш порт SSH Управление сервером Предпочтительно со своего IP или через VPN
80/TCP HTTP и проверка домена при HTTP-01 Из интернета для обычной схемы с Let's Encrypt
443/TCP HTTPS Из интернета
3000/TCP на VPS Первичная настройка панели Dokploy Ограниченный доступ; можно использовать SSH-туннель
3000, 8000 и другие порты приложения Связь приложения с Traefik внутри Docker Публиковать на VPS для работы домена обычно не требуется
5432, 3306, 6379 PostgreSQL, MySQL, Redis Внутренняя сеть, если внешнее подключение специально не требуется

Проверьте и firewall внутри ОС, и сетевой firewall провайдера, если он используется. У Docker есть особенность: опубликованные порты контейнеров могут обходить привычные правила UFW. Поэтому запись ufw deny 3000 сама по себе не доказывает, что панель закрыта извне. Для начальной настройки ограничьте этот порт на внешнем firewall, а после подключения HTTPS уберите его публикацию.

Запустите официальный установщик

Установите необходимые утилиты:

sudo apt update
sudo apt install -y ca-certificates curl git dnsutils

Скачайте установщик с официального домена Dokploy, просмотрите его перед запуском и выполните с правами администратора:

curl -fsSL https://dokploy.com/install.sh -o /tmp/dokploy-install.sh
sudo sh /tmp/dokploy-install.sh

Установщик подготовит компоненты и установит Docker, если его ещё нет. Для первого рабочего сервера используйте стабильный выпуск. Canary предназначен для проверки новых изменений.

После завершения проверьте состояние:

sudo docker service ls
sudo docker ps

Если сервис Dokploy не выходит в рабочее состояние, сначала прочитайте журнал:

sudo docker service logs --tail 100 dokploy

Повторный запуск установщика не исправит нехватку диска, занятый порт или ошибку сети. Сначала устраните причину из журнала.

Домен и HTTPS для панели Dokploy

Создайте DNS-записи

В DNS-зоне домена добавьте две записи:

Тип Имя Значение
A panel 203.0.113.10
A app 203.0.113.10

Записи нужно создавать у того DNS-провайдера, чьи NS-серверы сейчас назначены домену. Изменение зоны у регистратора ничего не даст, если домен уже делегирован другому сервису.

На первом запуске с Cloudflare удобно оставить эти записи в режиме DNS only. Так проще проверить прямое соединение с VPS. Проксирование можно включить после проверки HTTPS.

Проверьте ответы DNS:

dig @1.1.1.1 panel.example.com A +short
dig @1.1.1.1 app.example.com A +short
dig @8.8.8.8 app.example.com A +short
dig app.example.com AAAA +short
dig panel.example.com AAAA +short

В режиме DNS only A-записи должны возвращать IP вашего VPS. Если есть AAAA-запись, её IPv6-адрес тоже должен вести на этот сервер с работающими портами 80 и 443. Устаревшая AAAA-запись способна сломать HTTPS, даже когда IPv4 настроен правильно. Если IPv6 для сайта не используется, такую запись нужно убрать.

Ожидайте обновления в пределах TTL записей. Если разные DNS-серверы продолжают возвращать разные адреса, сначала разберитесь с зоной и кэшем, затем запрашивайте сертификат.

Создайте администратора и включите HTTPS

Первичный адрес панели — http://203.0.113.10:3000. Чтобы не передавать пароль через публичное HTTP-соединение, откройте панель через SSH-туннель. Выполните на своём компьютере в отдельном терминале:

ssh -N -L 3000:127.0.0.1:3000 ubuntu@203.0.113.10

Пока соединение открыто, перейдите в браузере на http://localhost:3000 и создайте администратора. Локальный порт 3000 на компьютере должен быть свободен.

В настройках самой панели, в разделе Server или Web Server, укажите panel.example.com, включите HTTPS и выпуск сертификата Let's Encrypt. Если форма запрашивает email, укажите рабочий адрес.

Откройте https://panel.example.com в новой вкладке и проверьте вход. После этого уберите публикацию порта 3000 стандартного сервиса Dokploy:

sudo docker service update --publish-rm "published=3000,target=3000,mode=host" dokploy

Выполняйте эту команду только после проверки HTTPS: она отключает вход по IP:3000 и прежний SSH-туннель к этому порту. Сохраните доступ к серверу по SSH и включите двухфакторную аутентификацию в панели.

Подключать GitHub удобнее уже после настройки постоянного адреса панели. Иначе при смене IP:3000 на домен придётся проверять адреса обратного вызова и webhook в GitHub App.

Подготовка приложения в GitHub

Dokploy получает код из репозитория, собирает Docker-образ и запускает контейнер. Чтобы проверить эту цепочку без зависимостей от базы данных и сторонних API, создадим маленькое приложение с двумя адресами:

  • / — показывает версию приложения;
  • /health — возвращает ok и HTTP 200.

Создайте в GitHub репозиторий dokploy-demo. Он может быть приватным. Добавьте в его корень следующие файлы и сохраните их в ветке main.

Файл server.mjs

import { createServer } from 'node:http';

const port = Number(process.env.PORT ?? 3000);
const version = 'v1';

const server = createServer((req, res) => {
  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
  res.setHeader('Cache-Control', 'no-store');

  if (req.url === '/health') {
    res.writeHead(200);
    res.end('ok\n');
    return;
  }

  if (req.url === '/') {
    res.writeHead(200);
    res.end(`Приложение работает. Версия: ${version}\n`);
    return;
  }

  res.writeHead(404);
  res.end('Not found\n');
});

server.listen(port, '0.0.0.0', () => {
  console.log(`Listening on 0.0.0.0:${server.address().port}`);
});

process.on('SIGTERM', () => server.close(() => process.exit(0)));

Адрес 0.0.0.0 здесь принципиален: сервер принимает соединения на интерфейсах контейнера. Если приложение слушает только 127.0.0.1 или ::1, Traefik из другого контейнера до него не доберётся.

Файл Dockerfile

FROM node:24-bookworm-slim
WORKDIR /app

ENV NODE_ENV=production
ENV PORT=3000

COPY --chown=node:node server.mjs ./server.mjs

USER node
EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD node -e "fetch('http://127.0.0.1:' + process.env.PORT + '/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"

CMD ["node", "server.mjs"]

В примере нет внешних пакетов, поэтому package.json и установка зависимостей не нужны. Проверка здоровья использует уже установленный Node.js и не зависит от наличия curl внутри образа.

EXPOSE 3000 описывает порт приложения. Эта инструкция не открывает порт VPS и не запускает сервер. За запуск отвечает CMD, за доступ по домену — настройка Traefik.

Файл .dockerignore

.git
node_modules
.env
.env.*
npm-debug.log

Файл исключает ненужные данные из контекста сборки. Для защиты от случайного коммита добавьте .env и .env.* также в .gitignore. Сам по себе .dockerignore не мешает отправить секреты в GitHub.

Если разворачиваете собственное приложение

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

Проект Что проверить
Node.js API Команду запуска, переменную PORT, прослушивание 0.0.0.0 и необходимые переменные окружения
Next.js с серверным рендерингом Production-сборку и серверный запуск; для standalone-режима используйте Dockerfile под этот режим
React или Vue на Vite без SSR Сборку статических файлов, обычно в dist, и их выдачу production-веб-сервером
Монорепозиторий Путь приложения, Dockerfile и контекст сборки, содержащий общие пакеты и lock-файл
Проект с несколькими сервисами Docker Compose, выбор веб-сервиса для домена, внутренние соединения и постоянные тома

Для статического Vite-проекта не оставляйте npm run dev или vite preview в качестве production-сервера. Соберите файлы и отдавайте их через Nginx либо подходящий режим публикации статики. В Dokploy для Static и Nixpacks с Publish Directory внутренний порт веб-сервера — 80.

Если используете Nixpacks или Railpack, проверьте определённые ими команды и версию среды. Dockerfile удобен, когда эти параметры нужно явно хранить вместе с кодом.

Подключение GitHub и первый деплой

Создайте и установите GitHub App

  1. Откройте Git Sources в Dokploy и выберите GitHub.
  2. Укажите, где находится репозиторий: в личном аккаунте или организации.
  3. Нажмите Create GitHub App и задайте уникальное имя.
  4. После создания нажмите Install.
  5. Разрешите доступ к нужному репозиторию и завершите Install & Authorize.
  6. Вернитесь в Dokploy и проверьте, что репозиторий появился в списке.

Создание GitHub App и её установка — разные действия. Если приложение создано, но не установлено в нужный аккаунт или организацию, Dokploy не получит доступ к репозиторию.

Для приватного проекта не требуется включать публичный доступ. Достаточно разрешить чтение через интеграцию. Другой вариант — подключение через Git по SSH с отдельным ключом доступа к репозиторию.

Создайте Application

В Dokploy создайте проект, откройте окружение production и добавьте сервис типа Application. Для учебного примера укажите:

Поле Значение
Source GitHub
Repository Ваш dokploy-demo
Branch main
Build Type Dockerfile
Dockerfile Path Dockerfile
Docker Context Path .
Docker Build Stage Оставить пустым: в примере один этап

Сохраните настройки и нажмите Deploy. В Deployments откройте журнал сборки. Затем перейдите в Logs и найдите сообщение:

Listening on 0.0.0.0:3000

Журнал сборки и журнал работающего приложения решают разные задачи. Успешная сборка означает, что образ удалось создать. Приложение после этого ещё может завершиться из-за отсутствующей переменной, недоступной базы или неправильной команды запуска.

Добавьте переменные своего приложения

В учебном примере необходимые значения уже заданы в Dockerfile. Для реального проекта заполните Environment: строку подключения к базе, ключи интеграций, секрет сессии и публичный URL, если приложение их требует.

Названия берите из документации проекта. Переменная APP_URL не является универсальной настройкой Dokploy: другое приложение может ожидать совсем другое имя.

Учитывайте, когда используется значение:

  • При запуске: например, адрес базы и серверный API-ключ. После изменения нужно пересоздать приложение с новым окружением.
  • При сборке: например, публичные переменные фронтенда. После изменения нужен новый образ.

В Next.js значения NEXT_PUBLIC_* встраиваются в клиентский код во время сборки. В Vite публичные VITE_* тоже доступны пользователю браузера. Пароли и закрытые API-ключи в такие переменные помещать нельзя.

Для Dockerfile несекретные параметры сборки задавайте через Build Time Arguments и соответствующие ARG в Dockerfile. Токены для скачивания приватных зависимостей передавайте через Build-time Secrets и секретные монтирования BuildKit. Не записывайте их в Dockerfile и слои образа.

Подключение домена и HTTPS к приложению

DNS-запись app.example.com уже должна указывать на VPS. Откройте Domains внутри Application и создайте домен:

Поле Для нашего примера
Host app.example.com
Path /
Internal Path Без дополнительного префикса
Strip Path Выключено
Container Port 3000
HTTPS Включено
Certificate Let's Encrypt

В Host вводится имя без https://, пути и номера порта. В Container Port указывается порт процесса внутри контейнера. Это не внешний 443 и не произвольно выбранный свободный порт VPS.

Путь запроса выглядит так:

Браузер → VPS:443 → Traefik → контейнер приложения:3000

Traefik принимает HTTPS, а до приложения на этом VPS обращается по HTTP внутри Docker. Самому учебному приложению сертификат не нужен.

Не добавляйте для этого маршрута публикацию 3000:3000 в Advanced → Ports. Она не требуется для домена и может конфликтовать с другими сервисами.

Для Application изменения домена применяются через динамическую конфигурацию Traefik. Для Docker Compose и шаблонов после изменения Domains требуется повторный Deploy, поскольку маршрутизация задаётся метками контейнеров.

Проверьте результат извне

На своём компьютере выполните:

curl -i --max-time 15 https://app.example.com/health
curl -i --max-time 15 https://app.example.com/

Первый запрос должен вернуть HTTP 200 и ok. Второй — строку Приложение работает. Версия: v1. Не добавляйте -k: этот параметр отключает проверку сертификата и скрывает проблему HTTPS.

Отдельно проверьте перенаправление с HTTP:

curl -sS --max-time 15 -o /dev/null -D - http://app.example.com/

Ожидается перенаправление на HTTPS с правильным доменом в заголовке Location. Если ответ ведёт на старый адрес, IP или другой поддомен, проверьте редиректы в приложении, Traefik и Cloudflare.

Если используете Cloudflare

После проверки прямого HTTPS можно включить проксирование DNS-записи. Для соединения Cloudflare с VPS используйте Full (strict): на сервере должен быть действующий сертификат для нужного имени.

Flexible передаёт запросы от Cloudflare к серверу по HTTP. Если сервер перенаправляет их обратно на HTTPS, возникает цикл и ошибка ERR_TOO_MANY_REDIRECTS.

Если меняете общий режим SSL/TLS для всей DNS-зоны, заранее проверьте остальные сайты в этой зоне. Они тоже должны поддерживать выбранный режим.

Cloudflare Origin CA подходит для соединения между Cloudflare и сервером, но обычный браузер не доверяет ему при прямом обращении к VPS. Поэтому предупреждение после отключения проксирования может быть ожидаемым свойством Origin CA. В описанном здесь сценарии используется публично доверенный сертификат Let's Encrypt.

Как проверить автоматический деплой из GitHub

При подключении через GitHub App Dokploy поддерживает автоматическое развёртывание после push в выбранную ветку. Проверьте, что Auto Deploy включён.

  1. Измените в server.mjs значение version с v1 на v2.
  2. Сохраните изменение в ветке main и отправьте его в GitHub.
  3. Откройте Deployments в Dokploy: должна появиться новая запись с нужным коммитом.
  4. Дождитесь завершения и повторите запрос к главной странице.
curl -sS https://app.example.com/

Теперь ожидается Приложение работает. Версия: v2. Эта проверка подтверждает всю цепочку: GitHub отправил событие, Dokploy получил новый код, собрал образ и переключил приложение.

Push в другую ветку не должен обновлять production. Создание тега или GitHub Release также не следует считать заменой push в настроенную ветку: проверьте выбранный тип события.

Для рабочего проекта защитите production-ветку и запускайте проверки до слияния изменений. Обычный автодеплой по push сам по себе не означает, что Dokploy дождётся успешного выполнения всех ваших GitHub Actions.

База данных, загруженные файлы и резервные копии

Как подключить PostgreSQL или другую базу

Добавьте нужный сервис базы данных в Dokploy и используйте его внутренние параметры подключения. Передайте строку подключения приложению через Environment.

localhost внутри контейнера приложения обозначает этот же контейнер. База, работающая отдельным сервисом, должна быть доступна по своему внутреннему имени и порту. Также проверьте общую Docker-сеть, особенно если включали изоляцию сервисов.

Открывать PostgreSQL на публичном порту 5432 ради связи с приложением на том же сервере не нужно. Если приложение подключается к внешней управляемой базе, используйте выданные ей адрес, настройки TLS и правила доступа.

Где хранить пользовательские файлы

Файловая система контейнера не заменяет постоянное хранилище. При пересоздании контейнера локальные изменения могут исчезнуть.

Для каталога загрузок создайте постоянный том в Advanced → Volumes или Mounts. Путь внутри контейнера должен совпадать с тем, куда приложение действительно записывает файлы. Например, том в /app/uploads не поможет, если программа сохраняет загрузки в /tmp/uploads.

Для нескольких серверов или реплик удобнее объектное хранилище либо специально спроектированное общее хранилище. Обычный локальный Docker volume не переносит данные на другой VPS автоматически.

Что резервировать

  • Настройки Dokploy: резервная копия панели включает её PostgreSQL и файловую структуру /etc/dokploy.
  • Базы приложений: отдельные согласованные резервные копии PostgreSQL, MySQL и других СУБД.
  • Пользовательские файлы: тома, загрузки и объектное хранилище.
  • Данные для восстановления доступа: необходимые секреты, ключи шифрования и настройки внешних интеграций — в защищённом хранилище.

Наличие бэкапа панели не доказывает, что сохранены все базы и файлы размещённых приложений.

Встроенные Volume Backups рассчитаны на именованные Docker volumes. Для bind mounts нужен отдельный способ копирования. Если во время архивирования приложение продолжает менять файлы, копия может оказаться несогласованной. Для базы используйте штатный механизм резервирования, а для файлов предусмотрите остановку записи или другой согласованный способ.

Храните копии вне этого VPS. Проверьте восстановление в отдельном окружении: откройте приложение, найдите тестовую запись и скачайте сохранённый файл. Наличие архива в S3 ещё не подтверждает, что из него удастся восстановиться.

Как откатить неудачное обновление

До первого рабочего релиза определите, что именно будете возвращать: код, Docker-образ, конфигурацию или данные. Эти действия не взаимозаменяемы.

Для возврата к сохранённому образу настройте registry в Dokploy. Затем в Deployments → Rollback Settings включите сохранение образов для отката и выберите registry. Возможность вернуться к конкретному релизу появится для тех развёртываний, чьи образы действительно сохранены.

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

Откат приложения не откатывает миграции базы данных. Если новая версия изменила схему, сначала убедитесь, что прежний код с ней совместим. Восстановление базы из старого бэкапа может привести к потере записей, появившихся после его создания.

Автоматический rollback в Docker Swarm нужно настраивать отдельно: он опирается на healthcheck и параметры обновления. Проверка, которая всегда возвращает 200, не заметит сломанную авторизацию или неработающие заказы.

Обновление без заметного перерыва тоже требует подготовки: запаса ресурсов на одновременную работу версий, корректной проверки готовности и совместимых изменений данных. Само наличие Dokploy этого не гарантирует.

Ошибки Dokploy: как найти причину

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

  • код не скачивается — проверяйте GitHub и доступ к репозиторию;
  • образ не собирается — журнал Deployments, зависимости, память и контекст сборки;
  • контейнер завершается — Logs, команду запуска, окружение и базу;
  • контейнер работает, домен не открывается — DNS, Traefik, внутренний порт и сеть;
  • HTTP работает, HTTPS нет — сертификат и проверку домена;
  • после обновления исчезли данные — постоянное хранилище и пути монтирования.

Панель Dokploy не открывается

Проверьте сервисы и журналы:

sudo docker service ls
sudo docker service ps --no-trunc dokploy
sudo docker service logs --tail 100 dokploy
sudo docker service logs --tail 100 dokploy-postgres
df -h
df -i

Если панель открывается через SSH-туннель, но не по домену, её процесс работает: дальше проверяйте DNS и Traefik. Если не работает и локальное подключение, ищите причину в сервисе Dokploy, его базе, ресурсах или сети Docker.

При ошибке инициализации Swarm проверьте адрес сетевого интерфейса. На серверах с несколькими интерфейсами или NAT автоматически выбранный адрес может не подходить. На контейнерных VPS и системах с нестандартным ядром дополнительно проверяйте поддержку сетевых функций Swarm и IPVS. Переустановка панели не добавит отсутствующую возможность ядра.

GitHub не показывает репозиторий или сообщает Repository not found

  • Убедитесь, что GitHub App установлена в аккаунт, которому принадлежит репозиторий.
  • Проверьте, включён ли нужный репозиторий в список разрешённых.
  • Для организации проверьте её ограничения и необходимое одобрение владельца.
  • Проверьте существование выбранной ветки и наличие в ней файлов проекта.
  • При подключении через SSH проверьте выбранный ключ и SSH-адрес репозитория.

Для чужого публичного проекта можно использовать источник Git с HTTPS-адресом репозитория. Такой проект не обязан появляться среди репозиториев вашей GitHub App.

Если исходный репозиторий скачивается, но сборка не получает приватный npm-пакет или Git submodule, это отдельный доступ. Разрешение GitHub App на основной репозиторий не выдаёт автоматически права на все внешние зависимости.

Сборка падает на npm ci, не находит файл или запускается не из той папки

Сообщение или симптом Что исправить
npm ci требует lock-файл Добавьте актуальный package-lock.json. Если проект использует pnpm или Yarn, применяйте соответствующий менеджер и его lock-файл
package.json и lock-файл расходятся Обновите зависимости локально согласованным способом, проверьте сборку и закоммитьте оба файла
package.json или Dockerfile не найден Проверьте ветку, путь к файлу, регистр имени и контекст сборки
COPY не видит общий пакет монорепозитория Включите нужную папку в контекст сборки. Docker не копирует произвольные файлы за его пределами
Локально работает, на Linux модуль не найден Проверьте регистр имён: Header.tsx и header.tsx могут быть разными файлами
Unsupported engine, несовместимая версия runtime Согласуйте версию Node.js или другой среды с требованиями проекта
exec format error или no matching manifest Проверьте архитектуру VPS и образа: amd64 и arm64 должны быть совместимы
ETIMEDOUT, EAI_AGAIN при скачивании зависимостей Проверьте DNS и исходящий доступ к registry из среды сборки, затем состояние самого registry

Не заменяйте ошибку зависимостей случайным набором флагов принудительной установки. Сначала добейтесь воспроизводимой production-сборки из того же коммита.

502 Bad Gateway: контейнер запущен, но сайт не открывается

Если 502 возвращает Traefik, запрос дошёл до прокси, но получить нормальный ответ от приложения не удалось. Проверяйте по порядку:

  1. Внутренний порт. В Domains должен стоять порт из фактического запуска приложения. Если процесс слушает 8080, значение 3000 неверно.
  2. Адрес прослушивания. Нужен доступ через интерфейс контейнера. Прослушивание только 127.0.0.1 или ::1 не подходит.
  3. Состояние процесса. Контейнер мог перезапуститься сразу после успешной сборки.
  4. Healthcheck. Проверьте путь, порт, наличие используемой утилиты и достаточное время на запуск.
  5. Сеть. Traefik должен иметь доступ к контейнеру приложения. Особенно внимательно проверяйте изменённые вручную Compose-сети.
  6. Протокол. HTTP-маршрут должен вести на HTTP-порт приложения, а не на служебный порт другого протокола.

Найдите имя сервиса и ID контейнера:

sudo docker service ls
sudo docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Status}}"

Подставьте найденные значения вместо SERVICE_NAME и CONTAINER_ID:

sudo docker service ps --no-trunc SERVICE_NAME
sudo docker service logs --tail 100 SERVICE_NAME
sudo docker inspect --format '{{json .State}}' CONTAINER_ID

Для Compose-контейнера используйте docker logs CONTAINER_ID, если он не является Swarm-сервисом.

Не удаляйте healthcheck только ради зелёного статуса. Если проверка ошибочная, исправьте её. Если она обнаружила реальную недоступность приложения, исправлять нужно приложение.

404 page not found: домен дошёл до сервера, но маршрут не найден

Сначала отличите ответ Traefik от 404 самого приложения. Проверьте точное имя домена, Path и выбранный сервис. Для Compose после изменения домена выполните Deploy.

Открытие IP вместо доменного имени не проверяет доменный маршрут: Traefik выбирает приложение по имени хоста. Даже на исправно работающем VPS запрос к IP может вернуть 404.

Если главная страница открывается, а обновление страницы вроде /profile возвращает 404, проверьте маршрутизацию приложения. Для статической SPA обычно требуется возврат index.html на клиентские маршруты. Ошибку отсутствующего API-метода таким правилом скрывать не нужно.

Let's Encrypt не выдаёт сертификат

Откройте журнал Traefik в панели или, для стандартного контейнера, выполните:

sudo docker logs --tail 150 dokploy-traefik

Если Traefik у вас запущен как Swarm-сервис, используйте docker service logs с его фактическим именем.

Ошибка Что она означает и что делать
NXDOMAIN Имя не существует в DNS. Проверьте написание, NS-серверы и запись в активной зоне
SERVFAIL DNS не смог корректно ответить. Проверьте делегирование, DNSSEC и доступность авторитетных серверов
Timeout или connection refused Проверьте адреса A/AAAA, firewall и доступность нужного порта снаружи
Unauthorized, invalid response Проверочный запрос получил неправильный ответ. Ищите старый IP, чужой веб-сервер, перехват порта 80, редирект, авторизацию или блокировку в прокси
CAA запрещает выпуск Проверьте CAA домена и родительской зоны. Если ограничения нужны, разрешите letsencrypt.org
Rate limit, too many requests Остановите повторные запросы, исправьте первопричину и дождитесь времени повтора из ответа центра сертификации
Браузер видит стандартный сертификат Traefik Нужный сертификат ещё не получен или не выбран для этого имени. Проверяйте маршрут и ACME-журнал

Для стандартной проверки HTTP-01 порт 80 должен быть доступен и при первом выпуске, и при продлении. Закрытие его после получения сертификата может привести к проблеме спустя время, хотя сайт по HTTPS пока продолжит работать.

Не публикуйте приложение напрямую на внешнем порту 80: этим можно перехватить запросы, предназначенные Traefik. Также не удаляйте acme.json как универсальное «исправление SSL» — в нём находятся данные сертификатов и ACME.

Случайный запрос к несуществующему адресу /.well-known/acme-challenge/test может нормально вернуть 404. Проверку нужно оценивать по реальному запросу выпуска и соответствующему сообщению в журнале.

После исправления DNS или маршрута повторно сохраните настройки домена и следите за ACME-журналом. Если требуется перезапуск Traefik, учитывайте, что он обслуживает все приложения на этом сервере.

Как отделить проблему DNS от проблемы самого VPS

После выпуска сертификата можно обратиться к конкретному серверу, сохранив правильное доменное имя:

curl --resolve app.example.com:443:203.0.113.10 \
  -i --max-time 15 https://app.example.com/health

Если этот запрос работает, а обычный запрос к домену — нет, проверяйте DNS, Cloudflare и другие промежуточные узлы. Если оба запроса не проходят, продолжайте проверку сервера и приложения.

Проверка предполагает, что прямое подключение с вашего IP разрешено firewall. При использовании Cloudflare Origin CA обычная проверка доверия сертификату также будет отличаться от сценария с Let's Encrypt.

Cloudflare показывает 521, 522, 525 или 526

Код Куда смотреть
521 Работает ли веб-сервер и не отклоняет ли он подключения Cloudflare
522 Сетевую доступность, firewall и перегрузку сервера
525 TLS-соединение между Cloudflare и VPS
526 Сертификат на VPS: имя, срок действия и доверие в режиме Full (strict)

Переключение на Flexible не исправляет сертификат сервера. Сначала добейтесь корректного HTTPS на VPS и проверьте правила доступа со стороны Cloudflare.

Push в GitHub есть, а новый деплой не появляется

Проверьте ветку, Auto Deploy и Watch Paths. Фильтр путей может пропускать изменения, которые вы считаете значимыми.

Журнал доставки зависит от способа интеграции:

  • GitHub App: настройки владельца приложения → Developer settings → GitHub Apps → нужное приложение → Advanced → Recent deliveries.
  • Обычный webhook репозитория: Settings → Webhooks → нужный webhook → Recent deliveries.

Откройте событие нужного push и посмотрите URL назначения, ответ и тело ответа. Проверьте, не остался ли там старый IP:3000 после подключения домена панели.

Редирект на страницу входа, Cloudflare Access или CAPTCHA не является успешной обработкой webhook. Доступ должен быть настроен именно для необходимого обработчика; отключать защиту всей панели не требуется.

Ответ HTTP 200 подтверждает получение запроса, но не доказывает завершённый деплой. Следом должна появиться соответствующая запись в Dokploy. Если её нет, сопоставьте время события с журналом панели, настройками ветки и фильтрами. После исправления повторно отправьте событие через Redeliver.

Во время сборки VPS зависает или появляется Exit code 137

Код 137 означает завершение процесса сигналом SIGKILL. Частая причина — нехватка памяти, но по одному коду это не устанавливается.

free -h
sudo docker stats --no-stream
sudo journalctl -k --since "30 minutes ago" | grep -Ei 'oom|out of memory|killed process'

Сопоставьте время сбоя с сообщениями ядра. Для конкретного контейнера дополнительным подтверждением может быть OOMKilled: true в его состоянии.

При подтверждённой нехватке памяти:

  • оставьте одну одновременную сборку в настройках сервера Dokploy;
  • проверьте, не собирает ли один Compose-проект несколько тяжёлых сервисов параллельно;
  • увеличьте RAM или собирайте образ на отдельном build-сервере либо в GitHub Actions;
  • при сборке в CI публикуйте образ в registry и разворачивайте его в Dokploy по определённому тегу или digest.

Swap может смягчить кратковременный пик, но работает медленнее RAM. Увеличение лимита JavaScript heap тоже не добавляет серверу физической памяти.

No space left on device: закончился диск

df -h
df -i
sudo docker system df

Первый вывод показывает свободное место, второй — inode, третий — использование диска Docker. На VPS могут накопиться образы старых релизов, build cache, логи и локальные копии.

Удаляйте то, назначение чего проверили. Не запускайте очистку с удалением volumes вслепую: там могут находиться базы и пользовательские файлы. Перед удалением старых образов убедитесь, что нужные версии сохранены для отката. Для build cache используйте отдельную управляемую очистку.

Сайт открылся, но отдельные функции не работают

Симптом Проверка и следующий шаг
Фронтенд обращается к localhost или старому API Проверьте публичные переменные сборки. Исправьте адрес и соберите новый образ
Mixed Content На HTTPS-странице есть HTTP-запросы. Найдите их в инструментах браузера и исправьте URL ресурсов или API
CORS Проверьте разрешённый origin, протокол и обработку preflight на API. Не открывайте доступ всем источникам вместо настройки нужного домена
После входа снова появляется форма авторизации Проверьте публичный URL, домен cookies, Secure/SameSite и корректное доверие к своему reverse proxy
OAuth сообщает redirect_uri_mismatch Сравните callback URL в приложении и настройках провайдера: протокол, имя, путь и завершающий слеш
Нет соединения с базой Проверьте внутреннее имя, порт, общую сеть, готовность БД, пароль и требования TLS. localhost обычно не подходит для отдельного контейнера
WebSocket или потоковый ответ обрывается Проверьте маршрут, wss для HTTPS-страницы, тайм-ауты и поведение каждого промежуточного прокси
504 или тайм-аут долгой операции Сопоставьте журнал запроса с работой приложения и БД. Длительные задачи лучше выносить в очередь, если они не укладываются в ограничения HTTP-запроса
413 при загрузке файла Проверьте допустимый размер запроса в приложении, веб-сервере и внешнем прокси
Permission denied при записи Сопоставьте пользователя процесса и владельца тома. Настройте нужные права вместо chmod 777 на всё приложение
После деплоя исчезли загрузки Проверьте постоянный том и фактический путь записи. Само наличие тома без правильного монтирования проблему не решает
После обновления виден старый интерфейс Проверьте коммит в Deployments, версию ответа напрямую с VPS, затем браузерный кэш, service worker и CDN

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

Нужно ли заранее устанавливать Nginx или Certbot на VPS?

Для описанной схемы — нет. Внешние HTTP/HTTPS-запросы принимает Traefik, а сертификаты запрашивает настроенный в нём механизм ACME. Дополнительный веб-сервер на портах 80 и 443 может создать конфликт. Nginx внутри контейнера статического сайта — отдельный случай: он работает за Traefik.

Можно ли запустить несколько приложений на порту 3000?

Да, если каждое слушает 3000 внутри собственного контейнера. Traefik направляет запросы по доменам. Конфликт возникает, когда несколько сервисов пытаются занять один и тот же внешний порт VPS.

Можно ли обойтись без собственного домена?

Для временной проверки в Dokploy есть генерируемые адреса traefik.me с HTTP. Рабочее приложение удобнее сразу размещать на своём домене: так проще управлять HTTPS, авториза

  • 0 Пользователи считают это полезным
Помог ли вам данный ответ?

Связанные статьи

Корпоративная почта на базе собственного домена

Корпоративная почта на собственном домене не только придаёт профессиональный...

Установка и настройка Rclone

Rclone — это мощный инструмент командной строки для управления файлами на облачных хранилищах....

Apache vs Nginx: В чем разница, как установить и что выбрать?

  Когда выбираете веб-сервер для вашего проекта, Apache и Nginx часто оказываются в центре...

HTTP Ошибки: частые причины и как исправить

  Ошибка 403: Forbidden Описание: Сервер понимает запрос, но отказывается его выполнять. Обычно...

Let's Encrypt без панели управления

  SSL-сертификаты Let's Encrypt обеспечивают бесплатное и автоматизированное шифрование для...