Install TokenTimer self-hosted
Choose an install path
| Path | Best for | Prerequisites |
|---|---|---|
| Docker Compose | Single host, fastest start | Docker and Docker Compose |
| Kubernetes (Helm) | Production clusters | Kubernetes >= 1.29, Helm >= 3.14 |
Both paths deploy the same components: the REST API (Node.js, port 4000), the React dashboard, PostgreSQL, and a set of workers (alert discovery, alert delivery, weekly digest, auto sync, endpoint/SSL check).
Option A - Docker Compose
Step 1 - Clone and configure
cd tokentimer-core/deploy/compose
cp .env.example .env
Step 2 - Edit .env
Minimal required configuration (production-safe baseline):
# Core runtime
NODE_ENV=production
SESSION_SECRET=replace_with_a_long_random_value
# Database
DB_HOST=postgres
DB_PORT=5432
DB_NAME=tokentimer
DB_USER=tokentimer
DB_PASSWORD=replace_with_secure_password
# Initial admin bootstrap (first start only)
ADMIN_EMAIL=admin@your-company.com
ADMIN_PASSWORD=ChangeThisSecurePassword123!
ADMIN_NAME=Administrator
# Public URLs (what users/browsers should use)
APP_URL=http://localhost:5173
API_URL=http://localhost:4000
Optional but common additions: SMTP sender identity (SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, FROM_EMAIL, FROM_EMAIL_NAME) and host port remaps (API_PORT, DASHBOARD_PORT; keep APP_URL/API_URL in sync if you remap).
If you run locally on http://localhost with NODE_ENV=production, authentication can fail because secure session cookies are not persisted or sent by the browser on plain local HTTP. Put HTTPS in front of the API and dashboard (reverse proxy with TLS), or use NODE_ENV=development for local non-TLS testing. As a last-resort local troubleshooting option only, set SESSION_COOKIE_SECURE_LOCALHOST_OVERRIDE=true.
If the API sits behind a reverse proxy or load balancer, set TRUST_PROXY_HOPS to the number of proxy hops (the Compose default is 2, covering LB plus reverse proxy; set 0 when nothing is in front).
Step 3 - Start all services
# A) Build from local source (default)
docker compose up -d
# B) Use prebuilt images (override file)
# Optional vars in .env: TT_IMAGE_REGISTRY, TT_IMAGE_OWNER, TT_IMAGE_TAG
docker compose -f docker-compose.yml -f docker-compose.images.yml up -d
The Compose stack starts PostgreSQL, runs database migrations as a one-shot migrations service, then starts the API, the dashboard, and the five worker containers (worker-discovery, worker-delivery, worker-weekly-digest, worker-auto-sync, worker-endpoint-check).
Step 4 - Verify
# View logs
docker compose logs -f
# Check health
curl http://localhost:4000/health
# View running services
docker compose ps
You should see: all services Up (API and dashboard with healthy healthchecks), and the health endpoint responding with:
{
"status": "healthy",
"timestamp": "2026-03-19T12:00:00.000Z",
"uptime": 123.456,
"memory": { "rss": 52428800 },
"environment": "production"
}
Open http://localhost:5173 in your browser to reach the dashboard.
Option B - Kubernetes with Helm
The chart deploys the API and dashboard as Deployments, the workers as CronJobs, and PostgreSQL as a CloudNativePG Cluster by default (an existing external PostgreSQL is also supported). Optional resources (disabled by default): Ingress, HPA, PDB, ServiceMonitor, PrometheusRule, NetworkPolicy.
Step 1 - Install the CloudNativePG operator (if using in-cluster PostgreSQL)
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm repo update
helm install cnpg-operator cnpg/cloudnative-pg \
--namespace cnpg-system --create-namespace \
--version 0.23.0 \
--wait
Verify the CRDs are registered:
kubectl get crd clusters.postgresql.cnpg.io
If you already have PostgreSQL, skip the operator and point the chart at your database:
postgresql:
cloudnative:
enabled: false
external:
enabled: true
host: "db.example.com"
port: 5432
database: tokentimer
username: tokentimer
password: "secret" # or use existingSecret
sslMode: require
Step 2 - Install the chart
helm install tokentimer oci://ghcr.io/tokentimerch/charts/tokentimer \
--namespace tokentimer --create-namespace \
--set config.adminEmail="admin@your-company.com" \
--set config.adminPassword="SecurePassword123!" \
--set config.sessionSecret="replace-with-long-random-value" \
--set postgresql.auth.password="your-db-password" \
--set ingress.enabled=true \
--set ingress.hosts[0].host="tokentimer.your-domain.com"
For production, prefer a values file (cp deploy/helm/values.yaml my-values.yaml, edit, then -f my-values.yaml) and pre-existing Kubernetes Secrets instead of plaintext values:
config:
existingSecret: "my-tokentimer-secrets" # must contain SESSION_SECRET
postgresql:
external:
existingSecret: "my-db-secret" # all DB_* keys
smtp:
existingSecret: "my-smtp-secret" # all SMTP_* + FROM_* keys
The chart auto-generates SESSION_SECRET, DB_PASSWORD, and ADMIN_PASSWORD (when adminEmail is set) if you do not provide them, but auto-generated values change on each helm upgrade. Set explicit values or use existingSecret for production stability.
Step 3 - Wait for pods and access the dashboard
kubectl get pods -n tokentimer -w
You should see: API and dashboard pods reach Running/Ready, and the PostgreSQL cluster become healthy.
# If using ingress
open https://tokentimer.your-domain.com
# Or port-forward
kubectl port-forward svc/tokentimer-dashboard 8080:80 -n tokentimer
open http://localhost:8080
If the admin password was auto-generated, retrieve it with:
kubectl get secret -n tokentimer tokentimer-secrets \
-o go-template='{{index .data "ADMIN_PASSWORD" | base64decode}}{{println}}'
With NODE_ENV=production, the API sets the Secure flag on session cookies. Browsers will not persist or send secure cookies over plain HTTP. For anything beyond local port-forwarding, place HTTPS in front of the API and dashboard (Ingress with TLS or a reverse proxy).
Required environment variables
Minimum required for a working install (Docker Compose):
SESSION_SECRET=long-random-string # required - long random value
DB_PASSWORD=secure-db-password # required
ADMIN_EMAIL=admin@your-company.com # required - bootstraps the admin account
ADMIN_PASSWORD=secure-admin-password # required on first start; remove afterwards
APP_URL=http://localhost:5173
API_URL=http://localhost:4000
For every available variable (SMTP, Twilio, integrations, worker tuning, security overrides), see the Configuration Reference, the annotated deploy/compose/.env.example, and deploy/helm/values.yaml.
First login
TokenTimer creates the admin user automatically on first startup from ADMIN_EMAIL and ADMIN_PASSWORD.
- Navigate to your dashboard URL and open the login page.
- Log in with your configured
ADMIN_EMAILandADMIN_PASSWORD. - You should see your default admin workspace.
After first login, remove ADMIN_PASSWORD from your .env file (or values). It is only needed for the bootstrap on first start.
Recommended next steps: invite team members (Workspace Settings, Members), add your first token, configure alert thresholds and channels in Alerts & thresholds, and configure SMTP (via System Settings UI or env vars).
Upgrading
Docker Compose
# Pull latest images
docker compose pull
# Restart services
docker compose up -d
# Verify health
curl http://localhost:4000/health
Kubernetes
helm upgrade tokentimer oci://ghcr.io/tokentimerch/charts/tokentimer -f my-values.yaml -n tokentimer
# Monitor rollout
kubectl rollout status deployment/tokentimer-api
Upgrade notes:
- API and dashboard Deployments roll automatically on
helm upgrade(checksum annotations change when config, SMTP, Twilio, or DB values change). - CronJob spec changes apply on the next scheduled worker run.
- Pods read ConfigMap/Secret env vars only at start; a rollout is required after changing
config.baseUrl,config.apiUrl, or SSO env. - Back up the database before major upgrades:
docker compose exec postgres pg_dump -U tokentimer tokentimer > backup.sql
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| API won't start | Database unreachable or credentials wrong | Test with psql postgresql://tokentimer:password@localhost:5432/tokentimer; check docker compose logs api |
| Login returns 401 on localhost | NODE_ENV=production with plain-HTTP local URLs; the browser drops secure session cookies | Put HTTPS in front, or use NODE_ENV=development locally; last resort: SESSION_COOKIE_SECURE_LOCALHOST_OVERRIDE=true |
| No admin account exists | Admin bootstrap skipped (no ADMIN_EMAIL/ADMIN_PASSWORD on first start, or DISABLE_ADMIN_BOOTSTRAP set) | Set the bootstrap variables and restart the API once; remove ADMIN_PASSWORD afterwards |
| Workers not sending alerts | SMTP unconfigured, or alerts still queued | Check worker logs (docker compose logs worker-discovery worker-delivery); verify SMTP env (docker compose exec api env filtered on SMTP_/FROM_EMAIL); inspect the alert_queue table for pending rows |
| Dashboard loads but API calls fail | API_URL/APP_URL mismatch with actual public URLs | Align the URL variables with what browsers use, rebuild/restart the dashboard |
| Helm install stalls on database | CloudNativePG operator not installed (default in-cluster PostgreSQL requires it) | Install the CNPG operator first, or switch to postgresql.external |
| Settings changed but behavior unchanged (Kubernetes) | Pods still hold the old environment; env vars are read at start | Run a rollout restart of the affected Deployments |
Uninstalling
# Docker Compose (keeps data)
docker compose down
# ... including volumes
docker compose down -v
# Kubernetes
helm uninstall tokentimer -n tokentimer
# CloudNativePG PVCs are retained by default; delete manually if no longer needed
Next steps
- Configuration reference - configure SMTP for email alerts, Twilio for WhatsApp, and worker tuning.
- Alerts & thresholds - set thresholds, delivery windows, and channels.
- Tokens - add your first tokens: manual entry, file import, integrations, or endpoint monitoring.
- Backup & restore - set up PostgreSQL backups before you rely on the instance.
- Monitoring & metrics - expose Prometheus metrics and wire suggested alerts.
- Enable CertOps - turn on the certificate operations layer with the
CERTOPS_ENABLEDflag.