Skip to content

Local development

Sencai is an event-driven microservices architecture managed with Docker Compose. This guide teaches you how to run it locally.

  • Docker ≥ 20.10
  • Docker Compose ≥ 2.0
  • Git
  • Node.js ≥ 18 (for local service development)
  • Minimum 8 GB RAM (recommended 12 GB)
  • Minimum 20 GB free space
Terminal window
git clone https://github.com/sencai-ai/sencai.space.git
cd code
  1. Copy the template:
Terminal window
cd orchestration
cp .env.example .env
  1. Fill critical variables:
Terminal window
JWT_SECRET=$(openssl rand -base64 32)
API_MANAGER_ENCRYPTION_KEY=$(openssl rand -base64 32)
APP_KEYS=$(openssl rand -hex 16),$(openssl rand -hex 16),$(openssl rand -hex 16),$(openssl rand -hex 16)
ADMIN_ENCRYPTION_KEY=$(openssl rand -hex 32)
  1. Edit .env and fill remaining values (cloud tokens, SMTP, etc.)

Basic command:

Terminal window
cd orchestration
./run-local.sh [profiles...]

run-local.sh with no arguments starts the default profile set (orchestration/run-local.sh DEFAULT_PROFILES):

core,traefik,backend,backend-db,auth,git,mq,webhook,monitoring,frontend,web,cloud,docs,admin,audit,agent,vault,billing,watchdog,glitchtip,posthog,opsloop,llm,backup,cell,bootstrap
ProfileServices
coreAPI Manager + ssh-proxy-service + workspace-connector (+ Traefik)
backend / backend-dbStrapi backend / its MySQL
frontendNuxt platform frontend (auth + gravity)
webPublic marketing web (sencai-web, nginx SSG)
adminAdmin dashboard (sencai-admin)
auditSencai Audit viewer (port 3300, audit.sencai.localhost)
authKeycloak + keycloak-db + auth-service-consumer
mqRabbitMQ (sencai-mq) + notification-service + event-store-consumer
webhookWebhook Publisher
cloudCloud Connector + worker + PostgreSQL
gitGitea Apps + MySQL + act-runner + Git Connector
docsDocumentation (Astro 5 + Starlight — this site)
agentagent-gateway (fleet C2 gateway, port 4400)
billingbilling-adapter (Lago/Stripe, port 3600)
watchdogsencai-watchdog (dunning microservice — payment-failure suspend/archive cron, port 3700)
llmllm-service (LLM facade, port 4430)
opsloopopsloop-consumer (intelligence loop, port 3005)
backupbackup-scheduler (MySQL → S3/GCS)
cellcell-metadata (region/cell routing)
vaultHashiCorp Vault
monitoringPrometheus, Grafana, Loki, Tempo, OTEL collector, exporters, Alertmanager, uptime-kuma
bootstrapOne-shot seed of Strapi data
forumSencai Forum (Lemmy backend + UI + Postgres + pict-rs + oauth2-proxy) — not in the default set, start explicitly
allEverything
Terminal window
# Backend + auth only
./run-local.sh core backend auth mq
# Everything
./run-local.sh all

Once running, access services through Traefik at their *.sencai.localhost hostnames — this is the primary access method for the whole stack (direct-port access is a debugging fallback, see below):

