Skip to content

Run the standalone stack

The standalone stack (deploy/docker-compose.standalone-full.yml) runs Elitea’s whole one-host topology — elitea-web, elitea-main, the LLM gateway, an agent worker, PostgreSQL, NATS (live updates and the runtime command bus), and a mock identity provider — driven by deploy/scripts/standalone-stack.sh. Install Elitea covers the same stack from an operator’s perspective; this page covers the local-development path: seeding fixtures, pointing the gateway at a real model, and the overlays that swap in the DeepWiki real engine or a staging database.

  1. Generate local certificates

    Terminal window
    deploy/scripts/standalone-stack.sh certs

    Idempotent. It mints two independent trust roots: the gateway CA for the elitea-main → LLM gateway egress hop, and the runtime CA that signs the worker’s authorized identity — plus the DeepWiki and Inventory provider server certificates off the gateway CA.

  2. Build the images

    Terminal window
    deploy/scripts/standalone-stack.sh build

    up reuses whatever image already carries the stack’s tag, so a source change only reaches a running stack after you run build first.

  3. Bring the stack up

    Terminal window
    deploy/scripts/standalone-stack.sh up

    Schema bootstrap, the artifact bucket, RBAC roles, and the worker’s runtime authorization all happen automatically on a fresh up — none of the seed* subcommands below are required to reach a working, logged-in stack.

certs, build, up, down, logs
  • certs — generate local mTLS and runtime-plane material (idempotent).
  • build [service...] — rebuild images from source.
  • up — bring the stack up and wait for it to settle; prints the URLs below when it’s ready.
  • down [-v] — tear down; add -v yourself to also drop volumes.
  • logs [service...] — tail the last STANDALONE_LOG_TAIL (default 400) lines per service.
seed, seed-runtime, seed-llm, seed-index (all optional dev/E2E convenience)

None of these are required for a working stack — an operator normally configures a provider from the admin UI after logging in instead.

  • seed — delegates to the E2E seeder (apps/elitea-web/scripts/e2e-stack.sh seed) for schema bootstrap, oidc-mock users, and RBAC fixtures, then applies tenant migrations for the newly seeded projects and restarts elitea-main so it backfills their per-project secret header.
  • seed-runtime — manual fallback to authorize the worker’s identity if up skipped it.
  • seed-llm — writes an LLM credential and models via the product API; needs Configurations plane enabled. See Point the gateway at a real model below.
  • seed-index — provisions an embedding model and an indexable toolkit. It only writes the embedding model row when none exists yet, so it never overwrites the name seed-llm wrote.
check

Verifies the gateway’s mTLS hop, the runtime plane, the chat critical path (via chat-smoke.py), the embedding path, and a real SDK client round trip. It counts passes, failures, and skips — a skipped assertion fails the run unless you pass --allow-skips — and it also checks that the number of assertions it made matches the number it is written to make. There is no separate status subcommand: check and podman compose -p elitea-standalone -f deploy/docker-compose.standalone-full.yml ps cover that.

VariableDefault in this stackWhat it does
SECRETS_MASTER_KEYunsetOptional here: seeded credentials carry a literal api_key rather than a vault reference. See the three states below if you set one.
ELITEA_INITIAL_GLOBAL_ADMINSunsetComma-separated oidc:<sub> / saml:<nameid> / email:<address> entries that become global administrators on first login — see Install Elitea.
GATEWAY_IDENTITY_SECRETstandalone-dev-identity-secretHMAC key elitea-main and the gateway share to sign/verify the project/user/tenant identity headers.
GATEWAY_EGRESS_ALLOWLISTllm-mock:8090,llm-real:8090,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16host:port allowlist the gateway checks a saved credential’s base URL against. Add your own model host before saving a credential that points there.
GATEWAY_SELF_LLM_ORIGINShttp://elitea-main:8080/llm/v1,http://localhost:8080/llm/v1The origins the gateway advertises to itself, for the self-referential-origin guard.
GATEWAY_NATS_URLunsetOptional. The stack’s NATS serves only elitea-main’s live updates (ELITEA_EVENTS_NATS_URL: the project SSE stream and canvas presence); the gateway is not pointed at it, so budget enforcement stays disabled with a startup warning — set GATEWAY_NATS_URL=nats://nats:4222 (and LLM_BUDGET_REQUIRE_ENFORCEMENT if you need the cluster’s stricter posture) to enforce budgets locally.
PortServiceOverride
8084elitea-main, the browser edge (relays /app and /docs to elitea-web, redirects / to /app/)STANDALONE_PORT
8085LLM gateway, direct (debug)STANDALONE_GATEWAY_PORT
15433PostgreSQLSTANDALONE_PG_PORT
8090llm-mock journalSTANDALONE_MOCK_PORT
9400oidc-provider-mockfixed — cannot be remapped

