Self-Host Quickstart
Запуск Multica на своём сервере или машине через Docker (или Helm в Kubernetes). Около 10 минут.
На этой странице — развёртывание сервера Multica (backend + frontend + PostgreSQL) на своей машине или сервере через Docker. Когда всё готово, данные полностью под вашим контролем — workspace, issue, комментарии и конфигурация agent.
Выполнение task agent по-прежнему идёт через локальный daemon и AI coding tools на вашей машине — как в Cloud. Self-host меняет слой сервера, а не слой выполнения.
Требования
- Docker с возможностью запускать
docker compose - Git (опционально, но удобно для клонирования исходников)
- Машина, которая может работать постоянно (локальная / внутренняя сеть / облачный хост — всё подходит)
- Хотя бы один AI coding tool на машине с daemon (не обязательно на той же, где сервер — подойдёт dev-ноутбук)
1. Клонируйте проект и запустите backend
Уже на Kubernetes? Пропустите Docker и используйте Helm chart — перейдите к Kubernetes deployment ниже, затем вернитесь к шагу 4 для первого входа.
git clone https://github.com/multica-ai/multica.git
cd multica
make selfhostmake selfhost:
- Сгенерирует
.envиз.env.example, если файла нет — с случайными JWT_SECRET и паролем Postgres - Скачает официальные Docker-образы (PostgreSQL, Multica backend, Multica frontend)
- Поднимет все сервисы через
docker-compose.selfhost.yml - Дождётся готовности endpoint
/healthbackend
Для production-проверок после старта используйте /readyz, если нужно, чтобы проверка падала при проблемах с БД или миграциями.
Контейнер backend автоматически выполняет миграции БД при старте (docker/entrypoint.sh запускает ./migrate up до старта сервера) — вывод миграций будет в логах backend. Обновление версий устроено так же.
Образ ещё не опубликован? Если make selfhost не может скачать образы, возможно, вы на не выпущенном теге. Переключитесь на стабильный release или соберите из исходников: make selfhost-build.
После запуска:
- Frontend: http://localhost:3000
- Backend: http://localhost:8080
Порты слушают только 127.0.0.1. docker-compose.selfhost.yml привязывает все опубликованные порты к loopback — ss -tlnp не покажет 0.0.0.0:8080, и с других машин сервисы недоступны по задумке. Секреты и учётные данные Postgres не должны висеть в открытом интернете. Для доступа с других машин поставьте reverse proxy с TLS — см. шаг 5b — Cross-machine: front with a reverse proxy.
2. Важно: оставьте production safety включённой
docker-compose.selfhost.yml по умолчанию ставит APP_ENV=production и оставляет MULTICA_DEV_VERIFICATION_CODE пустым — на публичных инстансах нет фиксированного кода.
MULTICA_DEV_VERIFICATION_CODE задавайте только для локальной или приватной test automation. Если фиксированный код включён при APP_ENV не production, любой, кто может запросить код, войдёт с этим значением. См. Auth setup → Fixed local testing codes.
Перед публичным развёртыванием убедитесь, что в .env стоит APP_ENV=production, а MULTICA_DEV_VERIFICATION_CODE пуст.
3. Настройте email-сервис (опционально, но рекомендуется)
Без email пользователи не получат коды подтверждения по почте; сервер печатает сгенерированные коды в stdout.
Поддерживаются два backend доставки — выберите подходящий:
Вариант A — Resend (облако / публичный интернет):
-
Зарегистрируйтесь на Resend и получите API key
-
Подтвердите домен отправки, которым владеете
-
Укажите в
.env:RESEND_API_KEY=re_xxxxxxxxxxxx RESEND_FROM_EMAIL=noreply@yourdomain.com
Вариант B — SMTP relay (внутренние сети / on-premise):
Используйте, когда развёртывание не достигает api.resend.com или уже есть внутренний mail relay (Microsoft Exchange, Postfix, on-prem SendGrid и т. п.). SMTP_HOST имеет приоритет над Resend, если заданы оба — verification и invite mail идут через внутренний relay. STARTTLS поднимается автоматически, если объявлен; порт 465 (SMTPS / implicit TLS) автоматически включает немедленный TLS handshake, а SMTP_TLS=implicit (алиасы: smtps, ssl) принудительно включает его на нестандартном SMTPS-порту.
Для анонимного Exchange internal relay (порт 25) — хост доверен по IP, отправка без учётных данных:
SMTP_HOST=exchange.internal.example.com
SMTP_PORT=25
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_TLS_INSECURE=false
RESEND_FROM_EMAIL=noreply@yourdomain.com # reused as the From: headerДля authenticated submission (порт 587, STARTTLS) — relay требует service account; STARTTLS поднимается автоматически:
SMTP_HOST=smtp.internal.example.com
SMTP_PORT=587
SMTP_USERNAME=multica
SMTP_PASSWORD=...
SMTP_TLS_INSECURE=false # set true only for private CA / self-signed
RESEND_FROM_EMAIL=noreply@yourdomain.comДля implicit TLS / SMTPS (порт 465) — провайдеры вроде Aliyun / Tencent enterprise mail без STARTTLS. Порт 465 автоматически включает implicit TLS, поэтому SMTP_TLS здесь опционален:
SMTP_HOST=smtp.qiye.aliyun.com
SMTP_PORT=465
SMTP_USERNAME=multica@yourdomain.com
SMTP_PASSWORD=...
SMTP_TLS=implicit # optional on 465; required on a non-standard SMTPS port
RESEND_FROM_EMAIL=noreply@yourdomain.comДля строгих публичных relay (например Google Workspace smtp-relay.gmail.com), отклоняющих greeting localhost с публичного IP, задайте SMTP_EHLO_NAME на FQDN, который ожидает relay — иначе соединение обрывается и на следующей команде проявляется непрозрачный EOF. По умолчанию — hostname контейнера, обычно не валидный FQDN:
SMTP_HOST=smtp-relay.gmail.com
SMTP_PORT=587
SMTP_EHLO_NAME=mail.yourdomain.com # FQDN the relay accepts; defaults to the (non-FQDN) container hostname
RESEND_FROM_EMAIL=noreply@yourdomain.comЗатем перезапустите: docker compose -f docker-compose.selfhost.yml restart backend. При рестарте backend печатает выбранный provider и согласованный TLS mode (EmailService: SMTP relay <host>:<port> (starttls|implicit-tls) from=… / Resend API / DEV mode) — учётные данные в лог не попадают, эту строку можно показывать при обращении за помощью.
Подробнее об auth (OAuth, signup allowlist) и полный справочник SMTP — в Auth setup и Environment variables → Email.
4. Первый вход и создание workspace
Откройте http://localhost:3000:
- Введите email
- Возьмите код подтверждения из настроенного email backend (Resend или SMTP relay); если ни один не настроен — скопируйте из stdout контейнера сервера, строка
[DEV] Verification code - Не используйте
888888, если явно не задалиMULTICA_DEV_VERIFICATION_CODE=888888на не-production приватном инстансе - Войдите и создайте первый workspace
5. Направьте CLI на свой сервер
Установка CLI — как в Cloud quickstart → 2. Install the CLI — Homebrew / script / PowerShell на выбор.
5a. Та же машина
Если CLI и сервер на одном хосте, defaults уже подходят:
multica setup self-hostКоманда направит CLI на http://localhost:8080 (backend) и http://localhost:3000 (frontend), проведёт через вход в браузере, сохранит PAT локально и автоматически запустит daemon.
5b. Cross-machine: front with a reverse proxy
Поскольку compose stack слушает только 127.0.0.1, daemon на другой машине не достучится до http://<server-ip>:8080 напрямую — и так и задумано: иначе секреты сервера были бы доступны из открытого интернета. Поставьте reverse proxy на сервере с TLS, проксирующий на 127.0.0.1:8080 (backend) и 127.0.0.1:3000 (frontend), затем укажите CLI публичный HTTPS URL:
multica setup self-host \
--server-url https://<your-domain> \
--app-url https://<your-domain>Предпочитаете переменные окружения флагам? setup self-host читает MULTICA_SERVER_URL и MULTICA_APP_URL, если соответствующий флаг не передан — флаг всё равно важнее env. MULTICA_SERVER_URL также принимает форму daemon ws://…/ws из Environment variables и нормализует её к HTTP base.
Минимальный Caddyfile для frontend и backend (с WebSocket — нужен и daemon, и веб-приложению) на одном hostname:
multica.example.com {
# WebSocket route — must come before the catch-all
@ws path /ws /ws/*
handle @ws {
reverse_proxy 127.0.0.1:8080 {
flush_interval -1
}
}
# Backend API
handle /api/* {
reverse_proxy 127.0.0.1:8080
}
# Everything else → frontend
reverse_proxy 127.0.0.1:3000
}После поднятия proxy задайте FRONTEND_ORIGIN=https://multica.example.com в .env сервера и перезапустите backend — иначе WebSocket origin check отклонит браузер (Troubleshooting → WebSocket can't connect).
Cloudflare Tunnel — ещё один надёжный вариант: TLS и публичный hostname без открытия портов на хосте. Эквивалент на Nginx (отдельные hostname app. / api., proxy_set_header Upgrade для WebSocket) работает так же; ключевое — TLS termination и проброс заголовка Upgrade на /ws.
6. Создайте agent и назначьте первый task
Тот же flow, что в Cloud — см. Cloud quickstart → Steps 5-6.
7. Usage rollup (действий оператора не требуется)
Dashboard Usage / Runtime читают производную таблицу task_usage_hourly, которую заполняет rollup_task_usage_hourly(). Начиная с MUL-2957 backend выполняет этот rollup in-process через DB-backed scheduler — pg_cron больше не нужен, внешний cron / systemd timers больше не рекомендуются. Образ pgvector/pgvector:pg17 из коробки работает без изменений.
In-process scheduler тикает каждые 30 секунд и забирает 5-минутный UTC plan через таблицу sys_cron_executions. Несколько реплик backend безопасны — уникальный ключ (job_name, scope_kind, scope_id, plan_time) гарантирует одного победителя на plan. Для новых развёртываний настройка не нужна.
Совместимость — существующие регистрации pg_cron. Если rollup раньше был зарегистрирован как job pg_cron (SELECT cron.schedule('rollup_task_usage_hourly', '*/5 * * * *', …)), удалять не обязательно — SQL-функция держит advisory lock 4246 внутри, app scheduler и pg_cron не могут писать дважды. Чтобы убрать лишнюю запись:
SELECT cron.unschedule('rollup_task_usage_hourly')
FROM cron.job WHERE jobname = 'rollup_task_usage_hourly';Обновление с v0.3.4 → v0.3.5+. В предыдущем release операторам нужно было вручную запускать cmd/backfill_task_usage_hourly до migration 103, иначе fail-closed guard прерывал migrate up. С MUL-2957 это автоматически: команда migrate выполняет идемпотентный monthly-slice backfill (под advisory lock 4246) сразу перед migration 103, затем продолжает. Standalone backfill на нагруженной БД всё ещё можно запустить с --sleep-between-slices=2s для снижения read pressure, но это больше не обязательно.
Полный справочник — включая operations notes и форму Kubernetes deployment — в SELF_HOSTING_ADVANCED.md → Usage Dashboard Rollup репозитория.
Kubernetes deployment (alternative)
Если у вас уже есть Kubernetes cluster, в репозитории есть Helm chart в deploy/helm/multica/. Это аналог make selfhost для k8s — те же образы backend, frontend и Postgres pgvector/pgvector:pg17, упакованные как Deployments / Services / Ingresses с одним ConfigMap из values.yaml. Разрабатывался под k3s + Traefik + local-path и должен работать на любом cluster с Ingress controller и default ReadWriteOnce StorageClass.
Chart не шаблонизирует secret values. Он ссылается на Secret multica-secrets по имени — реальные JWT / DB / Resend / Google keys не нужно хранить в git или values.yaml. Создайте namespace + Secret один раз через kubectl:
kubectl create namespace multica
kubectl -n multica create secret generic multica-secrets \
--from-literal=JWT_SECRET="$(openssl rand -hex 32)" \
--from-literal=POSTGRES_PASSWORD="$(openssl rand -hex 16)" \
--from-literal=RESEND_API_KEY="" \
--from-literal=GOOGLE_CLIENT_SECRET="" \
--from-literal=CLOUDFRONT_PRIVATE_KEY="" \
--from-literal=MULTICA_DEV_VERIFICATION_CODE=""Затем установите chart:
git clone https://github.com/multica-ai/multica.git
cd multica
helm install multica deploy/helm/multica -n multicaDefaults предполагают hostname multica.dev.lan (web) и api.multica.dev.lan (backend). Добавьте их в /etc/hosts (или local DNS), указывая на любой node IP, где доступен Ingress. Для других hostname скопируйте deploy/helm/multica/values.yaml, отредактируйте ingress.frontend.host / ingress.backend.host и соответствующие backend.config.appUrl / frontendOrigin / localUploadBaseUrl / googleRedirectUri, затем установите с -f my-values.yaml.
На холодном cluster backend может быть Running, но не Ready несколько минут, пока ждёт Postgres и выполняет миграции — startupProbe это переживает, pod не должен перезапускаться. Когда Ready:
curl -H "Host: api.multica.dev.lan" http://<ingress-ip>/healthz
# {"status":"ok","checks":{"db":"ok","migrations":"ok"}}Откройте http://multica.dev.lan и продолжите с шага 4 — First login выше. Направьте CLI на Ingress hostname:
multica setup self-host \
--server-url http://api.multica.dev.lan \
--app-url http://multica.dev.lanЧтобы подтянуть свежие образы без смены chart: kubectl -n multica rollout restart deploy/multica-backend deploy/multica-frontend. Чтобы зафиксировать release Multica, задайте images.backend.tag / images.frontend.tag в values и выполните helm upgrade. helm -n multica uninstall multica удаляет workloads, но оставляет PVC и Secret; kubectl delete namespace multica стирает всё.
Полный справочник — три режима входа, workaround backend ExternalName для build-time REMOTE_API_URL в web-образе, resource limits и TLS — в SELF_HOSTING.md репозитория.
Частые проблемы
- Backend не стартует: смотрите логи контейнера
docker compose -f docker-compose.selfhost.yml logs backend; обычно виноваты неверныеDATABASE_URLилиJWT_SECRETв.env - Код подтверждения не приходит: email backend не настроен (ни Resend, ни SMTP) → ищите
[DEV] Verification codeвdocker compose logs backend - WebSocket не подключается: для публичных развёртываний задайте
FRONTEND_ORIGINна реальный frontend domain; см. Troubleshooting → WebSocket won't connect - Usage / Runtime dashboard на нуле:
rollup_task_usage_hourly()не планируется — см. шаг 7 и Troubleshooting → Usage dashboard shows zero migrate upпадает сrefusing to drop legacy daily rollups: guard пути обновленияv0.3.4 → v0.3.5+. С MUL-2957 migrate автоматически выполняет backfill перед migration 103 — см. шаг 7
Дальше
- Environment variables — полный справочник env
- Auth setup — Resend / OAuth / signup allowlist подробно
- GitHub integration — подключите GitHub App, чтобы PR автоматически связывались с issue, а merge закрывал их
- Troubleshooting — начните здесь, если что-то пошло не так
- Desktop app — опциональная настройка Desktop через
~/.multica/desktop.json; веб-frontend + CLI остаётся самым быстрым путём self-host