Skip to content

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:

bash
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.

yaml
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 volumestripwire-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:

WhereDatabaseSchema
docker compose / productionPostgres (bundled, no manual install)Owned by Alembic — applied by the migrate service
Host-native make dev-apiSQLite file under TRIPWIRE_DATA_DIR (zero setup)Auto-created from the models
Test suitethrowaway SQLite per testAuto-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_PASSWORD for the first admin, and TRIPWIRE_JWT_SECRET so 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.
  • ConcurrencyTRIPWIRE_MAX_CONCURRENT_RUNS (default 2) 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) and TRIPWIRE_DAILY_COST_CAP_USD (rolling 24h); an aborted run reports the reason. Per-request TRIPWIRE_LLM_TIMEOUT and TRIPWIRE_LLM_RETRIES harden 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 safeTRIPWIRE_SECRET_KEY (decrypts stored secrets) and TRIPWIRE_JWT_SECRET (signs sessions). Store them in your secret manager. If you lose TRIPWIRE_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 same TRIPWIRE_SECRET_KEY.
  • Postgres (compose) — run ./deploy/backup.sh: it pg_dump | gzips the database to ./backups/ and keeps the 14 most recent. upgrade.sh calls it automatically before every upgrade. Restore with gunzip -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.db file under TRIPWIRE_DATA_DIR (stop the API or copy the WAL alongside it), plus the artifacts/ 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 service api, port 8400). Provide ANTHROPIC_API_KEY and any tracker / log credentials via env or a secret manager; mount a volume at TRIPWIRE_DATA_DIR to persist the SQLite DB + artifacts (or use Postgres, above). Set TRIPWIRE_JWT_SECRET and TRIPWIRE_SECRET_KEY so sessions and encrypted secrets survive restarts.
  • Dashboard — built from frontend/Dockerfile (service web, port 3400). Point VITE_API_BASE at 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 / localhost targets (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:

bash
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 artifacts

The 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/health returns OK (and /health/ready is 200).
  • [ ] ANTHROPIC_API_KEY set for the engine.
  • [ ] First admin set (TRIPWIRE_ADMIN_EMAIL / TRIPWIRE_ADMIN_PASSWORD); TRIPWIRE_JWT_SECRET + TRIPWIRE_SECRET_KEY set for production.
  • [ ] Database chosen — SQLite volume persisted, or TRIPWIRE_DATABASE_URL pointed at Postgres.
  • [ ] Chromium available to the runner (playwright install chromium).
  • [ ] Data volume persisted (TRIPWIRE_DATA_DIR) for artifacts (+ SQLite).
  • [ ] TRIPWIRE_CORS_ORIGINS includes the dashboard's origin; VITE_API_BASE points 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

Tripwire — AI-native, self-healing E2E testing. Terms · Privacy · Legal Notice