Reference
On-Prem Licensing
Enterprise plans can run the full LiveXFace stack on your own infrastructure. On-prem deployments are licensed: the server verifies a cryptographically signed license file at startup and on every request. This guide covers activation (online and air-gapped), license management with frctl, and troubleshooting.
How licensing works
- Your license is a signed token verified offline against a public key embedded in the binary — no internet connection is required to run.
- The license is bound to a node id that is generated once and persisted in the
/datavolume. Keep that volume — wiping it creates a new node id, which consumes another seat. - Seats (
max_instances) count logical deployments, not pods or replicas. - After the license term ends there is a short read-only grace window (default 14 days): reads keep working and responses carry an
X-License-Status: graceheader, writes are rejected. After the grace window the API fails closed with403 LICENSE_INVALID./healthalways stays up.
What you receive
After an Enterprise (on-prem) purchase, the organization owner receives an activation key by email — it looks like LXF-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX. The key is shown only once; store it in your secret manager. It is exchanged for the actual license during activation.
Option A — Online activation (recommended)
If the deployment host can reach the licensing server, the stack activates itself on first boot. Configure the API container:
# .env (API service)
LICENSE_ENFORCED=true
ACTIVATION_KEY=LXF-... # from the purchase email
LICENSE_SERVER_URL=https://api.livexface.com
LICENSE_DATA_DIR=/data # must be a persistent volume
docker compose -f docker-compose.onprem.yml up -dOn first run the server generates its node id, exchanges the activation key for a signed license, and caches it at /data/license.jwt. Subsequent restarts use the cached license and work fully offline.
Option B — Air-gapped activation
For isolated environments, activation happens through the web portal from any machine with internet access:
# 1. On the isolated host: print the node fingerprint
docker compose exec api /app/frctl license fingerprint -data /data
# → { "node_id": "nid_...", ... }
# 2. On any online machine: open the activation portal
# https://livexface.com/activate
# Paste the activation key + node_id → download license.jwt
# 3. Back on the isolated host: install and verify
docker compose exec api /app/frctl license install -token /path/to/license.jwt
docker compose exec api /app/frctl license status
# → STATUS: OK (tier, expiry, days remaining)The frctl utility
frctl ships inside the API image at /app/frctl and never needs the server running:
frctl license fingerprint— print this node's id and host info (for portal activation).frctl license install -token <file-or-jwt>— cache a license obtained from the portal.frctl license status— verify the cached license:OK,GRACE, orINVALIDwith the reason, plus customer, tier, and expiry.
Heartbeat & usage telemetry
Online installations check in with the licensing server every 6 hours (configurable via LICENSE_HEARTBEAT_HOURS). The heartbeat confirms the license has not been revoked and reports a single aggregate number — lifetime faces processed — used for renewal true-ups. Air-gapped installations never heartbeat and are never penalized for being offline; a temporary network outage only logs a warning and is retried.
Renewal
Renewing issues a new license file. Online installs pick it up by re-activating (delete /data/license.jwt and restart, or set a fresh ACTIVATION_KEY); air-gapped installs repeat the portal flow with the same node id — re-activation of the same node never consumes an extra seat.
Troubleshooting
- 403 LICENSE_INVALID on every request — run
frctl license status. Common causes: no license installed, expired beyond grace, or the license is bound to a different node id (the/datavolume was recreated). - SEAT_LIMIT_REACHED at activation — all seats are in use. Deactivate an old deployment or contact us to add seats. Re-activating the same node is always allowed.
- license not yet valid — the host clock is behind. Sync NTP and restart.
- Lost activation key — contact support; we can rotate it (the old key stops working).
Installing (air-gapped) from an image bundle
Air-gapped hosts cannot pull images. We ship a single tarball containing both images; load it, then start the stack:
# verify integrity, then load both images into the local daemon
shasum -a 256 -c livexface-onprem-<version>.tar.gz.sha256
docker load -i livexface-onprem-<version>.tar.gz
# configure and start (set VERSION=<version> + secrets in .env)
cp .env.example .env
docker compose -f docker-compose.onprem.yml up -dUpgrading to a new version
Upgrades are safe as long as the license_data volume is preserved — it holds your node id and license, so the deployment stays activated across versions.
# 1. Back up first (see below).
# 2. Load the new images (online: docker pull; air-gapped: docker load -i ...).
# 3. Point .env at the new VERSION, then recreate — migrations run automatically
# via the one-shot 'migrate' service on startup:
docker compose -f docker-compose.onprem.yml up -d
# 4. Verify health and license:
curl -fsS http://localhost:8080/health
docker compose -f docker-compose.onprem.yml exec api /app/frctl license status -data /dataDatabase migrations are forward-only and idempotent. Never delete the license_data volume during an upgrade — a fresh node id consumes another seat and forces re-activation.
Backup & restore
Three things carry state; back all three up on a schedule:
- PostgreSQL — collections, faces, API keys, usage. Logical dump:
# Backup
docker compose -f docker-compose.onprem.yml exec postgres \
pg_dump -U postgres livexface | gzip > backup-$(date +%F).sql.gz
# Restore (into a fresh, empty database)
gunzip -c backup-YYYY-MM-DD.sql.gz | \
docker compose -f docker-compose.onprem.yml exec -T postgres psql -U postgres livexface- Object storage (MinIO) — stored face images. Back up the
minio_datavolume (ormc mirrorto external storage). - License data — the
license_datavolume (node id + license). Losing it means re-activating; back it up so a host rebuild keeps the same seat.
Restore order after a host rebuild: restore volumes → restore the database → docker compose up -d → verify with frctl license status. If the license volume was lost, re-activate (online or via the portal) — this may consume a seat until the old activation is released.