This is a personal proof-of-concept project. It is not intended for production use. Please refrain from using it.
Skip to content

Deploying Upliftr

Upliftr is AI debugging agents that debug your app: you describe a bug or a flow in plain English, an agent drives a real browser to reproduce it, root-causes the failure to the exact backend line, and files a deduped ticket. The agents are non-destructive by design, read-only, they investigate and diagnose, they never mutate your code, data, or infrastructure. You self-host them, bringing your own Anthropic key. It ships as 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 UPLIFTR_ADMIN_EMAIL + UPLIFTR_ADMIN_PASSWORD for headless/CI). See Authentication.

yaml
services:
  postgres:
    image: postgres:17-alpine
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-upliftr}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-upliftr}   # set a strong one in prod
      POSTGRES_DB: ${POSTGRES_DB:-upliftr}
    volumes: [upliftr-pgdata:/var/lib/postgresql/data]
    healthcheck:                                          # api waits on this
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-upliftr}"]

  migrate:                                                # runs alembic, then exits
    image: ghcr.io/upliftrhq/upliftr-api
    command: ["alembic", "upgrade", "head"]
    depends_on: { postgres: { condition: service_healthy } }

  api:
    build: ./backend
    ports: ["8400:8400"]
    environment:
      ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
      UPLIFTR_DATA_DIR: /data                            # run artifacts (named volume)
      UPLIFTR_DATABASE_URL: postgresql+psycopg://upliftr:upliftr@postgres:5432/upliftr
      UPLIFTR_JWT_SECRET: ${UPLIFTR_JWT_SECRET:-}       # set in prod
      UPLIFTR_SECRET_KEY: ${UPLIFTR_SECRET_KEY:-}       # set in prod
    volumes: [upliftr-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, upliftr-pgdata (the Postgres database: suites, run history, the built-in issue tracker, plans, settings, users) and upliftr-data (run artifacts / screenshots, under UPLIFTR_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 UPLIFTR_DATA_DIR (zero setup)Auto-created from the models
Test suitethrowaway SQLite per testAuto-created

Compose builds the API's UPLIFTR_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 UPLIFTR_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 UPLIFTR_DATABASE_URL=postgresql+psycopg://upliftr:upliftr@localhost:5432/upliftr, 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 UPLIFTR_ADMIN_EMAIL / UPLIFTR_ADMIN_PASSWORD for the first admin, and UPLIFTR_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 UPLIFTR_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, UPLIFTR_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 UPLIFTR_RUN_COST_CAP_USD (per run) and UPLIFTR_DAILY_COST_CAP_USD (rolling 24h); an aborted run reports the reason. Per-request UPLIFTR_LLM_TIMEOUT and UPLIFTR_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

Upliftr's state is two things: the database (suites, runs, issues, plans, settings, users, API tokens, integration secrets encrypted) and the artifacts directory (UPLIFTR_DATA_DIR, the run screenshots). Back up both.

  • What to keep safe, UPLIFTR_SECRET_KEY (decrypts stored secrets) and UPLIFTR_JWT_SECRET (signs sessions). Store them in your secret manager. If you lose UPLIFTR_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 UPLIFTR_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/upliftr_<ts>.sql.gz | docker compose exec -T postgres psql -U upliftr upliftr. Schedule it (cron) and keep the secret key with the dumps.
  • SQLite (host-native dev), back up the upliftr.db file under UPLIFTR_DATA_DIR (stop the API or copy the WAL alongside it), plus the artifacts/ subtree.
  • Rotating UPLIFTR_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 UPLIFTR_DATA_DIR to persist the SQLite DB + artifacts (or use Postgres, above). Set UPLIFTR_JWT_SECRET and UPLIFTR_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 UPLIFTR_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, an Upliftr agent drives a browser against whatever URL an authenticated user points it at, including internal / localhost targets (that's the job, debugging 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 integrations are the GitHub Action or GitLab CI, which sets up Python + uv, installs Chromium, and re-runs the checks the agent saved 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 upliftr run "suites/*.upliftr.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 (UPLIFTR_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 (UPLIFTR_ADMIN_EMAIL / UPLIFTR_ADMIN_PASSWORD); UPLIFTR_JWT_SECRET + UPLIFTR_SECRET_KEY set for production.
  • [ ] Database chosen, SQLite volume persisted, or UPLIFTR_DATABASE_URL pointed at Postgres.
  • [ ] Chromium available to the runner (playwright install chromium).
  • [ ] Data volume persisted (UPLIFTR_DATA_DIR) for artifacts (+ SQLite).
  • [ ] UPLIFTR_CORS_ORIGINS includes the dashboard's origin; VITE_API_BASE points at the API.
  • [ ] Concurrency (UPLIFTR_MAX_CONCURRENT_RUNS) + cost caps sized for the host.
  • [ ] Issue destinations + a log backend configured in Settings.

Related: Getting Started · CI · Settings & env

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