DocumentationChangelog
Getting Started

Install

Control runs as a published container image on a host you own. Fetch the compose file and the environment template, fill in four values, put your own proxy in front, and create the admin account on first boot.

What you need

An x86-64 Linux host with Docker Engine and Compose v2 — a cloud VM, a managed container platform, or your own hardware. 2 vCPU and 4 GB RAM is plenty to start. The image is linux/amd64; there is no arm64 build published yet.
Durable storage. The database, the App Logs history and dev-session git checkouts all live on disk, so a platform with ephemeral filesystems needs volumes attached.
A way in that terminates TLS — a reverse proxy on the host (Caddy, nginx), a Cloudflare Tunnel, or your platform's own router.
Your Control licence key, emailed minutes after purchase. Not required to install — activation never blocks the install, and a missing or expired key degrades to the free tier rather than locking you out of your own data.
Not serverless
Control keeps a process alive between requests — a one-minute ticker fires the morning plan, the evening recap, mail intake and stale-run reaping. On a platform that freezes the process between requests, none of that fires. Anything that runs a long-lived container is fine; a function runtime is not.

Fetch the files

The compose file and the environment template are published on this site. Fetch both into an empty directory rather than writing your own — the compose file carries the digest pins, the health checks, the loopback bind and the required-variable guards that the notes below assume.

terminal
mkdir -p ~/control && cd ~/control
curl -fsSLO https://www.talonlabs.dev/control/docker-compose.yml
curl -fsSL https://www.talonlabs.dev/control/control.env.example -o .env

One optional third file: the search profile bind-mounts a SearXNG settings file, so if you plan to turn that on, fetch it now — curl -fsSL --create-dirs -o deploy/searxng/settings.yml https://www.talonlabs.dev/control/deploy/searxng/settings.yml. Without the profile nothing reads it.

The compose file and the template are also downloadable from the download page. Running Kubernetes instead? A Helm chart ships alongside the appliance — ask us and we will set you up.

Fill in the .env

Four values are required, and compose refuses to start without them rather than defaulting to something weak. Everything below the template's first divider is optional and documented in place — Configuration is the same list with the reasoning attached.

.env
# three secrets, one command each: openssl rand -hex 32
POSTGRES_PASSWORD=…
AUTH_SECRET=…
TALON_CONTROL_ENCRYPTION_KEY=…
# the public origin YOUR proxy answers on — not ours
NEXTAUTH_URL=https://control.your-domain.com
Escrow the encryption key before first boot
TALON_CONTROL_ENCRYPTION_KEY encrypts every stored credential — AI provider keys, mail passwords, the GitHub App key, licence material — before it reaches the database. Lose it and a restored backup does not help you: what is in the backup is ciphertext. Put it in your password manager now, not later.

The template ships with a known-good TALON_CONTROL_IMAGE_TAG already pinned. Leave it pinned. A tag that moves makes "what is actually running" unanswerable and turns rollback into guesswork — see Upgrades & rollback.

Bring it up

terminal
docker compose up -d
docker compose ps # app and db healthy
curl -s localhost:3001/api/health/ready # {"status":"ready"}

Migrations run themselves: the container applies anything pending before the server starts, on every boot. A 503 from /api/health/ready means the app is alive but cannot reach Postgres — check the database container before anything else.

Put a proxy in front

The app binds to loopback on purpose. Nothing on the box should be reachable from the internet directly, so a proxy on the host is the only way in — a Cloudflare Tunnel that dials out (no inbound firewall rule at all), Caddy with automatic certificates, or whatever your platform routes with.

Caddyfile
control.example.com {
reverse_proxy 127.0.0.1:3001
}
Set TALON_TRUST_PROXY behind a proxy — and only behind one
Behind a proxy every request arrives from 127.0.0.1, so the app cannot tell callers apart by address. Without TALON_TRUST_PROXY=true it falls back to throttling on the account identity a caller supplies, and only to a single shared bucket when there is no identity either — so unauthenticated endpoints lose per-client limiting. Traffic analytics is the harder loss: with no trusted IP it can never derive a visitor hash, so unique visitors and geo read zero permanently rather than approximately. If you ever expose the app directly instead, turn this off: the forwarded header is then attacker-controlled, and trusting it would let one client mint unlimited buckets.

First boot

1
Open your origin. A first boot against an empty database redirects to /setup to create the admin account — or set TALON_CONTROL_ADMIN_EMAIL and TALON_CONTROL_ADMIN_PASSWORD to bootstrap it from the environment instead.
2
Paste your licence key under Settings → License. It verifies locally, with no network call — see Licensing.
3
Save an AI provider under Settings → AI. The console works without one; the agent fleet does not run at all until it has a model to call.
4
Connect what you use under Connections — mail, GitHub, Slack, Stripe. All optional, and each one is what turns a section from a register into something that acts.
5
Take a backup and restore it before you trust the box with anything. Backups is the shortest page here and the one that matters most.

A fresh install is empty

Control starts with no data about your company — no customers, no contacts, no notes, no ledger entries. What is seeded is only the machinery that describes how Control itself works: the built-in automations, the UK chart of accounts, and the agent skills.

Seeding never deletes, and never overwrites your edits
The seeders insert what is missing and carry existing rows forward — but a field is only replaced while it is still byte-identical to what we last shipped into it. A built-in automation whose goal you rewrote keeps your version at every future upgrade, and one you switched off stays off.