Deploying Tripwire
Tripwire is two apps — a FastAPI backend (API + engine) and a Vite/React frontend (dashboard). The default execution path is cross-platform Playwright, so runs work on Linux, Windows, and macOS. This page covers Docker Compose, self-hosting, and CI runners.
docker-compose
The repo ships a docker-compose.yml that brings up the API + dashboard surface:
cp .env.example .env # add ANTHROPIC_API_KEY (+ tracker creds if wanted)
docker compose up --build- API + OpenAPI docs →
http://localhost:8400/docs - Dashboard →
http://localhost:3400
Compose brings up four things: a bundled Postgres (no manual install), a migrate service that applies the schema (alembic upgrade head) and exits, the api (which waits for Postgres to be healthy and migrations to finish), and the web dashboard (which waits for the api to be healthy). Containers run as non-root.
The API is authenticated — the first time you open the dashboard it shows a "Create your admin account" screen (or pre-seed the admin with TRIPWIRE_ADMIN_EMAIL + TRIPWIRE_ADMIN_PASSWORD for headless/CI). See Authentication.
services:
postgres:
image: postgres:17-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER:-tripwire}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-tripwire} # set a strong one in prod
POSTGRES_DB: ${POSTGRES_DB:-tripwire}
volumes: [tripwire-pgdata:/var/lib/postgresql/data]
healthcheck: # api waits on this
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-tripwire}"]
migrate: # runs alembic, then exits
image: tripwire-api
command: ["alembic", "upgrade", "head"]
depends_on: { postgres: { condition: service_healthy } }
api:
build: ./backend
ports: ["8400:8400"]
environment:
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
TRIPWIRE_DATA_DIR: /data # run artifacts (named volume)
TRIPWIRE_DATABASE_URL: postgresql+psycopg://tripwire:tripwire@postgres:5432/tripwire
TRIPWIRE_JWT_SECRET: ${TRIPWIRE_JWT_SECRET:-} # set in prod
TRIPWIRE_SECRET_KEY: ${TRIPWIRE_SECRET_KEY:-} # set in prod
volumes: [tripwire-data:/data]
depends_on:
postgres: { condition: service_healthy }
migrate: { condition: service_completed_successfully }
healthcheck: # GET /health/ready (db + disk)
test: ["CMD", "python", "-c", "import httpx,sys; sys.exit(0 if httpx.get('http://localhost:8400/api/v1/health/ready').status_code==200 else 1)"]
stop_grace_period: 25s # drain in-flight on SIGTERM
web:
build: ./frontend
ports: ["3400:3400"]
depends_on: { api: { condition: service_healthy } }Pointing the dashboard at a remote API
The dashboard calls a relative /api path; the bundled nginx proxies it to the api service. If you serve the dashboard separately from the API, set VITE_API_BASE at build time (it's baked into the bundle, not read at runtime) to the API's public URL.
Persist the two named volumes — tripwire-pgdata (the Postgres database: suites, run history, the built-in issue tracker, plans, settings, users) and tripwire-data (run artifacts / screenshots, under TRIPWIRE_DATA_DIR). Provide credentials (the Anthropic key, and any GitHub / GitLab / Jira tokens, and a log backend) via environment or the Settings API. See Settings & env.
Compose covers the API + UI surface
The compose containers run the API and dashboard. The default Playwright runner drives a headless Chromium and is cross-platform — to run actual browser tests inside a container, base the API image on a Playwright-ready image (or install playwright install --with-deps chromium) so Chromium and its system libraries are present.
Database — Postgres in Docker, SQLite for quick local dev
Structured data lives in a SQL database via SQLAlchemy, with a deliberate split so prod is robust and local stays instant:
| Where | Database | Schema |
|---|---|---|
docker compose / production | Postgres (bundled, no manual install) | Owned by Alembic — applied by the migrate service |
Host-native make dev-api | SQLite file under TRIPWIRE_DATA_DIR (zero setup) | Auto-created from the models |
| Test suite | throwaway SQLite per test | Auto-created |
Compose builds the API's TRIPWIRE_DATABASE_URL from POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB automatically — set a strong POSTGRES_PASSWORD in .env for production. To use an external/managed Postgres, set TRIPWIRE_DATABASE_URL to override the bundled one.
Migrations. Postgres schema changes are versioned with Alembic and applied by the migrate service on every up (or run by hand with make migrate / alembic upgrade head). SQLite dev/test auto-creates the schema from the models, so there's nothing to run locally.
Parity. CI runs the full backend test suite against both SQLite and Postgres, plus a job that asserts alembic upgrade head applies cleanly — so "works locally, breaks in prod" can't slip through. Locally you can do the same with make dev-db && make test-backend-pg.
Want host-native dev on Postgres too?
make dev-db starts just the Postgres container; then export TRIPWIRE_DATABASE_URL=postgresql+psycopg://tripwire:tripwire@localhost:5432/tripwire, make migrate, and make dev-api.
Zero-downtime upgrades
./deploy/upgrade.sh performs a safe rolling upgrade: back up → build → migrate → roll the api and web containers → wait for readiness. With the optional docker-rollout plugin installed it does a true zero-downtime swap (start the new container, wait for its healthcheck, then stop the old one); without it, it recreates the services. The api ships a --timeout-graceful-shutdown and a stop_grace_period, so in-flight requests drain instead of being cut off, and the healthcheck-gated depends_on means the dashboard never points at an api that isn't ready.
Production checklist: auth, secrets, scaling
- Auth — set
TRIPWIRE_ADMIN_EMAIL/TRIPWIRE_ADMIN_PASSWORDfor the first admin, andTRIPWIRE_JWT_SECRETso sessions survive restarts. CI and the MCP server authenticate with API tokens. See Authentication. - Secrets at rest — UI-set credentials are encrypted in the database with
TRIPWIRE_SECRET_KEY. Set it (and back it up) so encrypted secrets survive a DB move or restore. Process env vars always take precedence over stored settings. - Concurrency —
TRIPWIRE_MAX_CONCURRENT_RUNS(default2) sets how many runs execute in parallel; each is a browser plus LLM calls, so size it to the host. The queue is durable — runs persist as rows and a server restart reconciles anything left mid-run. - Cost caps — bound model spend with
TRIPWIRE_RUN_COST_CAP_USD(per run) andTRIPWIRE_DAILY_COST_CAP_USD(rolling 24h); an aborted run reports the reason. Per-requestTRIPWIRE_LLM_TIMEOUTandTRIPWIRE_LLM_RETRIESharden against transient model errors. - Abuse — failed logins are rate-limited per IP (10 / 5 min →
429). On a public deployment, also rate-limit at your reverse proxy and terminate TLS there.
Backup, restore & key rotation
Tripwire's state is two things: the database (suites, runs, issues, plans, settings, users, API tokens — integration secrets encrypted) and the artifacts directory (TRIPWIRE_DATA_DIR, the run screenshots). Back up both.
- What to keep safe —
TRIPWIRE_SECRET_KEY(decrypts stored secrets) andTRIPWIRE_JWT_SECRET(signs sessions). Store them in your secret manager. If you loseTRIPWIRE_SECRET_KEY, the encrypted settings in the DB can't be decrypted — you'd re-enter every integration credential. A restored DB on a new host needs the sameTRIPWIRE_SECRET_KEY. - Postgres (compose) — run
./deploy/backup.sh: itpg_dump | gzips the database to./backups/and keeps the 14 most recent.upgrade.shcalls it automatically before every upgrade. Restore withgunzip -c backups/tripwire_<ts>.sql.gz | docker compose exec -T postgres psql -U tripwire tripwire. Schedule it (cron) and keep the secret key with the dumps. - SQLite (host-native dev) — back up the
tripwire.dbfile underTRIPWIRE_DATA_DIR(stop the API or copy the WAL alongside it), plus theartifacts/subtree. - Rotating
TRIPWIRE_SECRET_KEY— re-encryption isn't automatic. To rotate: with the old key still set, re-save each secret setting through the dashboard/API so it re-encrypts under the new key, or clear and re-enter credentials after switching keys. Don't just swap the key on a populated DB — existing ciphertext won't decrypt.
Self-hosting
Put a reverse proxy / TLS terminator in front of both services:
- API — containerised from
backend/Dockerfile(compose serviceapi, port 8400). ProvideANTHROPIC_API_KEYand any tracker / log credentials via env or a secret manager; mount a volume atTRIPWIRE_DATA_DIRto persist the SQLite DB + artifacts (or use Postgres, above). SetTRIPWIRE_JWT_SECRETandTRIPWIRE_SECRET_KEYso sessions and encrypted secrets survive restarts. - Dashboard — built from
frontend/Dockerfile(serviceweb, port 3400). PointVITE_API_BASEat the API's public URL. - CORS — include the dashboard's public origin in
TRIPWIRE_CORS_ORIGINS. - Secrets — integration tokens and the Anthropic key are encrypted at rest and secret-masked on read; keep them in env / a secret manager, never in git.
- Network & trust — by design, Tripwire drives a browser against whatever URL an authenticated user points it at, including internal /
localhosttargets (that's the job — testing your own apps). So treat any authenticated user as able to reach the API host's network: keep the instance on a trusted network behind your proxy, and only invite users you'd trust with that reach. Run-ids are unguessable and artifacts are authenticated, so they aren't enumerable by an outsider.
CI runners
The default Playwright path runs fine on standard Linux CI runners — no macOS host required. The simplest integration is the GitHub Action, which sets up Python + uv, installs Chromium, and runs your suites with a meaningful exit code. For other CI systems, run the CLI directly:
cd backend
uv venv
uv pip install "pyyaml>=6.0" "anthropic>=0.49" "httpx>=0.28" "pillow>=11.0" \
"websocket-client>=1.8" "playwright>=1.40"
source .venv/bin/activate
playwright install --with-deps chromium
ANTHROPIC_API_KEY=$KEY python -m app.cli run "suites/*.tripwire.yaml" \
--file-issues github --out artifactsThe CLI exits 0 (pass) / 1 (failed) / 2 (broken) / 3 (config error), so the job gates the PR. Verified failures file deduped, root-caused issues to your configured destinations — a failed CI run leaves a triaged ticket behind, not just a red check.
Legacy macOS desktop driver
There's an optional legacy driver (TRIPWIRE_DRIVER=desktop) that drives the local macOS browser via native Quartz events (mac_control.py), requiring a macOS host with a display session. It is not needed for normal deployment — the cross-platform playwright driver (the default) is the supported path everywhere. Only choose desktop if you specifically want to drive a real on-screen macOS browser.
Checklist
- [ ] API reachable on
/api/v1;GET /api/v1/healthreturns OK (and/health/readyis 200). - [ ]
ANTHROPIC_API_KEYset for the engine. - [ ] First admin set (
TRIPWIRE_ADMIN_EMAIL/TRIPWIRE_ADMIN_PASSWORD);TRIPWIRE_JWT_SECRET+TRIPWIRE_SECRET_KEYset for production. - [ ] Database chosen — SQLite volume persisted, or
TRIPWIRE_DATABASE_URLpointed at Postgres. - [ ] Chromium available to the runner (
playwright install chromium). - [ ] Data volume persisted (
TRIPWIRE_DATA_DIR) for artifacts (+ SQLite). - [ ]
TRIPWIRE_CORS_ORIGINSincludes the dashboard's origin;VITE_API_BASEpoints at the API. - [ ] Concurrency (
TRIPWIRE_MAX_CONCURRENT_RUNS) + cost caps sized for the host. - [ ] Issue destinations + a log backend configured in Settings.
Related: Getting Started · CI · Settings & env