Skip to content

Self-hosting Tripwire

Run the whole platform on your own infrastructure with Docker — the API + engine, the dashboard, the docs, and a Postgres database, behind an automatic-HTTPS front door, with migrations, health checks, backups, and zero-downtime upgrades.

There are two paths:

  • Quick install — one command does everything. Recommended.
  • Manual setup — wire each piece by hand (custom proxy, external Postgres, …).

If you just want to run it on your laptop to develop or evaluate, see Local development instead.

On a fresh Linux host with Docker installed, one command stands up the entire stack — it generates every secret, writes .env, brings the platform up behind a TLS front door, creates your admin, and prints the login:

bash
git clone https://github.com/tripwire-e2e/tripwire.git /opt/tripwire
cd /opt/tripwire
sudo ./install/install.sh

The installer:

  1. Pre-flights the host — checks Docker, Compose, and ports 80/443, and installs the docker-rollout plugin so upgrades are zero-downtime.
  2. Prompts for your dashboard domain, docs domain, admin email, and Anthropic key. (The key is the instance fallback — each org can also bring its own later in the dashboard, so you can leave it blank and set it in the UI.)
  3. Generates every secretTRIPWIRE_SECRET_KEY, TRIPWIRE_JWT_SECRET, the database password, and a strong admin password — into a chmod 600 .env.
  4. Builds, migrates, and starts Postgres + api + web + docs behind Caddy, which auto-provisions Let's Encrypt certificates.
  5. Creates your admin (org + owner) on first boot and prints the one-time login plus the DNS records to add.

Then point your domain's A records at the host:

app.yourco.com   →  <this host's IP>
docs.yourco.com  →  <this host's IP>

HTTPS comes up automatically within a minute. That's it — skip to Day-2 operations.

Re-running is safe

./install/install.sh is idempotent: run it again any time and it reuses your existing .env and just brings the stack up to date.

Save the admin password

It's printed once. Store it in your password manager and change it after first login in Settings. (Lose it before then? Re-create the admin by setting TRIPWIRE_ADMIN_EMAIL / TRIPWIRE_ADMIN_PASSWORD in .env against a fresh database.)


Manual setup

Prefer to wire everything yourself — a custom reverse proxy, an external Postgres, or no docs site? Here's every step the installer automates.

What you'll run

docker compose up brings up the core services; the cloud overlay adds the docs site and a Caddy TLS front door:

ServiceWhat it isPort
webThe React dashboard (static SPA) served by nginx, which reverse-proxies /api → the api.3400 (localhost)
apiThe FastAPI backend + execution engine (drives headless Chromium).8400 (localhost)
postgresBundled database (suites, runs, issues, plans, settings, users).internal
migrateApplies Alembic migrations, then exits. The api waits for it.
docsThe VitePress docs site (nginx). Cloud overlay only.internal
caddyTLS front door — terminates HTTPS and routes app. → web, docs. → docs. Cloud overlay only.80 / 443

The dashboard calls the API over a relative /api path, so the browser only talks to the front door; web, api, and postgres are bound to localhost / the internal Docker network and are never exposed directly.

Prerequisites

  • A Linux host (a 2 vCPU / 4 GB VM is a reasonable start — each concurrent run drives a real Chromium, so size up if you raise TRIPWIRE_MAX_CONCURRENT_RUNS).
  • Docker Engine 24+ and Docker Compose v2 (docker compose version).
  • An Anthropic API key (sk-ant-…) — Claude is the engine that drives the browser. (Optional up front; orgs can BYO key in the dashboard.)
  • A domain + DNS if exposing beyond localhost.

1. Get the code

bash
git clone https://github.com/tripwire-e2e/tripwire.git /opt/tripwire
cd /opt/tripwire

2. Configure secrets

bash
cp .env.example .env
ini
# .env — the minimum for a real deployment
TRIPWIRE_DEPLOYMENT_MODE=selfhosted
ANTHROPIC_API_KEY=sk-ant-...

# Database — compose builds the api's connection string from these.
POSTGRES_PASSWORD=<a strong random password>

# Set these so sessions and encrypted secrets survive restarts/migrations.
TRIPWIRE_JWT_SECRET=<random>
TRIPWIRE_SECRET_KEY=<random>

Keep TRIPWIRE_SECRET_KEY safe

It encrypts integration credentials at rest. If you lose it you can't decrypt stored secrets, and a database restored onto a new host needs the same key. Store it in your secret manager and back it up with your database dumps.

Generate strong values quickly:

bash
echo "POSTGRES_PASSWORD=$(openssl rand -hex 24)"  >> .env
echo "TRIPWIRE_JWT_SECRET=$(openssl rand -hex 48)" >> .env
echo "TRIPWIRE_SECRET_KEY=$(openssl rand -hex 48)" >> .env

Optionally pre-seed the first admin (otherwise you create it in the browser):

ini
TRIPWIRE_ADMIN_EMAIL=admin@yourco.com
TRIPWIRE_ADMIN_PASSWORD=<at least 8 chars>

See the full Settings & env reference for every variable.

3. Bring it up

bash
docker compose up -d --build

On first boot, in order: postgres becomes healthy → migrate applies the schema and exits → api starts and passes its health check (/api/v1/health/ready) → web starts. Watch it settle:

bash
docker compose ps          # services "running"/"healthy", migrate "exited (0)"
docker compose logs -f api

Containers run as non-root; data persists in two named volumes (tripwire-pgdata for the database, tripwire-data for run-artifact screenshots).

4. Create the first admin

If you didn't pre-seed an admin, browse to the dashboard and you'll get a one-time "Create your admin account" screen. Set an email + password (min 8 chars) and you're in. From Settings you can invite the rest of your team. See Authentication.

