MAATRIX / Блог / Как установить и настроить Windmill на VPS

Как установить и настроить Windmill на VPS

MAATRIX

Скрипт на Python или TypeScript, который вы держите в личных заметках и запускаете руками раз в неделю, рано или поздно должен стать инструментом, которым пользуется вся команда — с формой ввода, расписанием и логом запусков. Windmill закрывает именно этот разрыв: превращает голый код в веб-приложение, workflow с шагами и API-эндпоинт без отдельного бэкенда. Ниже — рабочий способ поднять его на своём VPS с нуля, без облачного тарифа и без чужой инфраструктуры.

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

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

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

Что такое Windmill и зачем разворачивать его самому

Windmill — open-source платформа для превращения скриптов в внутренние инструменты, workflow и API. По сути это три вещи в одной системе:

  • Scripts — код на Python, TypeScript/JavaScript, Go, Bash, SQL и ещё десятке языков, который Windmill сам оборачивает в задачу с параметрами и запускает в изолированном воркере.
  • Flows — визуальный конструктор пайплайнов: шаги, условия, циклы, retry, параллельные ветки, вебхуки и расписания — примерно то, для чего люди берут n8n, но с кодом первого класса, а не только с блоками интеграций.
  • Apps — низкокодовый билдер внутренних UI поверх тех же скриптов: форма, кнопка, таблица с данными из вашего скрипта.

Разница с облачным Windmill Cloud принципиальна для тех, кто работает с российскими картами: self-hosted версия не требует привязки зарубежного способа оплаты к самому продукту — вы платите только за сервер, а сам Windmill open-source (AGPLv3) и бесплатен для self-hosting без ограничения числа пользователей на community-функциях. Второй момент — данные. Скрипты автоматизации часто трогают внутренние API, базы, ключи от платёжных систем; держать это на своей инфраструктуре, а не в чужом облаке — разумная гигиена безопасности.

Архитектура Windmill состоит из нескольких Docker-контейнеров: server (API и веб-интерфейс), один или несколько worker (выполняют сами задачи), lsp (языковой сервер для автодополнения в редакторе) и обязательная PostgreSQL — она же основное хранилище состояния, она же очередь задач. Отдельного Redis Windmill не требует, что упрощает стек по сравнению с похожими системами.

Требования к серверу

Windmill не тяжеловес, но и не игрушка на 512 МБ. Реалистичные ориентиры для старта:

СценарийCPURAMДиск
Тест / соло-разработчик, 1 воркер2 vCPU2-4 ГБ20 ГБ SSD
Команда 3-10 человек, 2-3 воркера4 vCPU8 ГБ40 ГБ SSD
Активные flows с параллелизмом, тяжёлые скрипты (pandas, ML)4-8 vCPU16 ГБ+60 ГБ+ SSD

Важный нюанс: каждый worker при первом запуске скрипта на новом языке или с новыми зависимостями кэширует окружение (venv для Python, node_modules для JS) — это съедает и диск, и CPU в момент "прогрева". Если у вас много разных скриптов на Python с разными наборами пакетов, закладывайте диск с запасом — кэш легко разрастается до нескольких гигабайт.

Для боевой установки понадобится домен (или поддомен), направленный A-записью на IP сервера — Windmill сам по себе не выдаёт HTTPS, это задача обратного прокси перед ним. Если домен ещё не настроен, сначала разберитесь с DNS — это отдельный шаг, не специфичный для Windmill.

Нужен сервер под эту задачу?

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

Арендовать сервер

Установка Docker и подготовка окружения

Windmill официально распространяется как набор образов для Docker Compose — это самый быстрый и предсказуемый путь на VPS. Если Docker ещё не стоит:

curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
docker --version
docker compose version

Дальше создаём рабочую директорию и скачиваем официальный docker-compose.yml из репозитория проекта:

mkdir -p /opt/windmill && cd /opt/windmill
curl -o docker-compose.yml https://raw.githubusercontent.com/windmill-labs/windmill/main/docker-compose.yml

Перед запуском обязательно откройте файл и посмотрите, что в нём — в официальном compose используются переменные окружения с дефолтными значениями (в том числе пароль от Postgres), их нужно переопределить, а не оставлять как есть.

Настройка docker-compose.yml и переменных окружения

Создайте файл .env рядом с compose-файлом — так секреты не попадут в сам YAML и не утекут случайно в git, если вы решите версионировать конфиг:

# .env
POSTGRES_PASSWORD=сгенерируйте_длинный_случайный_пароль
POSTGRES_DB=windmill
BASE_URL=https://windmill.example.com

Пароль удобно сгенерировать так, чтобы не думать над "надёжностью" вручную:

openssl rand -base64 32

В самом docker-compose.yml ключевые блоки, на которые стоит обратить внимание:

services:
  db:
    image: postgres:16
    restart: unless-stopped
    volumes:
      - db_data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}

  windmill_server:
    image: ghcr.io/windmill-labs/windmill:main
    restart: unless-stopped
    environment:
      DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db/${POSTGRES_DB}?sslmode=disable
      BASE_URL: ${BASE_URL}
    ports:
      - "127.0.0.1:8000:8000"
    depends_on:
      - db

  windmill_worker:
    image: ghcr.io/windmill-labs/windmill:main
    restart: unless-stopped
    environment:
      DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db/${POSTGRES_DB}?sslmode=disable
      WORKER_GROUP: default
      NUM_WORKERS: 2
    volumes:
      - worker_dependency_cache:/tmp/windmill/cache
    depends_on:
      - db
    deploy:
      replicas: 3

volumes:
  db_data:
  worker_dependency_cache:

Три момента, которые часто упускают из виду при первом деплое:

  1. Порт сервера должен смотреть только на localhost (127.0.0.1:8000:8000), а не на 0.0.0.0 — наружу Windmill должен отдавать только обратный прокси с HTTPS. Открытый напрямую 8000 порт — это панель управления скриптами без шифрования на видном месте в интернете.
  2. Число воркеров (replicas или отдельные сервисы windmill_worker) масштабируйте по нагрузке, а не "на всякий случай" — каждый воркер держит свой пул процессов и кэш зависимостей, лишние реплики просто едят RAM.
  3. worker_dependency_cache обязательно должен быть постоянным volume, а не эфемерным — иначе каждый рестарт контейнера будет заново собирать venv/node_modules для всех скриптов, и первый запуск после деплоя станет ощутимо медленнее.

Запуск:

docker compose up -d
docker compose logs -f windmill_server

Дождитесь в логах строки о том, что сервер слушает порт, и проверьте локально:

curl -I http://127.0.0.1:8000

Ответ 200 или 302 означает, что сервер поднялся и можно переходить к внешнему доступу.

Обратный прокси и HTTPS

Windmill не занимается TLS сам — эту работу отдают Caddy, Nginx или Traefik перед контейнером. Caddy для этой задачи удобнее всего: сертификат Let's Encrypt выпускается и продлевается автоматически, конфиг короче, чем эквивалентный Nginx.

Минимальный Caddyfile для Windmill:

windmill.example.com {
    reverse_proxy 127.0.0.1:8000
}

Если Caddy ещё не установлен — процесс с нуля описан в статье Caddy с авто-SSL на Ubuntu 24.04: пошаговая установка; там же разобраны частые причины, по которым сертификат не выдаётся (закрытый порт 80/443 в файрволе, неправильная A-запись).

После правки Caddyfile:

sudo systemctl reload caddy

Проверьте, что HTTPS отдаёт корректный сертификат:

curl -I https://windmill.example.com

Отдельно закройте прямой доступ к порту приложения в файрволе (UFW или аналог) — открытыми снаружи должны остаться только 80/443 (и 22 для SSH — желательно не на дефолтном порту).

Первый вход, workspace и права доступа

При первом открытии https://windmill.example.com Windmill предложит создать суперадминский аккаунт — email и пароль хранятся в его собственной базе, отдельно от системы. Сразу после входа:

  1. Смените дефолтный workspace demo на свой рабочий — Workspaces в Windmill это изолированные пространства со своими скриптами, секретами и правами, удобно разделять по проектам или окружениям (dev/prod).
  2. Настройте группы и роли (Settings → Users): по умолчанию новый пользователь получает права Developer, что означает доступ к редактированию и деплою скриптов. Для команды, где часть людей должна только запускать готовые flows через Apps, создайте роль с ограниченными правами — это делается через RBAC-настройки workspace.
  3. В разделе Resources заведите переменные окружения и секреты (API-ключи, строки подключения к внешним базам) централизованно, а не хардкодьте их в теле скриптов — Windmill шифрует значения ресурсов в базе и не показывает их в логах выполнения по умолчанию.

