Skip to main content
Version: next

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:

ItemFile / formatPurpose
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.txtPulls images and the Helm chart from harbor.tokentimer.ch; details in Registry access
Install bundletokentimer-enterprise-<version>.tar.gzThe 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

PathRequirements
Docker ComposeDocker 24+ with the Compose plugin
Kubernetes (Helm)Helm 3.14+, Kubernetes 1.29+, and the CloudNativePG operator if you use the bundled PostgreSQL
All pathsOutbound 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:

PathContents
VERSIONBundle version, build time, source commit
QUICKSTART.mdOffline copy of the install walkthrough
LICENSEBSL 1.1 product license terms
compose/docker-compose.ymlProduction Docker Compose file
compose/docker-compose.license-file.ymlOptional bind-mount helper for a file-based license JWT
compose/docker-compose.sso.ymlOptional SSO overlay (bootstrap manifest and keyring)
compose/.env.exampleEnvironment template (full reference)
compose/.env.sso-basic.exampleSSO secret env var template (OIDC and SAML examples)
compose/sso-providers.README.md and sso-providers.*.example.jsonSSO 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
warning

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_KEY in .env to 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.key to compose/license.key and 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.

info

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).

info

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/:

FileUse case
values-minimal.yamlSingle-node CNPG, no ingress
values-external-db.yamlExternal PostgreSQL + ingress + SMTP
values-full.yamlProduction-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.yamlLab / Minikube with HPA, metrics, all workers
values-sso-basic-oidc.yamlSingle OIDC provider bootstrap
values-sso-basic-saml.yamlSingle SAML provider bootstrap
values-sso-multi-provider.yamlEntra OIDC + Keycloak OIDC + Keycloak SAML
values-sso-full-reference.yamlEvery supported ssoProviders[] field (parameter catalog)

License lifecycle

The license JWT drives feature gating at runtime:

StateBehavior
Valid licenseEnterprise 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 graceSSO 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.

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

SymptomLikely causeFix
unauthorized: authentication required on docker pullToken expired or wrong robot prefixConfirm the full username (robot$tokentimer-enterprise+<company>-<id>) and re-login; see Registry access
API fails at startup with a license errorInvalid or corrupt license JWTRe-import from System Settings > License or fix TT_LICENSE_KEY / the mounted license file
Login succeeds but immediately redirected to /loginCookie blocked because Secure is set on plain HTTPEither run behind HTTPS or set SESSION_COOKIE_SECURE_LOCALHOST_OVERRIDE=true for local testing
AWS / Azure / GCP / Vault tiles greyed out in the import modalLicense missing the entitlementLog in and check System Settings > License (or request a re-issued license)
/auth/oidc/<slug> redirects to /login?error=license_oidc_requiredLicense does not include feature:oidcSame as above
ImagePullBackOff in KubernetesPull secret missing or in the wrong namespaceSee Registry access, troubleshooting
SSO redirect URI shows http://localhost:4000/...tokentimer.config.apiUrl left at chart defaultSet apiUrl (and baseUrl) to your public HTTPS origins, run helm upgrade, restart the API if you only changed bootstrap secrets outside Helm

Next steps