Skip to content

Self-hosting

TapPass is a single FastAPI service (tappass.adapters.inbound.api.main:app) backed by PostgreSQL. Redis is optional shared state for multi-worker deployments. Governance decisions run in-process.

The server repo root ships a complete local stack (repo access comes with your license — contact us if you don’t have it; the public tappass-sdk repo is the client SDK and does not contain the server).

Three commands: clone, generate secrets, start.

Terminal window
git clone https://github.com/tappass/tappass && cd tappass
mkdir -p data # audit-log mount; on Linux, create it before docker does (root-owned otherwise)
python3 -c "import secrets; print('TAPPASS_ADMIN_API_KEY=tp_' + secrets.token_urlsafe(32))" >> .env
python3 -c "import secrets; print('TAPPASS_JWT_SECRET=' + secrets.token_urlsafe(48))" >> .env
python3 -c "import secrets, base64; print('TAPPASS_VAULT_KEY=' + base64.b64encode(secrets.token_bytes(32)).decode())" >> .env
docker compose up -d

The server is up when curl http://localhost:9620/health returns {"status": "healthy", ...}.

The three secrets are required — the container refuses to boot without them (Preflight failed), because a server without an admin key, JWT secret, and vault key is open to anyone. Keep the .env file: it is your admin credential.

Service Image Port Notes
tappass built from source (Dockerfile) 9620 API + dashboard at http://localhost:9620
postgres postgres:16-alpine 5432 Schema auto-applied from deploy/shared/migrations/schema.sql on first boot
redis redis:7-alpine 6379 --maxmemory 64mb --maxmemory-policy allkeys-lru

The compose file reads .env and injects DATABASE_URL, TAPPASS_KV_URL, TAPPASS_DEBUG=true, and CORS for the local dashboard. ./data is mounted into the container for the JSONL audit log.

From any machine with pip install tappass:

Terminal window
tappass config set server http://localhost:9620
# authenticate with the admin key you generated into .env
grep TAPPASS_ADMIN_API_KEY .env | cut -d= -f2 | tappass login --with-token
tappass org create --slug acme --name "Acme"
tappass org switch acme

The org switch matters: the admin session starts in the platform org, and everything you do next — invites, agents, approvals — binds to whichever org your session is in.

To add yourself (or a teammate) as a real account, invite them:

Terminal window
tappass org members add --email you@acme.com --role admin

With no email provider configured, the response includes an accept_url — open it to set a password and finish joining. (With email configured, the invitee gets the link by mail and the URL is never returned.) Use a real email domain — reserved names like .test are refused.

tappass try, tappass chat, tappass ask, and tappass policy create --from-prompt need a model to reason with. Give the server one:

  • add OPENAI_API_KEY=sk-... to .env and docker compose up -d again, or
  • add an org-level provider key in the dashboard under Settings → LLM providers.

Without one, these commands tell you exactly this — everything else (gateway, policies, /v1/govern enforcement, audit) works with no LLM key at all.

When --from-prompt understands part of your instruction but cannot express it as enforceable rules, the policy is created without that part — and TapPass records it: an audit event (policy.intent.dropped), an email to the platform operators, and optionally a GitHub issue an automation can pick up:

Terminal window
TAPPASS_DROPPED_INTENT_EMAIL=ops@your-company.com # comma-separated; who operates this server
TAPPASS_DROPPED_INTENT_GITHUB_REPO=your-org/your-repo # optional
TAPPASS_GITHUB_TOKEN=ghp_... # optional
Terminal window
just setup # uv venv + uv pip install -e ".[dev]" pre-commit
just run # uv run python -m uvicorn tappass.adapters.inbound.api.main:app --reload --port 9620

The tappass commands below are the server package’s CLI (installed by the editable install above) — not the pip SDK, whose tappass command only offers configure/run/status.

just run needs a reachable PostgreSQL. Start one in Docker (and print the DATABASE_URL for your .env):

Terminal window
tappass db start # container 'tappass-pg' on :5432, runs migrations
tappass db status # show storage backend + connection status
tappass db stop # stop container (data preserved)
tappass db migrate # run pending migrations against $DATABASE_URL
Terminal window
tappass up --defaults # non-interactive: picks storage, generates secrets, runs migrations, starts the server
tappass quickstart # server + account + registered agent + copy-paste code, in one shot
tappass down # stop the tappass / tappass-pg containers (data preserved)

tappass up without --defaults walks you through storage (memory, local PostgreSQL, Supabase) and runtime (here, Docker, Kubernetes) interactively.

  • Docker Compose: the entrypoint preflight requires the three secrets above, so authentication is always on — log in with the admin key as shown.
  • From source (just run, no TAPPASS_ADMIN_API_KEY set): authentication is disabled and every request is auto-authenticated as dev@localhost in org default with role org_admin. The startup self-test reports auth_enforcement: warn_dev_mode.
  • Production: set TAPPASS_ADMIN_API_KEY (plus the other production secrets — see Configuration).

Production mode is opt-in: TAPPASS_PRODUCTION=1, or auto-detected from TAPPASS_ENV=production. At startup verify_production_config() re-validates the live Settings and refuses to boot (ConfigurationError) if any of these fail:

Check Requirement
TAPPASS_ADMIN_API_KEY must be set — otherwise all endpoints are unauthenticated
TAPPASS_JWT_SECRET must be set and ≥ 32 chars (48+ recommended); an ephemeral per-process key breaks every token on restart
TAPPASS_VAULT_KEY must be set — otherwise credentials are encrypted with a publicly known dev key
TAPPASS_DEBUG must be false — debug leaks stack traces with internal paths
TAPPASS_TOKEN_KEY_FILE must point at a persistent token-signing PEM — otherwise the signing key is regenerated on every restart, invalidating outstanding tokens and JWKS caches
Google audience TAPPASS_SSO_GOOGLE_CLIENT_ID or TAPPASS_GOOGLE_CLIENT_IDS must be configured (prevents audience-confusion account takeover on /auth/google-token); TAPPASS_GOOGLE_TOKEN_ALLOW_ANY_AUDIENCE=true is refused
SAML (if enabled) TAPPASS_SAML_WANT_ASSERTIONS_SIGNED and TAPPASS_SAML_WANT_RESPONSE_SIGNED must be true

A non-critical warning is also logged when TAPPASS_AUDIT_LOG_PARAMS=true (request parameters in the audit log may expose confidential data).

DATABASE_URL is mandatory. With no database URL the boot raises RuntimeError: DATABASE_URL is required — every store (accounts, agents, memberships, sessions, budgets) requires durable Postgres storage. The only escape hatch is TAPPASS_ALLOW_EPHEMERAL=1 (in-memory stores), intended for the test harness only, never production.

Migrations normally run as a discrete release step (via tappass db migrate or your pipeline). Set TAPPASS_RUN_MIGRATIONS_ON_BOOT=1 to run them during startup instead — the runner uses an advisory lock, so concurrent boots are safe. Production deployments should leave this off so a bad migration cannot wedge every replica simultaneously.