Install & deploy
August 20, 2026 · View on GitHub
The Station is the self-hostable host: one container serves the REST
API (/v1), the MCP surface (/mcp) and the Studio web console (/).
The Studio is a static bundle built into the image there is no separate
frontend service to run. Remember what you are deploying: the console is a
window; the forests behind it are the product, and everything worth keeping
lives in volumes, so the container itself is disposable.
Docker (recommended)
From the repository root:
cp .env.example .env # optional but recommended fill in what you use
docker compose up --build -d
docker compose logs station # the first boot says how to get in
The volumes
A crash, a rebuild or an update never loses data, because everything that matters lives in named volumes:
| Volume | Mounted at | Holds |
|---|---|---|
forests | /forests | every forest: markdown, its embedded git history, dataset .db payloads, _derived/ caches |
registry | /registry | the host registry (station.db): principals, API-key digests, grants, audit one SQLite file |
models | /models | GGUF weights for the optional local inference sidecars multi-GB, kept out of the image |
Note a fourth volume,
documents, is mounted at/dataas the container's working directory and as a place to put files the Station may mirror. Swap it for a bind mount of your own folder (./documents:/data) and setMONKEYLLM_INGEST_ROOTS=/datato let the console read it the allow-list is empty by default, and empty means none. See Feeding the forest.
.env essentials
The compose file reads .env natively; anything left commented out falls
back to the code's own defaults. The annotated catalogue is
.env.example the short version:
# Chat provider (any OpenAI-compatible /v1 endpoint):
MONKEYLLM_LLM_ENDPOINT=https://openrouter.ai/api/v1
MONKEYLLM_LLM_API_KEY=sk-or-your-key-here
MONKEYLLM_LLM_MODEL=google/gemma-3-12b-it
# Where the Station answers. Loopback by default on a server, a reverse
# proxy reaches the container over the docker network, never the host port.
#STATION_PORT=8800
#STATION_BIND=127.0.0.1
# Hosts the MCP surface answers to; add your domain when serving through one.
#MONKEYLLM_STATION_ALLOWED_HOSTS=localhost,localhost:8800,127.0.0.1,127.0.0.1:8800
A provider configured this way arrives pre-configured and read-only in Studio → Models, marked "from the environment" the key is never copied into the registry, and you rotate it by editing the variable and restarting.
First boot: the setup screen
Starting a Station mints nothing (spec J.2.5): the registry is exactly as
authoritative after boot as before it, and the first-boot log names the
console URL http://localhost:8800. Open it, and a Station with nobody
in it shows the setup screen (spec J.2.4): pick a username and
password and you are the owner administrator of every forest,
present and future, including before the first one exists. The screen also
offers to start you with a seeded demo forest, an empty one, or nothing at
all.

