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 /data volume. 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: grace header, writes are rejected. After the grace window the API fails closed with 403 LICENSE_INVALID. /health always 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:

Shell
# .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 -d

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

Shell
# 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, or INVALID with 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 /data volume 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:

Shell
# 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 -d

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

Shell
# 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 /data

Database 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:
Shell
# 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_data volume (or mc mirror to external storage).
  • License data — the license_data volume (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.