5. Put TLS in front

The simplest path is the bundled Caddy front door — the same one the installer uses. Set the hostnames and select the overlay in .env:

ini
TRIPWIRE_APP_HOST=tripwire.yourco.com
TRIPWIRE_DOCS_HOST=docs.yourco.com
# Make every `docker compose` command use the TLS + docs overlay:
COMPOSE_FILE=docker-compose.yml:docker-compose.cloud.yml

Then bring it up and point DNS (A records for both hosts) at the server:

bash
docker compose up -d --build

Caddy auto-provisions Let's Encrypt certificates and routes app. → the dashboard and docs. → the docs. No Caddyfile editing — the hostnames come from .env.

Bring your own proxy

Prefer Traefik / nginx / an existing ingress? Skip the overlay (leave COMPOSE_FILE unset), keep web and api on localhost, and point your proxy at localhost:3400 (the dashboard, which proxies /api itself). Set TRIPWIRE_CORS_ORIGINS=["https://tripwire.yourco.com"] and recreate the api.

Day-2 operations

Backups

./deploy/backup.sh dumps the database (pg_dump | gzip) to ./backups/ and keeps the 14 most recent. Schedule it:

bash
# crontab -e — nightly at 02:00
0 2 * * * cd /opt/tripwire && ./deploy/backup.sh

Restore a dump:

bash
gunzip -c backups/tripwire_<timestamp>.sql.gz \
  | docker compose exec -T postgres psql -U tripwire tripwire

Keep TRIPWIRE_SECRET_KEY alongside the dumps — the encrypted settings inside are useless without it.

Upgrades

bash
git pull
./deploy/upgrade.sh

It backs up → builds → runs migrations → rolls every app container (api, web, docs) → reloads Caddy → waits for readiness. It upgrades whatever services your COMPOSE_FILE selects, so the same command serves both the core and the TLS-front-door stacks. With the docker-rollout plugin (the installer adds it) it does a true zero-downtime swap — start new, wait healthy, stop old; without it, it recreates the services (a brief blip). The api drains in-flight requests on shutdown, and the durable run queue reconciles any run interrupted by the restart.

Scaling & cost control

  • ConcurrencyTRIPWIRE_MAX_CONCURRENT_RUNS (default 2) sets how many runs execute in parallel; each is a browser + LLM calls, so size it to the host. Tripwire runs as a single process — scale runs with this knob, not by running multiple api replicas.
  • Cost capsTRIPWIRE_RUN_COST_CAP_USD (per run) and TRIPWIRE_DAILY_COST_CAP_USD (rolling 24h) bound model spend; an aborted run reports the reason.
  • Login abuse — failed logins are rate-limited per IP (10 / 5 min → 429); add proxy-level limits too on a public deployment.

Using a managed/external Postgres

Point the api at it instead of the bundled service, then run migrations against it:

ini
TRIPWIRE_DATABASE_URL=postgresql+psycopg://user:pass@db.internal:5432/tripwire
bash
docker compose run --rm migrate    # applies alembic upgrade head to your DB
docker compose up -d api web       # (you can drop the bundled postgres service)

Tune the pool with TRIPWIRE_DB_POOL_SIZE / TRIPWIRE_DB_MAX_OVERFLOW / TRIPWIRE_DB_POOL_TIMEOUT.

Uninstall

bash
./install/uninstall.sh        # stops the stack; prompts before deleting data volumes

Production checklist

  • [ ] ANTHROPIC_API_KEY (or BYO per-org), a strong POSTGRES_PASSWORD, TRIPWIRE_JWT_SECRET, and TRIPWIRE_SECRET_KEY set in .env (secrets in a manager, not in git).
  • [ ] TLS front door up (TRIPWIRE_APP_HOST + the cloud overlay, or your own proxy); web/api/ postgres not publicly exposed.
  • [ ] DNS A records for app. (and docs.) point at the host.
  • [ ] First admin created; team invited from Settings.
  • [ ] Nightly deploy/backup.sh; TRIPWIRE_SECRET_KEY stored with the backups.
  • [ ] TRIPWIRE_MAX_CONCURRENT_RUNS and cost caps sized to the host/budget.
  • [ ] Integration credentials use a dedicated service account, not a personal token.

Troubleshooting

SymptomLikely cause / fix
api stuck "starting", never healthyIt can't reach Postgres or migrations failed. docker compose logs migrate api; confirm POSTGRES_PASSWORD matches what the db came up with (a changed password on an existing tripwire-pgdata volume won't apply — reset the volume or set the old password).
HTTPS doesn't come upDNS for TRIPWIRE_APP_HOST / TRIPWIRE_DOCS_HOST must resolve to this host and ports 80/443 must be open — Caddy needs both to complete the ACME challenge. docker compose logs caddy.
Dashboard loads but every call is 401Expected until you create/sign in as the admin. If sessions drop after a restart, set TRIPWIRE_JWT_SECRET (otherwise it's regenerated each boot).
Stored integration secrets "disappear" after a moveTRIPWIRE_SECRET_KEY changed or wasn't set — encrypted values can't be decrypted. Restore the original key.
Screenshots/artifacts 404 in the dashboardThe tripwire-data volume isn't persisted, or a proxy is stripping the ?token= query param on /api/v1/artifacts.
Runs fail instantly with a model errorCheck ANTHROPIC_API_KEY, and any TRIPWIRE_RUN_COST_CAP_USD/DAILY_COST_CAP_USD that may be aborting them.

Next: the Deployment reference for the compose internals, and Settings & env for every variable.

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