Setup exists only while the registry holds no credential, and it closes permanently the moment it is used. From then on the console shows the ordinary sign-in.
Note a Station published on a public URL with an unclaimed owner seat is a race against strangers. Point your domain at it and complete setup yourself before announcing the address; the first-boot log says as much.
Browserless deploys: the bootstrap key
A headless server, CI, or an MCP-only client still needs a first door.
Start the Station once with --bootstrap-key (or set
MONKEYLLM_STATION_BOOTSTRAP_KEY=1 platform UIs with no argv field can
ask through the environment) and the boot mints one full-authority API
key, with the owner bit, and prints it in the log exactly once only its
digest is stored, so save it before the logs rotate.
The flag and the setup screen are two doors onto the same one-shot window: whichever you use spends it, and minting the key closes setup. A restart with the flag on a Station that already has a way in mints nothing.
Note you do not need
MONKEYLLM_STATION_ADMIN/MONKEYLLM_STATION_PASSWORD. Those two are a break-glass account held in the environment, compared at login and never stored; setting them replaces the setup screen rather than complementing it. When they are set, the first-boot log names the username to sign in as it never prints the password.
Local models
The compose file ships a local embedder behind a profile, and carries a chat sidecar as a commented template llama.cpp in both cases, running beside the Station.
docker compose --profile local-embed up -d # the embedder (bge-m3)
For local chat, open docker-compose.yml and uncomment the llm
service (profile local-llm) it ships commented because chat weights
are heavy and many deployments point MONKEYLLM_LLM_ENDPOINT at an
external OpenAI-compatible provider instead (for a llama.cpp setup on
your own GPU, see docs/local-inference.md).
The first start downloads the weights from Hugging Face into the models
volume (defaults: Qwen2.5-7B-Instruct Q4_K_M for chat, bge-m3 Q8_0 for
embeddings override with LLAMA_CHAT_HF / LLAMA_EMBED_HF); later
starts reuse them. Then point the Station at the sidecars by their compose
service names, in .env:
MONKEYLLM_LLM_ENDPOINT=http://llm:8090/v1
MONKEYLLM_EMBED_ENDPOINT=http://embed:8091/v1
The Station reads these at startup, so the sidecar is only half the job —
set the variable and restart the Station. On a compose platform with
no --profile flag (Dokploy, Coolify), set COMPOSE_PROFILES=local-embed
(plus local-llm if you uncommented the chat service) in the environment
instead; Compose reads it on its own.
Note the sidecars run on CPU by default, which is fine for the 7B Q4 navigator. Leave the embedder off to keep entry search (
locate) on its BM25-only contract the vector layer is optional by design.
From source
Install the engine and the Station, build the console once, and serve.
pip install -e . # the engine (add ".[dev]" for the test suite)
pip install -e apps/station # the host: REST, MCP, serves the Studio
cd apps/studio
npm ci && npm run build # the console, built once into apps/studio/dist
The Station serves the bundle from apps/studio/dist; if you skip the
build, it answers with that exact hint instead of a console.
station serve --root /forests --registry /registry/station.db --port 8800 --writable
--rootthe forest registry directory (default/forests, orMONKEYLLM_STATION_ROOT).--registrythe host registry SQLite file (default/registry/station.db, orMONKEYLLM_STATION_REGISTRY).--portdefault8800;--hostdefaults to127.0.0.1.--writableaccepts writes, ingest and forest creation. Without it the Station is read-only: reads keep working, writes are refused up front withE_READONLY.--bootstrap-keyand--no-warmwork here exactly as in Docker.
Note outside Docker, nothing loads
.envfor you (there is no python-dotenv). Load it into the shell yourself:set -a; source .env; set +a.
Environment reference
These are the variables the Integrations console documents (Studio →
Integrations → Environment reference); the full annotated catalogue lives
in .env.example.
| Variable | Meaning |
|---|---|
MONKEYLLM_STATION_ADMIN, MONKEYLLM_STATION_PASSWORD | Break-glass console login. Both must be set; never stored; rotate by restart. |
MONKEYLLM_STATION_ALLOWED_HOSTS | Hosts the MCP surface answers. Add your domain, or * to skip the check. |
STATION_PORT | Published port of the compose service 8800 by default. |
MONKEYLLM_LLM_ENDPOINT, MONKEYLLM_LLM_API_KEY, MONKEYLLM_LLM_PROVIDER | Chat provider, pre-configured and read-only under Models; the key is never copied into the registry. |
MONKEYLLM_LLM_MODEL, MONKEYLLM_LLM_MAX_TOKENS, MONKEYLLM_LLM_REASONING | Default model id, response budget and reasoning mode for that provider. |
MONKEYLLM_EMBED_ENDPOINT, MONKEYLLM_EMBED_MODEL, MONKEYLLM_EMBED_API_KEY | Embedding endpoint and model for the optional vector layer. Unset keeps entry search on its BM25-only contract. |
MONKEYLLM_S3_ENDPOINT | S3-compatible endpoint for remote payloads MinIO, R2. |
MONKEYLLM_STATION_PROVIDER_ALLOW_PRIVATE | Set to 1 to let Test connection reach loopback and private addresses. Needed when your model runs on the same machine or network (llama.cpp, Ollama); off by default, so a hosted-only deployment cannot be asked to open a connection into its own network. |
MONKEYLLM_STATION_IMPORT_MAX_MB | Largest snapshot the import route accepts, in MB. Defaults to 1024; 0 means no limit. |
Updating & read-only
Updates are a rebuild: docker compose up --build -d (or redeploy, on
a managed host). The image is replaced, the volumes are untouched, and
work continues where it was.
Snapshot before you upgrade. Each forest packages as a git bundle with its full history (spec Part I):
docker compose exec station vine snapshot create --forest /forests/<name>
Back up the registry volume (one SQLite file) together with the
snapshots grants and key digests belong with the forests they govern.
vine snapshot restore <bundle> brings a forest back.
Read-only Station. Remove --writable from the command: line and
redeploy: reads keep working, while writes, ingest and forest creation are
refused up front with E_READONLY. One deliberate exception: a read-only
Station still serves POST /v1/admin/reindex (spec J.13.3), because a
catalog rebuild writes only the disposable _derived/ layer an index the
Station could never repair would degrade forever.
Where to go next
The Station is up and the setup screen is waiting. First access walks through claiming the owner seat, the welcome tour, and your first forest and from there, Connecting AI is where the brain starts to grow.