ServiceURLNotes
App / Frontendhttp://app.sencai.localhost/Nuxt platform (auth + gravity dashboard)
Backend APIhttp://app.sencai.localhost/apiStrapi
Strapi Adminhttp://app.sencai.localhost/adminCreate admin on first login
Keycloakhttp://app.sencai.localhost/kcsee note below — bare PathPrefix, no Host()
Cloud Consolehttp://app.sencai.localhost/cloudCloud Connector — bare PathPrefix, no Host()
Gitea Appshttp://app.sencai.localhost/gitea-appsbare PathPrefix, no Host()
Dozzle (container logs)http://app.sencai.localhost/dozzlebare PathPrefix, no Host()
Admin Dashboardhttp://admin.sencai.localhost/sencai-admin
Audit Viewerhttp://audit.sencai.localhost/port 3300
Marketing Webhttp://sencai.localhost/sencai-web
Docs (this site)http://docs.sencai.localhost/
GraphQL Gatewayhttp://graphql.sencai.localhost/
RabbitMQ UIhttp://mq.sencai.localhost/admin / admin
Grafanahttp://grafana.sencai.localhost/admin / admin
Prometheushttp://prometheus.sencai.localhost/
Alertmanagerhttp://alertmanager.sencai.localhost/
Status / Uptime Kumahttp://status.sencai.localhost/first run: create admin
Traefik Dashboardhttp://localhost:8080not behind Traefik itself
Lago Billinghttp://lago.sencai.localhost/profile: billing
Vaulthttp://vault.sencai.localhost/profile: vault
GlitchTiphttp://glitchtip.sencai.localhost/profile: glitchtip, first run: create admin
PostHoghttp://posthog.sencai.localhost/profile: posthog, first run: create admin
Agent Gateway (Fleet C2)http://agent-gateway.sencai.localhost/profile: agent
Opsloop Consumerhttp://opsloop.sencai.localhost/profile: opsloop
LLM Servicehttp://llm.sencai.localhost/profile: llm
Notification Servicehttp://notify.sencai.localhost/profile: mq
Workspace Connectorhttp://workspace-connector.sencai.localhost/profile: core
SSH Proxy (browser terminal)http://ssh-proxy.sencai.localhost/profile: core
Cell Metadatahttp://cell.sencai.localhost/profile: cell
Sencai Forumhttp://forum.sencai.space/ or http://forum.localhost/profile: forum
127.0.0.1 app.sencai.localhost admin.sencai.localhost audit.sencai.localhost sencai.localhost docs.sencai.localhost graphql.sencai.localhost mq.sencai.localhost grafana.sencai.localhost prometheus.sencai.localhost alertmanager.sencai.localhost status.sencai.localhost lago.sencai.localhost vault.sencai.localhost glitchtip.sencai.localhost posthog.sencai.localhost agent-gateway.sencai.localhost notify.sencai.localhost opsloop.sencai.localhost llm.sencai.localhost workspace-connector.sencai.localhost ssh-proxy.sencai.localhost cell.sencai.localhost forum.localhost

No-Host() routes: keycloak (/kc), cloud-connector (/cloud), gitea-apps (/gitea-apps) and dozzle (/dozzle) are registered in Traefik as bare PathPrefix(...) rules with no Host() restriction. They respond behind any hostname that reaches Traefik on the web entrypoint — not only app.sencai.localhost — so http://sencai.localhost/kc or http://docs.sencai.localhost/dozzle will also route to those services. Don’t rely on the hostname alone to isolate them.

Port 80 already taken: on machines where port 80 is occupied (e.g. a local k3s cluster’s ServiceLB/klipper-lb), set TRAEFIK_WEB_HOST_PORT in .env (e.g. 8088) and append :PORT to every URL above (http://app.sencai.localhost:8088/, http://grafana.sencai.localhost:8088/, …). The Traefik Dashboard on port 8080 is unaffected.

Every service that has a ports: mapping in docker-compose.ports.yml can also be reached directly on its container port, bypassing Traefik host-routing entirely — useful when isolating whether an issue is in the app or in the reverse-proxy layer:

Terminal window
http://localhost:1337/admin # Strapi admin, direct
http://localhost:15672 # RabbitMQ UI, direct

This is a fallback for debugging only — the *.sencai.localhost URLs above are the standard way to use the stack.

For faster development, run services outside Docker:

Terminal window
# Backend (Strapi)
cd sencai.space && npm run develop
# Frontend (Nuxt)
cd sencai.space-frontend && npm run dev
# API Manager
cd api-manager && npm run dev
Terminal window
mysql -h 127.0.0.1 -P 3306 -u root -p
Terminal window
psql -h localhost -p 5434 -U cloud_user -d cloud_connector
Terminal window
# All services
docker compose -f orchestration/docker-compose.yml logs -f
# Single service
docker compose -f orchestration/docker-compose.yml logs -f backend
Terminal window
# Stop stack
cd orchestration && ./run-local.sh stop
# Remove everything (including data!)
docker compose -f orchestration/docker-compose.yml down -v
IssueCauseSolution
”Port already in use”Port is occupiedChange port in .env
Strapi won’t startMySQL not readyWait 30s and restart
RabbitMQ connection refusedNot readyCheck logs
Keycloak 503Slow startup (normal)Wait 30–90s

See Root CLAUDE.md for complete reference.