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.
Option 1 — Docker Compose
Section titled “Option 1 — Docker Compose”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.
git clone https://github.com/tappass/tappass && cd tappassmkdir -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))" >> .envpython3 -c "import secrets; print('TAPPASS_JWT_SECRET=' + secrets.token_urlsafe(48))" >> .envpython3 -c "import secrets, base64; print('TAPPASS_VAULT_KEY=' + base64.b64encode(secrets.token_bytes(32)).decode())" >> .env
docker compose up -dThe 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.
First login and your workspace
Section titled “First login and your workspace”From any machine with pip install tappass:
tappass config set server http://localhost:9620
# authenticate with the admin key you generated into .envgrep TAPPASS_ADMIN_API_KEY .env | cut -d= -f2 | tappass login --with-token
tappass org create --slug acme --name "Acme"tappass org switch acmeThe 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:
tappass org members add --email you@acme.com --role adminWith 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.
LLM-dependent features
Section titled “LLM-dependent features”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.envanddocker compose up -dagain, 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.
Governance-gap alerts
Section titled “Governance-gap alerts”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:
TAPPASS_DROPPED_INTENT_EMAIL=ops@your-company.com # comma-separated; who operates this serverTAPPASS_DROPPED_INTENT_GITHUB_REPO=your-org/your-repo # optionalTAPPASS_GITHUB_TOKEN=ghp_... # optionalOption 2 — From source
Section titled “Option 2 — From source”just setup # uv venv + uv pip install -e ".[dev]" pre-commitjust run # uv run python -m uvicorn tappass.adapters.inbound.api.main:app --reload --port 9620The 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):
tappass db start # container 'tappass-pg' on :5432, runs migrationstappass db status # show storage backend + connection statustappass db stop # stop container (data preserved)tappass db migrate # run pending migrations against $DATABASE_URLOption 3 — Guided bootstrap
Section titled “Option 3 — Guided bootstrap”tappass up --defaults # non-interactive: picks storage, generates secrets, runs migrations, starts the servertappass quickstart # server + account + registered agent + copy-paste code, in one shottappass 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.
Authentication in dev vs production
Section titled “Authentication in dev vs production”- 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, noTAPPASS_ADMIN_API_KEYset): authentication is disabled and every request is auto-authenticated asdev@localhostin orgdefaultwith roleorg_admin. The startup self-test reportsauth_enforcement: warn_dev_mode. - Production: set
TAPPASS_ADMIN_API_KEY(plus the other production secrets — see Configuration).
The production gate
Section titled “The production gate”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 is required
Section titled “Database is required”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.