Windmill на сервере: частые ошибки и решения
Windmill превращает Python- и TypeScript-скрипты во внутренние инструменты и флоу без своей команды фронтенда, но именно из-за этой гибкости его сложнее диагностировать: сервер, воркеры, LSP и Postgres — разные процессы, и ошибка в одном маскируется под симптом в другом. Разбираем, где реально искать причину, когда задачи зависают в очереди, скрипты падают на установке зависимостей или прокси отдаёт 502.
Содержание
- Архитектура Windmill: какой процесс за что отвечает
- Сервисы не поднимаются: ошибки при первом запуске
- PostgreSQL: миграции и «задача в очереди, но не выполняется»
- Воркеры не берут задачи или падают по памяти
- Скрипты падают на установке зависимостей
- Reverse proxy, HTTPS и вебхуки
- Апдейты и совместимость версий
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Архитектура Windmill: какой процесс за что отвечает
Прежде чем чинить, нужно понимать, кто есть кто в стеке. Стандартный docker-compose.yml из репозитория Windmill поднимает минимум пять сервисов:
| Сервис | Роль | Что смотреть при сбое |
|---|---|---|
windmill_server | API, веб-интерфейс | docker compose logs windmill_server |
windmill_worker | исполняет скрипты и флоу | зависшие задачи, память, CPU |
windmill_worker_native | лёгкие HTTP-джобы без полного рантайма | падает редко |
lsp | автодополнение в редакторе кода | не мешает флоу, если упал |
db | PostgreSQL, единственное хранилище состояния | миграции, LISTEN/NOTIFY, место на диске |
Дальше идёт caddy (или свой Nginx/Traefik), который терминирует TLS и проксирует и UI, и API, и вебсокет LSP на одном хосте. Первый диагностический шаг всегда один:
docker compose ps
docker compose logs -f --tail=200 windmill_server
docker compose logs -f --tail=200 windmill_worker
Если windmill_server в рестарт-лупе, а воркеры при этом «Running» — проблема почти всегда в Postgres или в переменных окружения сервера. Если сервер жив, UI открывается, но задачи не выполняются — ищите проблему на стороне воркеров или очереди. Смотрите логи раздельно по сервисам, иначе вывод смешается и контекст потеряется.
Сервисы не поднимаются: ошибки при первом запуске
Самая частая причина падения windmill_server сразу после docker compose up -d — некорректный DATABASE_URL в .env:
error: error connecting to database: db error: FATAL: password authentication failed for user "postgres"
Проверьте, что пароль совпадает с POSTGRES_PASSWORD контейнера db, и что спецсимволы в нём URL-кодированы (@ → %40, # → %23) — иначе парсер строки подключения обрежет её раньше времени. Формат:
DATABASE_URL=postgres://windmill:ВАШ_ПАРОЛЬ@db:5432/windmill?sslmode=disable
Вторая типичная ошибка — конфликт порта на хосте:
Error starting userland proxy: listen tcp4 0.0.0.0:8000: bind: address already in use
Порт 8000 в дефолтном compose — внутренний API, наружу его обычно не публикуют (только через caddy на 80/443). Если вы прокинули 8000 сами и он занят, смените маппинг в docker-compose.yml, не трогая внутренний порт контейнера.
Третье: сервер стартует и не падает, но веб-интерфейс открывается пустым или зависает на загрузке — почти всегда BASE_URL не совпадает с тем адресом, по которому вы реально заходите (например, задан http://localhost при заходе по внешнему домену). Фронтенд обращается к API по адресу из BASE_URL, промах даёт в консоли браузера ошибки CORS или Failed to fetch. Правьте в .env и пересоздавайте контейнер сервера — просто restart переменные не подхватит:
docker compose up -d --force-recreate windmill_server
Отдельно про первого пользователя: суперадмин-права инстанса автоматически получает первый аккаунт, зарегистрированный на пустой базе. Если разворачивание автоматизировано и пользователи создавались через API раньше обычной регистрации через UI, есть риск остаться без доступа к Instance Settings. Пока в базе нет реальных данных, проще снести том Postgres и поднять стек заново, чем патчить права через SQL вслепую.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверPostgreSQL: миграции и «задача в очереди, но не выполняется»
Windmill гоняет миграции схемы автоматически при старте windmill_server. Если воркеры поднимаются одновременно с сервером без ожидания готовности БД, в их логе можно увидеть:
error: relation "queue" does not exist
Это не повреждение данных — просто воркер обратился к таблице до того, как сервер закончил миграцию. Лечится через depends_on с healthcheck у Postgres, чтобы воркеры и сервер ждали реальной готовности базы, а не просто запуска контейнера:
db:
image: postgres:16-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U windmill"]
interval: 5s
timeout: 5s
retries: 10
windmill_server:
depends_on:
db:
condition: service_healthy
Более тонкий случай: сервер запущен, миграции прошли, задачи ставятся в очередь через UI, но ни один воркер их не забирает — при этом в docker compose ps все контейнеры зелёные. Прежде чем подозревать зависание, проверьте таблицу очереди напрямую:
docker compose exec db psql -U windmill -d windmill -c \
"select id, script_path, tag, started_at from queue order by created_at desc limit 10;"
Если задачи видны и у них проставлен tag, а воркер этот тег не слушает — см. следующий раздел. Если очередь вообще не растёт при клике «Run» — проблема на стороне сервера, не воркера. Нюанс, который редко описывают: если Postgres стоит за пулером в режиме транзакций (PgBouncer с pool_mode=transaction), уведомления LISTEN/NOTIFY, на которых построена мгновенная доставка задач, до воркеров не долетают. Задачи всё равно подхватятся периодическим опросом очереди, но с задержкой в несколько секунд вместо мгновенной реакции — если это критично, подключайте Windmill к Postgres напрямую, без пулера в transaction mode. Общие грабли самого Postgres разобраны отдельно: PostgreSQL на сервере — частые ошибки.
Воркеры не берут задачи или падают по памяти
Кроме проблем на стороне БД, есть чисто воркерская причина зависших задач — несовпадение тегов. У каждого скрипта можно указать tag (worker group), и если для отдельной группы под тяжёлые джобы ни один воркер не поднят, задача вечно стоит в статусе «Waiting». Проверка — переменная WORKER_GROUP (или WORKER_TAGS, если воркер слушает несколько тегов) в окружении контейнера:
docker compose exec windmill_worker env | grep -i worker
Сравните значение с тегом зависшего скрипта в UI (вкладка Advanced → Concurrency & tags). Несовпадение — самая частая причина «Windmill завис» на инстансах, где кто-то один раз завёл выделенную группу под ML-джобы и забыл про неё.
Вторая причина — воркер убивает ядро по памяти. Скрипты выполняются в отдельных процессах воркера, и тяжёлые операции (большой CSV в pandas, парсинг PDF, эмбеддинги) могут выесть всю доступную контейнеру память:
docker inspect windmill_worker_1 --format '{{.State.OOMKilled}} exit={{.State.ExitCode}}'
OOMKilled=true и exit=137 — контейнеру не хватило памяти, задача прервалась без внятной ошибки в самом флоу. Если такие джобы регулярны, вынесите их в отдельную группу воркеров с большим лимитом памяти и ограничьте параллелизм через mem_limit в compose, чтобы одна тяжёлая задача не отбирала память у остальных.
Масштабирование воркеров при росте нагрузки — обычный --scale:
docker compose up -d --scale windmill_worker=3
Но реплики не спасают от нехватки RAM на хосте в целом: три воркера на 2 ГБ памяти конкурируют за те же 2 ГБ. docker stats быстро покажет, где потолок.
Скрипты падают на установке зависимостей
Windmill резолвит зависимости Python-скрипта из import-ов в коде и ставит их на воркере при первом деплое (с кэшированием). Если воркер сидит за фаерволом без исходящего HTTPS на PyPI, деплой скрипта падает с ошибкой вида:
Error building the lockfile: Could not install requirements:
Failed to connect to pypi.org
Проверка минимальна — docker compose exec windmill_worker curl -I https://pypi.org. Если политика режет прямой выход в интернет, нужен внутренний зеркальный индекс и PIP_INDEX_URL, проброшенный в окружение воркера, а не только на хост. То же с TypeScript-скриптами на Deno/Bun — им нужен доступ к deno.land/npm-реестру, и HTTP_PROXY/HTTPS_PROXY нужно явно прокинуть в контейнер воркера — переменные хоста контейнеру не наследуются автоматически.
Второе, что копится незаметно, — кэш зависимостей: каждая версия каждого пакета оседает в именованном томе воркера, и за месяцы активной разработки том может занять заметную часть диска (docker system df -v | grep -i windmill). Не удаляйте том «на живую» — это положит текущие выполнения; сначала docker compose stop windmill_worker, потом чистка. Общая методология поиска места на VPS — в статье про нехватку места на диске, принципы те же.
Третье — таймауты. Если долгий скрипт обрывается без явной ошибки в самом флоу, проверьте лимит времени выполнения во вкладке Advanced скрипта: дефолт рассчитан на короткие джобы, а не многочасовые батчи.
Reverse proxy, HTTPS и вебхуки
Дефолтный docker-compose.yml Windmill идёт со своим Caddy, который сам получает сертификат и проксирует всё разом. Если вы заменяете его на свой Nginx (например, чтобы держать несколько сервисов на одном сервере с единой конфигурацией), нужно закрыть три момента, иначе получите обрывы:
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 600s;
}
Upgrade/Connection обязательны — редактор держит вебсокет с LSP для автодополнения, без апгрейд-заголовков подсказки типов не приходят, а в консоли браузера сыплются обрывы соединения. proxy_read_timeout увеличен не просто так: длинные флоу и стриминг логов по умолчанию упираются в 60-секундный таймаут Nginx и выглядят как зависшая задача, хотя воркер продолжает работать.
Второе — BASE_URL должен буквально совпадать с внешним адресом за прокси. Если он указывает на внутренний хост или другой порт, вебхук-URL, который Windmill генерирует для триггера флоу по HTTP, будет указывать не туда, и внешний сервис будет получать отказ соединения — при этом внутри UI всё выглядит настроенным правильно.
Третье — отдельные поддомены под API и UI без единого прокси перед ними дают классический CORS: браузер блокирует запросы фронтенда к API другого происхождения. Проще свести оба под один хост через reverse proxy, чем городить CORS-заголовки на сервере. Настройка Nginx как обратного прокси на VPS с типовыми граблями разобрана в Nginx как reverse proxy — частые ошибки, а весь compose-стек продакшена — в Docker Compose для продакшена — частые ошибки.
Апдейты и совместимость версий
Отдельный источник трудно воспроизводимых ошибок — рассинхрон версий образов windmill_server и windmill_worker, когда обновили один сервис и забыли про другой (или один слетел на latest, а второй закреплён на конкретном теге). Протокол между сервером и воркерами меняется вместе со схемой БД, и разные версии рядом дают непредсказуемые сбои выполнения. Профилактика скучная: фиксируйте точный тег образа во всех сервисах compose, обновляйте разом и перед апдейтом снимайте дамп базы:
docker compose exec db pg_dump -U windmill windmill > windmill_backup_$(date +%F).sql
Откат при неудачном апдейте — это откат образа плюс восстановление дампа, а не попытка починить рассинхрон миграций руками.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Задача стоит в очереди со статусом Waiting и не выполняется — с чего начать?
Сверьте tag/WORKER_GROUP скрипта и живых воркеров (docker compose exec windmill_worker env | grep -i worker) — несовпадение тега самая частая причина. Если теги совпадают, смотрите docker compose logs windmill_worker на предмет ошибок подключения к базе или OOM.
Нужен ли License Key для self-hosted Windmill?
Базовая функциональность (скрипты, флоу, воркеры, UI) работает без лицензии. Ключ нужен только для части enterprise-возможностей вроде SSO/SAML — если он не прописан, а в логах видны ошибки про лицензию, проверьте .env на пример-заглушку из документации.
Можно ли развернуть Windmill без Docker?
Технически да, но тогда вы сами отвечаете за версии Postgres и рестарт сервера с воркерами при падении. Docker Compose — эталонный способ деплоя, под который тестируется каждый релиз.
После обновления флоу стали падать без явной причины — что проверить?
Версии образов windmill_server и windmill_worker — они должны совпадать. Рассинхрон версий после частичного обновления частая причина сбоев именно после апдейта.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →