MAATRIX / Блог / Windmill в Docker Compose: готовый файл

Windmill в Docker Compose: готовый файл

MAATRIX

Если вы устали писать скрипты на коленке и раскидывать их по cron на разных серверах, а n8n кажется слишком «no-code» для команды, где половина разработчиков, — Windmill закрывает эту нишу. Это open-source платформа, которая превращает Python/TypeScript/Bash/SQL-скрипты в внутренние инструменты, API-эндпоинты и workflow с ретраями, расписаниями и UI. Ниже — рабочий docker-compose.yml, с которым можно поднять её на своём сервере за один заход, без танцев с документацией.

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

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

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

Что такое Windmill и когда он нужен

Windmill — это связка из веб-редактора кода, движка выполнения задач (воркеров) и конструктора workflow. Разработчик пишет функцию на Python или TypeScript, Windmill сам генерирует форму параметров и превращает скрипт в готовый инструмент: можно вызвать по API, запустить по расписанию, встроить в flow с ветвлениями и повторами при ошибке.

Типичные сценарии, где он окупается:

  • внутренние админки и «кнопки» для нетехнических коллег (перезапустить джобу, выгрузить отчёт, обновить прайс);
  • ETL и синхронизация данных между сервисами без отдельного Airflow;
  • замена разрозненных cron-скриптов на серверах единым местом с логами, ретраями и правами доступа;
  • связка с AI-агентами и LLM-пайплайнами, где нужен контроль над кодом, а не только визуальные ноды.

По сравнению с n8n Windmill ближе к разработчикам: меньше визуального конструктора, больше настоящего кода с версионированием и тестами. Если вам, наоборот, нужен максимально визуальный no-code инструмент — почитайте про n8n на сервере, это разные философии для разных команд.

Для продакшен-нагрузки нужен отдельный VPS: минимум 2 vCPU / 4 ГБ RAM для связки сервер + база + пара воркеров, для реальной нагрузки с параллельными джобами — от 4 vCPU / 8 ГБ RAM. Каждый воркер Windmill — это, по сути, отдельный процесс, который может поднимать sandboxed-контейнеры для выполнения кода, так что диск и CPU расходуются быстрее, чем в статичном веб-сервисе.

Архитектура: из чего состоит стек

Официальный self-hosted стек Windmill — это не один контейнер, а несколько ролей одного и того же образа, которые общаются через общую базу Postgres:

КомпонентРольОбязателен
dbPostgres — хранит скрипты, flow, логи выполнения, очередь задачДа
windmill_serverВеб-UI, API, распределение задач по воркерамДа
windmill_workerВыполняет джобы (Python, Bash, SQL, TS)Да, минимум 1
windmill_worker_native«Лёгкие» воркеры для HTTP-запросов и native-задач без спавна контейнераОпционально, но заметно ускоряет типовые джобы
lspLanguage Server — автодополнение и подсветка ошибок в редакторе кодаОпционально, но без него редактор скучный
caddyReverse proxy с автоматическим HTTPS перед сервером и LSPОпционально, если HTTPS терминируется снаружи

Ключевая идея: сервер (windmill_server) сам ничего не выполняет — он раскладывает задачи в очередь в Postgres, а воркеры их разбирают. Это значит, что горизонтальное масштабирование сводится к увеличению числа реплик windmill_worker, без изменений в остальной части стека.

Postgres здесь не «просто база» — это ещё и очередь задач (через LISTEN/NOTIFY и таблицы очереди), поэтому диск под базу стоит выбирать с запасом IOPS, а не минимальный HDD-тариф.

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

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

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

Готовый docker-compose.yml

Ниже — рабочая конфигурация с одним сервером, тремя универсальными воркерами, одним native-воркером, LSP и Caddy для HTTPS. Сохраните как docker-compose.yml:

services:
  db:
    image: postgres:16-alpine
    restart: unless-stopped
    shm_size: 1g
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: windmill
    volumes:
      - db_data:/var/lib/postgresql/data
    command: >
      postgres
      -c shared_preload_libraries=pg_stat_statements
      -c max_connections=150
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 10

  windmill_server:
    image: ghcr.io/windmill-labs/windmill:main
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://postgres:${DB_PASSWORD}@db/windmill?sslmode=disable
      MODE: server
      BASE_URL: ${BASE_URL}
      RUST_LOG: info
    expose:
      - "8000"

  windmill_worker:
    image: ghcr.io/windmill-labs/windmill:main
    restart: unless-stopped
    deploy:
      replicas: 3
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://postgres:${DB_PASSWORD}@db/windmill?sslmode=disable
      MODE: worker
      WORKER_GROUP: default
      NUM_WORKERS: 1
      RUST_LOG: info
    volumes:
      - worker_dependency_cache:/tmp/windmill/cache
    # Раскомментируйте, только если джобам нужен доступ к docker на хосте —
    # это открывает воркеру root-доступ к хосту, оценивайте риски отдельно.
    # - /var/run/docker.sock:/var/run/docker.sock

  windmill_worker_native:
    image: ghcr.io/windmill-labs/windmill:main
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://postgres:${DB_PASSWORD}@db/windmill?sslmode=disable
      MODE: worker
      WORKER_GROUP: native
      WORKER_TAGS: native
      NUM_WORKERS: 3
      RUST_LOG: info

  lsp:
    image: ghcr.io/windmill-labs/windmill-lsp:latest
    restart: unless-stopped
    expose:
      - "3001"

  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
    depends_on:
      - windmill_server
      - lsp

volumes:
  db_data:
  worker_dependency_cache:
  caddy_data:

Файл Caddyfile рядом с compose:

{$BASE_URL} {
    reverse_proxy /ws/* lsp:3001
    reverse_proxy /* windmill_server:8000
}

Образы и переменные окружения Windmill развиваются — перед запуском в проде сверьтесь с актуальным docker-compose.yml в официальном репозитории проекта, если что-то из полей выше поменяли разработчики.

Настройка .env и первый запуск

Рядом с docker-compose.yml и Caddyfile создайте .env:

DB_PASSWORD=сгенерируйте_длинный_случайный_пароль
BASE_URL=windmill.your-domain.ru

Для генерации пароля подойдёт:

openssl rand -base64 32

Домен BASE_URL должен уже указывать A-записью на IP вашего сервера — Caddy сам получит сертификат Let's Encrypt при первом запросе. Дальше:

docker compose up -d
docker compose logs -f windmill_server

Дождитесь строки о готовности сервера (обычно занимает от 10 до 40 секунд — зависит от диска и от того, накатываются ли миграции базы впервые). Откройте https://windmill.your-domain.ru — Windmill предложит создать первый workspace и первого пользователя, который автоматически станет суперадмином инстанса.

Первое, что стоит сделать после входа — зайти в Instance Settings и:

  • отключить открытую регистрацию новых пользователей, если инстанс не для публичного доступа;
  • настроить SMTP, чтобы приглашения коллег и уведомления об упавших джобах приходили на почту;
  • создать отдельный workspace под продакшен-flow, а песочницу оставить для экспериментов — права в Windmill выдаются на уровне workspace.

HTTPS и reverse-proxy: Caddy или свой Traefik/nginx

Вариант с Caddy из compose выше подходит, если это единственный сервис на сервере. Если у вас уже крутится несколько приложений за общим reverse-proxy, логичнее не поднимать второй Caddy, а завести Windmill как ещё один сервис за уже существующим прокси.

Для Traefik добавьте лейблы вместо отдельного сервиса caddy и уберите секции ports у него:

  windmill_server:
    # ...остальное без изменений
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.windmill.rule=Host(`windmill.your-domain.ru`)"
      - "traefik.http.routers.windmill.tls.certresolver=letsencrypt"
      - "traefik.http.services.windmill.loadbalancer.server.port=8000"

Отдельно не забудьте прокинуть путь /ws/* на сервис lsp:3001 — без этого редактор кода будет работать, но без автодополнения и подсветки ошибок типов. Если раньше не разворачивали Traefik — есть отдельный разбор Traefik как reverse-proxy для докера и сравнение Traefik или nginx Proxy Manager, если выбираете между ними с нуля.

Второй момент — вебсокеты. И основной UI, и LSP держат постоянное WS-соединение с браузером для live-обновлений статуса джоб. Если между сервером и Windmill стоит ещё один прокси или CDN, убедитесь, что он не рвёт долгоживущие вебсокет-соединения по таймауту (для nginx это proxy_read_timeout в районе нескольких минут, для Caddy это работает из коробки).

Масштабирование воркеров, бэкап и обновление

Масштабирование в Windmill — это в первую очередь про воркеры, а не про сервер. Если очередь задач растёт, а джобы выполняются с задержкой, увеличьте replicas у windmill_worker:

docker compose up -d --scale windmill_worker=6

Группы воркеров (WORKER_GROUP) полезны, когда нужно физически разделить нагрузку: например, тяжёлые Python-джобы с ML-библиотеками пускать на одну группу с большим лимитом памяти, а лёгкие HTTP-вызовы — на native-группу, которая выполняется без спавна отдельного контейнера на каждую задачу и поэтому заметно быстрее реагирует на короткие джобы.

Для памяти на воркер полезно явно задать лимиты через compose, иначе одна тяжёлая джоба с утечкой может подвинуть соседние:

  windmill_worker:
    # ...
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 1g

Бэкап всего инстанса Windmill сводится к бэкапу Postgres — скрипты, flow, расписания, секреты (зашифрованные) и история выполнения хранятся там:

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

Вынесите эту команду в cron на хосте или, если хотите более надёжную схему с ретеншеном и шифрованием, разверните рядом отдельный контейнер бэкапа — подойдёт связка из статьи про бэкап и восстановление BorgBackup, нацеленная на директорию с дампами.

Обновление — это просто новый образ:

docker compose pull
docker compose up -d

Windmill сам прогоняет миграции базы при старте windmill_server. Перед обновлением на проде всё же сделайте свежий дамп — миграции необратимы, откатить версию образа назад после наката новой схемы базы не получится без восстановления из бэкапа.

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

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

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

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

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

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

Windmill бесплатный?

Ядро open-source (AGPL), self-hosted версия из этого docker-compose бесплатна без ограничений по числу пользователей. Часть enterprise-фич (например, multiplayer-редактирование, SSO определённых типов) доступна только в платной лицензии.

Можно ли обойтись без Caddy, если у меня уже есть nginx?

Да, просто проксируйте домен на порт 8000 контейнера windmill_server и отдельно — путь /ws/* на порт 3001 контейнера lsp, не забыв про поддержку вебсокетов в конфиге nginx.

Сколько нужно воркеров на старте?

Для команды до 10-15 человек и нечастых джоб хватает 2-3 универсальных воркера плюс один native-воркер из примера выше. Дальше смотрите на длину очереди в UI — если задачи «зависают» в статусе ожидания, добавляйте реплики.

Джобы на Python тянут внешние библиотеки — куда они ставятся?

В том же контейнере воркера, в volume worker_dependency_cache — так зависимости не переустанавливаются при каждом запуске одной и той же джобы, а кешируются между вызовами.

Нужен ли отдельный сервер под Windmill, или хватит того же VPS, где крутится сайт?

Технически можно и рядом, но воркеры при выполнении джоб — это дополнительные процессы и иногда контейнеры, которые едят CPU и диск рывками. Для стабильности продакшен-сайта лучше держать Windmill на отдельном сервере или хотя бы с явными лимитами памяти на воркерах, как показано выше.

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

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

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