Skip to content

Deployment Guide

Everything you need to deploy, operate, and troubleshoot TapPass.


Internet → Cloudflare Tunnel → TapPass → PostgreSQL (data-net)
→ Redis (data-net)
→ OPA (identity-net)
→ SPIRE (identity-net)
→ Conjur (secrets-net)
→ Platform (license-net)
  • 5 isolated Docker networks — each service only reaches what it needs
  • TapPass is the only container that bridges all networks
  • Cloudflare Tunnel handles TLS + DDoS — zero ports exposed to internet
  • SPIRE issues auto-rotating certificates for workload identity
  • Conjur stores all secrets — fetched at runtime, never on disk
Terminal window
cd deploy
./install.sh # Interactive wizard (8 steps)
# → generates .env.prod
# → calls deploy.sh
# → if Conjur: run conjur/setup.sh after
StepWhatDefault
1Prerequisites (Docker, disk)Auto-check
2Secrets (admin key, JWT, vault)Auto-generated
3License keyMust paste
4Cloudflare TunnelMust paste token
5SSOGoogle OIDC
6SPIREEnabled
7Secrets managementConjur
8LLM keysOptional

All defaults are production-secure. Hitting Enter on everything gives a hardened deployment.

ContainerImageNetworkPurpose
tappassghcr.io/tappass/tappassallCore API + dashboard
postgrespostgres:16.8-alpinedata-netApplication database
redisredis:7.4.8-alpinedata-netRate limiting, sessions
opaopenpolicyagent/opa:1.14.0identity-netPolicy engine
spire-serverghcr.io/spiffe/spire-server:1.11.0identity-netIdentity CA
spire-agentghcr.io/spiffe/spire-agent:1.11.0identity-netWorkload attestation
spiffe-helperghcr.io/spiffe/spiffe-helper:0.8.0identity-netCert rotation to disk
conjurcyberark/conjur:1.24.0secrets-netSecrets vault
conjur-postgrespostgres:16.8-alpinesecrets-netConjur database
tunnelcloudflare/cloudflared:2026.2.0frontend-netHTTPS ingress
PropertyHow
Non-rootUID 999 via setpriv in entrypoint
Read-only filesystemread_only: true + tmpfs for /tmp
No core dumpsulimit -c 0 in entrypoint
Capabilities dropped--inh-caps=-all
Zero secrets in docker inspectConjur fetches at runtime
TLS between containersInternal CA certs in deploy/certs/
Redis authrequirepass + TLS
PostgreSQL SSLssl=on with internal CA
Network segmentation5 isolated networks
Audit trailSHA-256 hash chain, tamper-evident

Secrets stored in CyberArk Conjur. Fetched at runtime via REST API.