Если Windmill будет ходить в собственную PostgreSQL-базу проекта (не ту, что использует сам Windmill для служебных данных, а вашу рабочую), удобно завести для неё отдельный контейнер и хранить строку подключения как секрет ресурса, а не в коде скрипта — практики управления паролями в Docker разобраны в статье Docker secrets: управление паролями.

Бэкапы, обновления и мониторинг

Всё состояние Windmill — скрипты, flows, история запусков, секреты, пользователи — живёт в одной PostgreSQL-базе. Это упрощает бэкап до одной команды:

docker compose exec db pg_dump -U postgres windmill | gzip > windmill_backup_$(date +%F).sql.gz

Вынесите это в cron с ротацией и, что важнее, проверяйте раз в месяц, что бэкап реально восстанавливается на тестовом инстансе — файл, который никто не пробовал развернуть, бэкапом не является. Общие грабли с бэкапами Docker-томов (в том числе для volume с зависимостями воркеров) разобраны в статье Бэкап Docker volume на сервере: частые ошибки и решения.

Обновление сводится к смене тега образа и рестарту:

docker compose pull
docker compose up -d

Тег main в официальном compose тянет актуальную сборку из основной ветки — для продакшена разумнее закрепиться на конкретной версии (например, v1.4XX.X, смотрите актуальные релизы в GitHub проекта) и обновляться осознанно, а не молча получать breaking changes при каждом pull. Перед обновлением делайте бэкап базы — миграции схемы в Windmill автоматические, но откатить их вручную сложнее, чем восстановить дамп.

Для базового мониторинга состояния контейнеров достаточно:

docker compose ps
docker stats --no-stream

Если нагрузка на воркеры регулярно упирается в CPU или RAM — это сигнал добавить реплики windmill_worker (для CPU-bound задач) или вертикально увеличить сервер (для тяжёлых по памяти скриптов вроде обработки датасетов).

Нужен сервер под эту задачу?

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

Арендовать сервер

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

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

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

Чем Windmill принципиально отличается от n8n?

Оба умеют flows и вебхуки, но n8n делает акцент на визуальных нодах-интеграциях с готовыми коннекторами, а Windmill — на коде: скрипт на Python или TypeScript в Windmill это код первого класса с версионированием, а не обёртка вокруг ноды. Если у вас уже есть кодовая база на Python, миграция логики в Windmill обычно быстрее. Сравнение самого n8n с альтернативами есть в статье n8n против Make: что выгоднее и когда — общая логика выбора между low-code и code-first платформой там применима и здесь.

Нужен ли отдельный Redis для очереди задач?

Нет, Windmill использует саму PostgreSQL как очередь (через SKIP LOCKED), отдельный брокер сообщений не требуется — это одно из архитектурных упрощений по сравнению с похожими системами.

Можно ли запускать Windmill без Docker, напрямую бинарником?

Технически да, есть standalone-бинарник, но официально поддерживаемый и документированный путь — Docker Compose; при ручной установке вы сами отвечаете за то, чтобы версии server/worker/lsp совпадали и корректно резолвили зависимости.

Как ограничить, какие скрипты может видеть конкретный пользователь?

Через комбинацию Workspaces (полная изоляция) и путей скриптов с префиксами вроде u/username/ или f/folder/ — доступ к папкам настраивается в разделе Folders с ролями read/write/admin отдельно от общей роли пользователя в workspace.

Что будет с запущенными flows, если контейнер worker перезапустится посреди выполнения?

Задача останется в очереди со статусом "не завершена", и при следующем доступном воркере Windmill попытается её подхватить согласно настройкам retry в самом flow — но это поведение стоит явно проверить на некритичных задачах перед тем, как полагаться на него в проде, так как поведение retry настраивается по шагам, а не глобально.

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

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

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