Documentation Index
Fetch the complete documentation index at: https://docs.useterse.ai/llms.txt
Use this file to discover all available pages before exploring further.
npx create-terse brings up a full Terse control plane (backend, dashboard, Postgres) as Docker containers on a machine you operate. By default the data plane runs locally too; flip to a remote runtime later if you need to.
If you only want job code to run in your network and would rather keep Terse Cloud managing triggers and history, see Self-hosting the data plane. For the bigger picture, see Hosting overview.
Self-hosted Terse is governed by the Sustainable Use License. Internal business use is fine. Reselling it as a hosted product is not.
Bootstrap
./terse) and the frontend and backend URLs the instance should serve on. The script writes docker-compose.yml, .env, and a README.md into the directory, generates secrets, pulls the Terse image, and runs docker compose up -d. Migrations run inside the backend container on startup. If terse-cli isn’t on PATH, it’s installed globally and pointed at your new instance.
Prerequisites: Docker with Compose v2, and Node 20+ for npx. No repo clone, no pnpm, no manual Prisma steps.
Non-interactive mode
For CI, scripts, or AI agents (Claude Code, etc.) that can’t answer prompts, pass-y to accept all defaults. The script also auto-enables this mode whenever stdin or stdout is not a TTY, so it won’t hang in a non-interactive shell.
| Flag | Default | Purpose |
|---|---|---|
-y, --non-interactive | off | Skip prompts and accept defaults. Auto-on when no TTY. |
--target <dir> | ./terse | Install directory. |
--frontend-url <url> | http://localhost:5173 | Frontend URL written to .env. |
--backend-url <url> | http://localhost:3001 | Backend URL written to .env. |
First login
With noWORKOS_* env vars set, the backend runs in single-operator mode. The first request bootstraps an admin identity and hands you a session cookie. There is no signup form and no second user. Anyone who can reach the backend URL becomes the admin.
For real multi-user auth, add the WORKOS_* env vars to .env and restart. The backend swaps in WorkOS on next boot.
Where job code runs
terse deploy against a self-hosted instance runs jobs as subprocesses inside the backend container, without per-run isolation. That’s fine for code you wrote and trust.
To run jobs in isolated per-execution sandboxes (what Terse Cloud uses), set MODAL_TOKEN_ID and MODAL_TOKEN_SECRET in .env and restart. To run them in your own runtime instead (a worker fleet, an internal Kubernetes cluster), use the Hybrid setup.
Configuration
Anything user-tunable lives in.env. Edit and run docker compose up -d to apply. The bootstrap fills in sensible defaults; the variables you’ll actually touch are:
| Variable | Purpose |
|---|---|
FRONTEND_URL, BACKEND_URL | What the browser sees. Change both together if you put the instance behind a real domain. |
LOCAL_SECRETS_ENCRYPTION_KEY | Encrypts project secrets at rest. Generated for you. Back it up out of band — lose it and every stored integration credential is unrecoverable. |
NODE_ENV | development by default. Set to production before exposing the instance. |
TERSE_IMAGE | Pin to a specific image tag instead of :latest. Commented out by default. |
WORKOS_*, MODAL_*, RESEND_API_KEY, integration OAuth secrets | Opt in to anything that’s off by default. |
docker-compose.yml. The README.md in your install directory is the daily-ops cheat sheet for logs, upgrades, and backups.
Integrations
There is one config file,.env. Adding integration credentials there and running docker compose up -d enables the integration on the next restart. No per-integration config file, no second admin panel.
API-key integrations (Datadog, Snowflake, HeyReach, LaunchDarkly, PostHog) need no env vars. Connect them from the dashboard, paste the API key, done.
OAuth integrations are gated by env vars. Every required var below must be set and non-empty, otherwise the integration is hidden from the dashboard’s connect list:
| Integration | Required | Optional |
|---|---|---|
| Gmail | GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET, GMAIL_REDIRECT_URI, GMAIL_FRONTEND_REDIRECT, GMAIL_PUBSUB_TOPIC | GMAIL_PUBSUB_AUDIENCE, GMAIL_PUBSUB_SERVICE_ACCOUNT_EMAIL |
| GitHub | GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, GITHUB_APP_CALLBACK_URL, GITHUB_APP_NAME | GITHUB_LOGIN_CALLBACK_URL, GITHUB_LOGIN_REDIRECT |
| Slack | SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, SLACK_OAUTH_CALLBACK_URL | SLACK_SIGNING_SECRET |
| Linear | LINEAR_CLIENT_ID, LINEAR_CLIENT_SECRET_ID, LINEAR_OAUTH_CALLBACK_URL, LINEAR_WEBHOOK_SIGNING_SECRET | — |
| Notion | NOTION_OAUTH_CLIENT_ID, NOTION_OAUTH_CLIENT_SECRET, NOTION_OAUTH_REDIRECT_URI | — |
| Attio | ATTIO_CLIENT_ID, ATTIO_CLIENT_SECRET, ATTIO_REDIRECT_URI | — |
| Web monitors (Parallel) | PARALLEL_API_KEY, PARALLEL_WEBHOOK_SECRET | — |
http://localhost:3001/<provider>/callback on the backend side, http://localhost:5173/ (or your FRONTEND_URL) on the frontend side. If the OAuth provider rejects localhost (Google does for several Gmail scopes, Slack does for distributed apps), expose the backend through a tunnel (ngrok, Cloudflare Tunnel) and point the callback URLs at the public hostname.
Where to go next
Hosting overview
Compare the three deployment options.
Hybrid: self-host the data plane
Keep Terse Cloud for orchestration, run jobs in your VPC.
CLI reference
Every
terse command, including terse target for retargeting.Quickstart
Build your first workflow against the running instance.
