Admin

Deployment

Production builds, Docker images, env vars.

Deployment

Dev containers

The repo's infra/docker-compose.yml ships both admin services ready to go:

shell
make admin-core  # base layer :3002
make admin       # project layer :3001 (extends base)

Both use infra/dockerfiles/Dockerfile.admin — minimal node:20-alpine with pnpm and corepack. First boot runs pnpm install; subsequent boots reuse the named volume for node_modules. Source is bind-mounted so edits hot-reload.

text
admin-core → binds ../js-admin-nuxt4 → :3002
admin      → binds ../your-admin + ../js-admin-nuxt4 (source only) → :3001

Both proxy /api/v1/** to admin-api:8001 via the Nuxt server's routeRules, so in-container requests stay on the Docker network.

Production builds

Use Nuxt's standard nuxt build:

shell
cd your-admin
pnpm build        # → .output/
node .output/server/index.mjs

Or containerise with a multi-stage Dockerfile:

dockerfile
FROM node:20-alpine AS build
WORKDIR /app
COPY js-admin-nuxt4/ /app/js-admin-nuxt4/
COPY your-admin/ /app/your-admin/
WORKDIR /app/your-admin
RUN corepack enable && pnpm install --frozen-lockfile
RUN pnpm build

FROM node:20-alpine
WORKDIR /app
COPY --from=build /app/your-admin/.output /app/.output
ENV NUXT_API_URL=https://admin-api.yourapp.com
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]

Keep the base layer source in the build context (under ../js-admin-nuxt4/) — Nuxt needs its source files at build time, not runtime.

Required env vars in production

KeyPurpose
NUXT_API_URLAdmin-api host (public — browser sees it)
NUXT_API_INTERNAL_URLAdmin-api host for SSR (server-to-server within your cluster)
NUXT_API_SECRETShared secret between admin-api and admin (if your backend enforces one)
NUXT_PUBLIC_AUTH_PREFIXOptional — where the backend mounts auth, relative to /api/v1 (default /auth). Set it to match the backend's AUTH_ROUTE_PREFIX when a project moved auth (e.g. /login for a stufio port). A mismatch 404s login.
NUXT_SESSION_PASSWORD32+ char random, for cookie signing
NUXT_PUBLIC_FLOWDSL_LICENSE_KEYOptional — unlocks the Flow Studio runner tier (also editable at runtime via flowstudio.license_key admin setting)
NUXT_PUBLIC_FLOWDSL_UI_PATHOptional — path the admin-api serves the Flow Studio at (default /flowdsl; must match backend FLOWSTUDIO_UI_PATH)

Admin-settings overrides

Several deployment-affecting values can now be edited at runtime from the admin UI without a redeploy. The backend re-reads them on the next request so changes take effect immediately.

GroupSettingEnv fallback
flowstudio.embedlicense_keyFLOWDSL_LICENSE_KEY
flowstudio.embedembed_urlFLOWSTUDIO_EMBED_URL
flowstudio.embedui_pathFLOWSTUDIO_UI_PATH
ai-guard.policydefault_on_error—
ai-guard.policyblock_flagged—
search.defaultsindex_prefixSEARCH_INDEX_PREFIX
search.defaultsembedding_providerSEARCH_EMBEDDING_PROVIDER
search.defaultsembedding_modelSEARCH_EMBEDDING_MODEL

Env values seed the initial configuration on a fresh deployment; admin edits override them thereafter and survive restarts (persisted in the _settings MongoDB collection). Set the admin setting to an empty string to revert to the env fallback.

LLM pricing — live overrides

The ledger price table ships with built-in rates for OpenAI, Anthropic, Gemini, and the full OVH catalog (chat, reasoning, code, vision, embeddings, plus free guard/ASR models). Admins can PUT/DELETE rows at /admin/llm/pricing/{provider}/{model} without a code change — the next LLM call picks up the new rate. Admin UI renders this under System → AI → Pricing.

Never set NUXT_STANDALONE=1 on a layered project admin — that reloads the base layer's CSS and creates duplicate styles.

Reverse proxy

Point your reverse proxy (nginx / Traefik / Caddy) at the admin Nuxt server. A minimal nginx block:

nginx
server {
  server_name admin.yourapp.com;
  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

The admin itself proxies /api/v1/** to your admin-api, so you don't need a separate proxy rule for API traffic.

Admin-api gating

The admin UI expects the admin-api binary (backend/cmd/admin-api), which bundles admin-only routes like /admin/users/*, /admin/flows, /admin/settings/*. The public backend/cmd/api omits these by construction — the split is enforced by a CI test (check-admin-leak) that greps the public binary's dependency tree for any admin path.

If your deployment co-locates both on one host, bind admin-api to a separate port (default 8001) and restrict it via your reverse proxy (IP allowlist, Tailscale, basic auth, etc.).

Observability

The admin surfaces system state at /system/status:

  • Per-worker heartbeat registry (roles + last-seen) from go-modules/workers/admin
  • Live health-check roll-up from /api/v1/health
  • Known infrastructure services (mongo, redis, nats, kafka) tagged healthy / unhealthy / not-monitored

Every backend instance publishes worker.heartbeat events on the EventBus; the admin aggregator reads them and renders the list. No additional plumbing needed — just make sure TRANSPORT is set (nats / kafka / redis / memory).