The runtime plane (elitea-main’s mTLS listeners) is not published to the host at all — only the worker, on the compose network, needs it.

llm-mock is a journaled OpenAI-compatible server on the compose network. mcp-mock is a mock MCP server for toolkit journeys; mcp-mock-trust writes the trust bundle the Rust worker uses to verify it.

Terminal window
LLM_PROVIDER=vllm LLM_API_BASE=http://192.168.1.10:8000 LLM_MODEL=Qwen/Qwen3-8B \
deploy/scripts/standalone-stack.sh seed-llm

LLM_API_BASE must not end in /v1 — the gateway appends /v1/chat/completions itself, and a trailing /v1 produces a 404 that looks like a routing bug. The credential is seeded as provider type vllm regardless of what server you’re pointing at (Ollama, LM Studio, TGI, …): the gateway’s egress dialer only lifts its private-address guard for that provider class. Set LLM_API_KEY if your server checks one; most self-hosted servers ignore it, but the variable cannot be empty. Set LLM_EMBEDDING_MODEL too if the server also serves embeddings — a self-hosted chat server usually does not, and seed-llm seeds no embedding row unless you name one.

Set STANDALONE_OVERLAY to a space-separated list of compose files after the base stack, or use the shortcuts below.

The agent worker is always the native Rust runtime: standalone-stack.sh layers docker-compose.standalone-rust-agent.yml on every run, so there is no worker to select.

DeepWiki real engine

STANDALONE_OVERLAY=deploy/docker-compose.deepwiki-native-real-engine.yml DEEPWIKI_ENGINE_IMAGE=<tag> deploy/scripts/standalone-stack.sh up switches the DeepWiki native engine from its fixture runner to the real analysis engine, cloning a public repository from github.com, with a deterministic LLM stub at the gateway’s allowlisted mock address.

Staging

docker-compose.staging.yml (used with the plain deploy/docker-compose.yml, not this stack) points elitea-main at a restored staging database dump and requires SECRETS_MASTER_KEY from your shell — it decrypts vault data the dump already carries.

Sign in through the browser at http://localhost:${STANDALONE_PORT:-8084}/app/. oidc-mock accepts any username as its “Subject” field — there is no real identity check. Whether that account becomes a global administrator depends on ELITEA_INITIAL_GLOBAL_ADMINS, or on the fixtures seed wrote if you ran it (its member and admin personas already hold the roles the journeys need).

ERROR: port 9400 (oidc-mock) is already in use

oidc-mock’s published port must equal its container port — the mock derives its issuer from the request’s Host header — so it cannot be remapped, and this stack cannot run at the same time as the E2E stack (Run the browser test stack). Stop the other stack first: apps/elitea-web/scripts/e2e-stack.sh down.

ERROR: mTLS material missing. Run: standalone-stack.sh certs

up checks for the gateway client certificate, the runtime CA, and the DeepWiki/Inventory provider certificates before starting anything, so a missing one fails fast with this message rather than leaving elitea-main in a restart loop.

SECRETS_MASTER_KEY states

The variable has three states, and they are not equivalent: set and valid — elitea-main wraps each project’s vault key with it, stored as a Fernet token; not set — elitea-main starts and logs a warning, storing the project key in the clear (anyone who can read the database can open every project secret); set but malformed — elitea-main refuses to start, naming the variable in its error. A trailing newline is not malformed (both Go and Python ignore it on decode); a stray space or tab is. This stack does not require the variable at all, unlike deploy/docker-compose.yml and the staging overlay, which fail to start without it.