Installation
Get TokenTimer Enterprise running from the delivery package. You do not need
to clone any repository or build any images; everything is delivered as
pre-built container images and an install bundle from the private TokenTimer
registry at harbor.tokentimer.ch.
What you receive from TokenTimer
When your enterprise license is issued, support@tokentimer.ch delivers a
single secure package containing:
| Item | File / format | Purpose |
|---|---|---|
| License | <your-id>.license.key (JWT) | Paste into .env as TT_LICENSE_KEY, import from the dashboard after boot, and/or mount as a file (see the Compose license file overlay below) |
| Registry robot credentials | <your-id>.creds.txt | Pulls images and the Helm chart from harbor.tokentimer.ch; details in Registry access |
| Install bundle | tokentimer-enterprise-<version>.tar.gz | The delivery tarball: Compose file, env template, Helm value examples, and documentation |
The install bundle (also called the delivery tarball) contains everything you need to deploy. For upgrades, you can either request a refreshed package or pull the latest bundle yourself from Harbor (see Upgrading).
Prerequisites
| Path | Requirements |
|---|---|
| Docker Compose | Docker 24+ with the Compose plugin |
| Kubernetes (Helm) | Helm 3.14+, Kubernetes 1.29+, and the CloudNativePG operator if you use the bundled PostgreSQL |
| All paths | Outbound HTTPS to harbor.tokentimer.ch:443 from any node that will pull images or charts |
You do not need Node.js, pnpm, source code, or any local image build.
Step 0: Authenticate to the TokenTimer registry
Registry authentication covers image pulls, the Helm chart, and (optionally)
future bundle versions. Your robot username and token are in the
<your-id>.creds.txt delivery file; see Registry access
for the credential format, scope, and rotation.
docker login harbor.tokentimer.ch \
-u 'robot$tokentimer-enterprise+<company>-<your-id>' \
-p '<your-robot-token>'
For Kubernetes, you also create a pull secret in the install namespace (see Option 2).
Step 1: Extract the install bundle
mkdir tokentimer && cd tokentimer
tar -xzf /path/to/tokentimer-enterprise-<version>.tar.gz
ls
You should see: QUICKSTART.md VERSION LICENSE compose/ helm/ docs/
The bundle contains:
| Path | Contents |
|---|---|
VERSION | Bundle version, build time, source commit |
QUICKSTART.md | Offline copy of the install walkthrough |
LICENSE | BSL 1.1 product license terms |
compose/docker-compose.yml | Production Docker Compose file |
compose/docker-compose.license-file.yml | Optional bind-mount helper for a file-based license JWT |
compose/docker-compose.sso.yml | Optional SSO overlay (bootstrap manifest and keyring) |
compose/.env.example | Environment template (full reference) |
compose/.env.sso-basic.example | SSO secret env var template (OIDC and SAML examples) |
compose/sso-providers.README.md and sso-providers.*.example.json | SSO bootstrap manifest reference and examples |
helm/README.md, helm/values.yaml, helm/examples/ | Chart install guide, enterprise chart defaults, and scenario starter values files |
docs/ | Offline copies of the configuration, authentication, architecture, registry, and SSO guides, plus a docs/core/ subfolder with the Core references (database, SMTP, alerts) |
Option 1: Docker Compose (fastest)
a. Configure .env
cd compose
cp .env.example .env
Minimum required edits:
SESSION_SECRET=replace_with_a_long_random_value # openssl rand -base64 32
DB_PASSWORD=replace_with_secure_password
ADMIN_EMAIL=admin@your-company.com
ADMIN_PASSWORD=ChangeThisSecurePassword123!
APP_URL=https://tokentimer.your-domain.com
API_URL=https://tokentimer.your-domain.com
If you run on plain http://localhost, set
SESSION_COOKIE_SECURE_LOCALHOST_OVERRIDE=true so the browser persists the
session cookie. Do not enable this in production. Put HTTPS in front instead.
For every other variable (SMTP, workers, proxy hops, license grace period),
see the Configuration reference and the annotated
compose/.env.example in the bundle.
b. Supply the license (pick one or combine)
- Set
TT_LICENSE_KEYin.envto the raw JWT (recommended for automation), and/or - Sign in after first boot and import the JWT under System Settings > License, and/or
- For a file on disk: copy
<your-id>.license.keytocompose/license.keyand start with the optional overlay file:
docker compose -f docker-compose.yml -f docker-compose.license-file.yml up -d
If you use only the dashboard import, you can start with plain
docker compose -f docker-compose.yml up -d and no license file on disk. How
the three sources are resolved against each other is documented in the
Configuration reference.
c. Start the stack
docker compose -f docker-compose.yml up -d
Compose pulls these images from harbor.tokentimer.ch/tokentimer-enterprise/
automatically:
tokentimer-enterprise-api:0.8.2(also runs the one-shot database migrations job)tokentimer-enterprise-worker:0.8.2(5 worker services: discovery, delivery, weekly digest, auto-sync, endpoint check)tokentimer-enterprise-dashboard:0.8.2
d. Verify
curl http://localhost:4000/health
open http://localhost:5173
You should see: the health endpoint responding, and the dashboard login page in the browser.
Log in with ADMIN_EMAIL / ADMIN_PASSWORD. Auto-sync providers (AWS,
Azure, GCP, Vault) appear active in the import modal once your license
entitlements load. If a provider stays greyed out, open
System Settings > License (signed-in admins only; GET /api/license/status
is not available without a session).
Option 2: Kubernetes (Helm)
The enterprise Helm chart is published as an OCI artifact in Harbor and
bundles the core chart as a vendored subchart. You do not need a separate
tokentimer-core install.
The bundle's helm/values.yaml documents every chart option, and
helm/examples/ contains scenario starter files (see the
table below).
a. Create the pull secret
kubectl create namespace tokentimer
kubectl create secret docker-registry tokentimer-enterprise-registry \
--docker-server=harbor.tokentimer.ch \
--docker-username='robot$tokentimer-enterprise+<company>-<your-id>' \
--docker-password='<your-robot-token>' \
-n tokentimer
The pull secret name matches the chart default (global.imagePullSecrets).
If you choose a different name, override via
--set tokentimer.global.imagePullSecrets[0].name=<your-name>. More detail,
including GitOps-friendly manifest generation, is in
Registry access.
b. Authenticate Helm to Harbor
The Helm CLI needs Harbor credentials to fetch the chart (the pull secret from step a only covers container-image pulls by the cluster, not the chart pull by the Helm client):
helm registry login harbor.tokentimer.ch \
-u 'robot$tokentimer-enterprise+<company>-<your-id>' \
-p '<your-robot-token>'
c. Create the license secret (optional)
You can supply the license JWT via TT_LICENSE_KEY in values, via the
dashboard after boot, and/or via a file mounted from a Kubernetes Secret. The
chart treats the license volume as optional when no Secret exists.
File-based install:
kubectl create secret generic tokentimer-license \
--from-file=license.key=/path/to/<your-id>.license.key \
-n tokentimer
If you rely on env or UI only, skip this step and omit
--set license.existingSecret=... on install (or set license.existingSecret
to "" in values if you overrode it).
d. Pick a starter values file and customize
cp helm/examples/values-external-db.yaml ./my-values.yaml
$EDITOR my-values.yaml
Set public URLs before SSO or production use. Chart defaults leave
localhost origins in place; if you skip this step, Manage SSO providers
will show redirect URIs such as http://localhost:4000/auth/oidc/<slug>/callback.
In my-values.yaml, under the core subchart key tokentimer.config (same
names as Compose APP_URL / API_URL):
tokentimer:
config:
# Dashboard origin (maps to APP_URL): links in email, where users open the UI
baseUrl: https://tokentimer.your-domain.com
# API origin (maps to API_URL): must be the URL browsers and your IdP use to reach the API
apiUrl: https://tokentimer.your-domain.com
Use the same host when ingress terminates TLS on one hostname for both UI and
API. Use separate hosts if you split them (for example
baseUrl: https://app.example.com, apiUrl: https://api.example.com) and set
SESSION_COOKIE_DOMAIN to the parent domain on the API via Helm extra env or
your values overlay. Do not use in-cluster DNS names (tokentimer-api, pod
IPs) here. See the
Canonical URLs section of the Configuration reference.
SSO callback paths default to ${apiUrl}/auth/oidc/<slug>/callback and
${apiUrl}/auth/saml/<slug>/callback (slug from your provider row). If the
API sits behind an ingress or reverse proxy, set trustProxyHops (top-level
chart value, maps to TRUST_PROXY_HOPS) so session cookies and redirects
respect X-Forwarded-* headers.
e. Install
Helm 3.8+ installs straight from the OCI registry; no intermediate
helm pull is needed.
helm install tokentimer oci://harbor.tokentimer.ch/tokentimer-enterprise/charts/tokentimer-enterprise \
--version 0.8.2 \
-n tokentimer \
-f my-values.yaml \
--set license.existingSecret=tokentimer-license \
--set tokentimer.config.adminEmail=admin@your-company.com
If you are not using a license Secret, drop license.existingSecret and set
TT_LICENSE_KEY under tokentimer.api.env (or paste the license in the UI
after install).
For air-gapped clusters, change-review workflows, or inspecting every chart
value, pull the chart locally first with
helm pull oci://harbor.tokentimer.ch/tokentimer-enterprise/charts/tokentimer-enterprise --version 0.8.2,
then install from the resulting .tgz. The full schema comes from
helm show values ./tokentimer-enterprise-0.8.2.tgz; the bundle's
helm/values.yaml and example files alone are not enough for that. See
Registry access.
envFrom and license volume names are derived from the Helm release name
(<release>-enterprise-env, and so on). See helm/README.md in the bundle
(section: Enterprise env wiring).
f. Verify
kubectl get pods -n tokentimer -w
You should see: API, dashboard, and worker pods reach Running.
Then open the dashboard at your ingress URL (tokentimer.config.baseUrl). If
you have no ingress yet:
kubectl port-forward -n tokentimer svc/tokentimer-dashboard 8080:80 &
Open http://localhost:8080 and log in with your admin credentials (retrieve
the password from the install secret if Helm auto-generated it; see
helm/README.md). Confirm System Settings > License and that import
providers match your entitlements.
Optional API smoke check (does not validate the license; use /health only):
kubectl port-forward -n tokentimer svc/tokentimer-api 4000:4000 &
curl http://localhost:4000/health
Scenario values files
The bundle ships starter values files under helm/examples/:
| File | Use case |
|---|---|
values-minimal.yaml | Single-node CNPG, no ingress |
values-external-db.yaml | External PostgreSQL + ingress + SMTP |
values-full.yaml | Production-shaped overlay: external DB, ingress, TLS, monitoring (not every chart key; pull the chart from Harbor and run helm show values on the .tgz, or helm get values -a on a live release) |
values-full-test.yaml | Lab / Minikube with HPA, metrics, all workers |
values-sso-basic-oidc.yaml | Single OIDC provider bootstrap |
values-sso-basic-saml.yaml | Single SAML provider bootstrap |
values-sso-multi-provider.yaml | Entra OIDC + Keycloak OIDC + Keycloak SAML |
values-sso-full-reference.yaml | Every supported ssoProviders[] field (parameter catalog) |
License lifecycle
The license JWT drives feature gating at runtime:
| State | Behavior |
|---|---|
| Valid license | Enterprise features enabled, bell icon clean |
| Grace period (default 30 days past expiry) | Features still work, responses carry the X-License-Warning header, bell icon shows yellow |
| Past grace | SSO endpoints redirect to /login?error=license_<provider>_required, auto-sync POST/PUT/run return 403; existing configs can still be read and deleted (cleanup is allowed) |
Admins can also import or remove a license JWT from
System Settings > License at runtime. Database-stored licenses take
precedence over the mounted file. Resolution order and the grace period
variable (TT_LICENSE_GRACE_DAYS) are documented in the
Configuration reference.
Upgrading
Each release ships as: new image tags in Harbor, a new Helm chart version in
Harbor, and a new install bundle in Harbor. Replace NEXT_VERSION below with
the version you are upgrading to. The bundle VERSION file records the
version you extracted; TokenTimer support will confirm the target release
when you renew.
Pull the new bundle (optional, recommended)
The bundle is published as a generic OCI artifact, pulled with ORAS (a single static binary):
oras login harbor.tokentimer.ch -u '<robot>' -p '<token>'
NEXT_VERSION=<paste here>
oras pull harbor.tokentimer.ch/tokentimer-enterprise/bundles/tokentimer-enterprise:${NEXT_VERSION}
tar -xzf tokentimer-enterprise-${NEXT_VERSION}.tar.gz
Docker Compose
cd compose
# Bump TT_ENTERPRISE_TAG in .env if you pin versions, then:
docker compose -f docker-compose.yml pull
docker compose -f docker-compose.yml up -d
Helm
NEXT_VERSION=<paste here>
helm upgrade tokentimer oci://harbor.tokentimer.ch/tokentimer-enterprise/charts/tokentimer-enterprise \
--version ${NEXT_VERSION} \
-n tokentimer \
-f my-values.yaml
Re-check tokentimer.config.baseUrl and tokentimer.config.apiUrl in
my-values.yaml after upgrade if you use SSO; chart defaults are
localhost-oriented and are not auto-corrected from ingress hostnames.
Config changes in my-values.yaml (URLs, OIDC/SAML env, SMTP, and so on)
take effect after the upgrade rolls the API and dashboard pods. The chart
annotates Deployments so helm upgrade triggers that rollout automatically.
Editing a running ConfigMap with kubectl alone does not restart pods.
Registry token renewal
Your robot account expires at the same time as your license. When you renew,
you receive a new robot token and must update your Docker login and the
Kubernetes pull secret. Existing pods continue running with their cached
image layers; only the next helm upgrade or docker compose pull
re-authenticates. See
Rotating credentials at renewal.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
unauthorized: authentication required on docker pull | Token expired or wrong robot prefix | Confirm the full username (robot$tokentimer-enterprise+<company>-<id>) and re-login; see Registry access |
| API fails at startup with a license error | Invalid or corrupt license JWT | Re-import from System Settings > License or fix TT_LICENSE_KEY / the mounted license file |
Login succeeds but immediately redirected to /login | Cookie blocked because Secure is set on plain HTTP | Either run behind HTTPS or set SESSION_COOKIE_SECURE_LOCALHOST_OVERRIDE=true for local testing |
| AWS / Azure / GCP / Vault tiles greyed out in the import modal | License missing the entitlement | Log in and check System Settings > License (or request a re-issued license) |
/auth/oidc/<slug> redirects to /login?error=license_oidc_required | License does not include feature:oidc | Same as above |
ImagePullBackOff in Kubernetes | Pull secret missing or in the wrong namespace | See Registry access, troubleshooting |
SSO redirect URI shows http://localhost:4000/... | tokentimer.config.apiUrl left at chart default | Set apiUrl (and baseUrl) to your public HTTPS origins, run helm upgrade, restart the API if you only changed bootstrap secrets outside Helm |
Next steps
- Configuration reference: enterprise environment variables, license resolution, and the SSO secrets keyring.
- SSO overview: pick an identity provider and register callback URLs.
- OIDC quickstart and SAML quickstart: end-to-end SSO setup for Compose and Helm, including the bootstrap manifest and keyring.
- Authentication model: admin bootstrap modes, RBAC, and audit logging.
- Architecture overview: components, data flow, and network requirements.