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.
Bring it up
Section titled “Bring it up”Generate local certificates
Terminal window deploy/scripts/standalone-stack.sh certsIdempotent. 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.
Build the images
Terminal window deploy/scripts/standalone-stack.sh buildupreuses whatever image already carries the stack’s tag, so a source change only reaches a running stack after you runbuildfirst.Bring the stack up
Terminal window deploy/scripts/standalone-stack.sh upSchema bootstrap, the artifact bucket, RBAC roles, and the worker’s runtime authorization all happen automatically on a fresh
up— none of theseed*subcommands below are required to reach a working, logged-in stack.
Subcommands
Section titled “Subcommands”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-vyourself to also drop volumes.logs [service...]— tail the lastSTANDALONE_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 ifupskipped 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 nameseed-llmwrote.
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.
Required and useful environment variables
Section titled “Required and useful environment variables”| Variable | Default in this stack | What it does |
|---|---|---|
SECRETS_MASTER_KEY | unset | Optional 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_ADMINS | unset | Comma-separated oidc:<sub> / saml:<nameid> / email:<address> entries that become global administrators on first login — see Install Elitea. |
GATEWAY_IDENTITY_SECRET | standalone-dev-identity-secret | HMAC key elitea-main and the gateway share to sign/verify the project/user/tenant identity headers. |
GATEWAY_EGRESS_ALLOWLIST | llm-mock:8090,llm-real:8090,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 | host: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_ORIGINS | http://elitea-main:8080/llm/v1,http://localhost:8080/llm/v1 | The origins the gateway advertises to itself, for the self-referential-origin guard. |
GATEWAY_NATS_URL | unset | Optional. 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. |
| Port | Service | Override |
|---|---|---|
| 8084 | elitea-main, the browser edge (relays /app and /docs to elitea-web, redirects / to /app/) | STANDALONE_PORT |
| 8085 | LLM gateway, direct (debug) | STANDALONE_GATEWAY_PORT |
| 15433 | PostgreSQL | STANDALONE_PG_PORT |
| 8090 | llm-mock journal | STANDALONE_MOCK_PORT |
| 9400 | oidc-provider-mock | fixed — 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.
The mock LLM and mock MCP services
Section titled “The mock LLM and mock MCP services”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.
Point the gateway at a real model
Section titled “Point the gateway at a real model”LLM_PROVIDER=vllm LLM_API_BASE=http://192.168.1.10:8000 LLM_MODEL=Qwen/Qwen3-8B \ deploy/scripts/standalone-stack.sh seed-llmLLM_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.
Overlays
Section titled “Overlays”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.
First login
Section titled “First login”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).
Common failures
Section titled “Common failures”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.