Terminal window
# View secrets in Conjur
ADMIN_KEY=$(grep CONJUR_ADMIN_API_KEY .env.prod | cut -d= -f2)
AUTH=$(curl -sf --data "$ADMIN_KEY" http://localhost:8080/authn/tappass/admin/authenticate | base64 -w0)
curl -sf -H "Authorization: Token token=\"$AUTH\"" http://localhost:8080/resources/tappass?kind=variable
Terminal window
# Rotate a secret
curl -sf -X POST -H "Authorization: Token token=\"$AUTH\"" \
--data "new-value" \
http://localhost:8080/secrets/tappass/variable/tappass%2FANTHROPIC_API_KEY
# TapPass picks up new value within 5 minutes (cache TTL)
# Or force immediate pickup:
curl -X POST http://localhost:9620/admin/secrets/invalidate?name=ANTHROPIC_API_KEY \
-H "Authorization: Bearer <admin_key>"

Secrets in .env.prod (chmod 600). Simpler but less secure.

Terminal window
# Quick
docker exec tappass-tappass-1 python3 -c \
"import urllib.request; print(urllib.request.urlopen('http://localhost:9620/health').read().decode())"
# Full
./scripts/health-monitor.sh
Terminal window
docker compose --env-file .env.prod -f docker-compose.prod.yml logs tappass -f --tail 50
Terminal window
docker compose --env-file .env.prod -f docker-compose.prod.yml restart tappass
Terminal window
bash scripts/upgrade.sh 0.6.0 # Upgrade to specific version
bash scripts/upgrade.sh --rollback # Rollback to previous

The upgrade script:

  1. Pulls new image
  2. Verifies signature (if cosign installed)
  3. Backs up database
  4. Rolling restart with health check (60s timeout)
  5. Auto-rollback if health check fails
WhatScheduleScriptRetention
TapPass PostgreSQLDaily 3:15 AMscripts/backup-postgres.sh30 days
Conjur PostgreSQLDaily 3:30 AMscripts/backup-conjur.sh30 days
Terminal window
bash scripts/backup-conjur.sh
bash scripts/backup-postgres.sh
Terminal window
bash scripts/test-restore.sh

Spins up temp containers, restores backups, verifies data. No production impact.

Terminal window
# Stop TapPass
docker compose --env-file .env.prod -f docker-compose.prod.yml stop tappass
# Restore TapPass PG
gunzip -c backups/postgres/tappass-YYYY-MM-DD.sql.gz | \
docker exec -i tappass-postgres-1 psql -U tappass tappass
# Restore Conjur
gunzip -c backups/conjur/conjur-YYYY-MM-DD.sql.gz | \
docker exec -i tappass-conjur-postgres-1 psql -U postgres postgres
# Restart
docker compose --env-file .env.prod -f docker-compose.prod.yml start tappass

Health monitor runs every 5 minutes via cron:

*/5 * * * * /path/to/deploy/scripts/health-monitor.sh >> logs/health-monitor.log 2>&1

Checks:

  • TapPass health endpoint
  • Platform health (licenses.tappass.ai)
  • All container status
  • Disk space (alerts >90%)
  • Internal CA cert expiry (alerts <30 days)

Alerting: set HEALTH_SLACK_WEBHOOK env var for Slack notifications.

Status files: deploy/status/tappass.down created when down, removed on recovery.

Re-register workload entries (after SPIRE restart)

Section titled “Re-register workload entries (after SPIRE restart)”
Terminal window
TOKEN=$(docker exec tappass-spire-server-1 /opt/spire/bin/spire-server token generate \
-spiffeID spiffe://tappass.internal/agent/spire-agent | grep Token | awk '{print $2}')
# Update .env.prod
sed -i "s/SPIRE_JOIN_TOKEN=.*/SPIRE_JOIN_TOKEN=$TOKEN/" .env.prod
# Restart agent
docker compose --env-file .env.prod -f docker-compose.prod.yml up -d --force-recreate spire-agent
# Re-register entries
docker exec tappass-spire-server-1 bash /opt/spire/register-entries.sh
Terminal window
docker exec tappass-tappass-1 openssl x509 -in /run/spire/certs/svid.pem -noout -dates -checkend 0
SymptomCauseFix
storage: memoryPG not connectedCheck docker logs tappass-postgres-1
PRODUCTION UNSAFESecrets not loadedVerify Conjur is healthy, check .conjur-authn-key
seat_limit_exceededToo many activationsDeactivate old seats in Platform admin
SPIRE certs expiredspiffe-helper stoppedRestart: docker restart tappass-spiffe-helper-1
NOAUTH Redis errorWrong passwordCheck REDIS_PASSWORD in .env.prod matches Redis config
OPA 403 on all requestsNo authz policy loadedCheck docker logs tappass-opa-1, verify policies mounted
#IssueStatus
1Conjur on plain HTTP (internal)Needs reverse proxy
2SPIFFE JWT → Conjur ES256 incompatibilityConjur OSS limitation
3Docker socket in SPIRE agentSPIRE attestor requirement
4Static Conjur API keyDepends on #2
5PostgreSQL SSL client certHOME=/root issue
6Platform HA for 50+ customersScaling milestone
7Platform auth: short-lived tokensArchitecture improvement