Skip to main content
Version: v4.12 Stable

How licensing works

vCluster Platform holds the license for a deployment. The license determines which features are enabled and sets limits on how many of certain resources you can create.

In the normal connected model, clusters hold no license of their own. They read a list of enabled features from the platform. Most licensing questions therefore come down to how the platform obtained its license and whether clusters can reach the platform to read features from it. A cluster can instead be given its own offline license key, which the sections below cover separately.

This page explains how that works, what each component caches, and how licensing degrades when it becomes unavailable. For the setup procedures themselves, see the air-gapped guides for an offline license key or an offline license server.

How the platform obtains its license​

The platform selects one of three licensing modes at startup, based on environment variables set on the platform deployment.

ModeEnabled bySource of the license
OnlineDefault, no configuration neededhttps://admin.loft.sh/license/v2
Offline license keyLICENSE_KEY environment variableThe key itself, a signed token verified locally against a built-in public key
Offline license serverLICENSE_SERVER environment variableAn in-cluster license server that serves the same API as the hosted one

Offline license key mode never contacts a license server. The platform decodes and verifies the key in-process, so use this mode when no egress is possible at all.

A fourth variable, LICENSE_TOKEN, doesn't change the mode. It supplies a static token that the platform sends when it requests a license, so the instance doesn't need interactive activation. The platform logs LICENSE_TOKEN env var is set, platform activation is not required at startup when it's present.

To find out which mode a running platform is in, see Inspect and debug licensing.

Instance identity​

In online and offline license server modes, the platform identifies itself to the license server with:

  • The UID of the kube-system namespace in the control plane cluster.
  • A short-lived token signed with the private key from the loft-cert Secret, sent along with that Secret's certificate.

This pairing ties a license to one platform instance. If the loft-cert Secret is lost, the platform generates a new identity and the existing license no longer matches it. Preserve that Secret across reinstalls and migrations. See Restore license for the backup and restore procedure.

The loft-license-cache Secret isn't part of the platform's identity. It holds a cached copy of the license response and the platform rebuilds it automatically, so it doesn't need to be migrated.

How clusters receive features​

In the normal connected model, a cluster reads its enabled features from the platform. The control plane detects which licensing mechanism is available at startup and selects it.

TypeDetected whenFeatures come from
OnlineA platform host and access key are both available, and FIPS mode is offThe platform's management API Features endpoint
OfflineA key is set but no platform hostA signed offline license key, verified locally
StandalonecontrolPlane.standalone.enabled is set, and no platform host and access key are available (see below)All Enterprise features stay enabled. Built-in allowances and Feature resources determine license warnings
In-cluster APIFeature resources exist in the control plane cluster and the cluster's control plane has RBAC to list themFeature resources in the control plane cluster
NoneNothing above is detectedNo Enterprise features are enabled

In production builds, the control plane evaluates these types in the order shown and selects the first match. It logs the selected type at startup, for example detected license type {"licenseType": "Online"}.

Online is the common case. By default, the control plane reads the platform connection from the vcluster-platform-api-key Secret in the cluster's host namespace. To point it at a different Secret, set platform.apiKey.secretName and platform.apiKey.namespace in vcluster.yaml.

Within whichever Secret applies, the control plane resolves the access key in this order.

  1. The LICENSE environment variable.
  2. The LOFT_PLATFORM_ACCESS_KEY environment variable.
  3. The only key in the Secret, if the Secret holds exactly one key.
  4. The license key in the Secret, then the accessKey key.

It resolves the host from LOFT_PLATFORM_HOST first, then from the Secret's host key.

Use an address the pod can reach

The host recorded in the Secret must be reachable from inside the control plane cluster. Logging in through a port-forward records a localhost address that the cluster's control plane pod can't reach, so feature retrieval fails. See Resolve Pro feature license errors.

Two cases trip people up when the detected type isn't what they expected.

FIPS mode is the first. With FIPS enabled, a host and access key don't select the online type, and detection falls through to the remaining rows. A cluster that looks correctly configured can land on standalone, in-cluster API, or none. Check the logged type before assuming the credentials are at fault.

The second is that the standalone deployment mode and the standalone license type aren't synonymous. With FIPS off, a standalone deployment configured with a platform host and access key selects the online type first. It uses the non-expiring file cache described below.

The standalone type also doesn't disable Enterprise features. It allows the standalone and Private Nodes features by default. For other Enterprise features, Feature resources in the standalone cluster determine whether kubectl requests receive an unlicensed-use warning.

Required network access​

Only the platform contacts the license server. Neither connected cluster agents nor clusters do.

SourceDestinationProtocol and portPurpose
Platform podsadmin.loft.shHTTPS, TCP 443License retrieval every 6 hours and usage snapshots every 15 minutes
Platform podsAnalytics endpoint from the license responseAs specified by the endpoint URLAnalytics every 30 minutes when there has been activity
Cluster control plane podsPlatform endpointHTTPS, TCP 443Feature retrieval and registration
Connected cluster agentsNone for licensingNot applicableAgents don't perform license validation

In offline license key mode, no egress to admin.loft.sh is required at all. For license retrieval in offline license server mode, the platform reaches the in-cluster server at the address given in LICENSE_SERVER.

Analytics calls go to the exact endpoint URL named in the license response, which isn't necessarily admin.loft.sh. They only happen when there has been activity since the last call. A response with no analytics endpoint stops them entirely.

The platform Service listens on port 443 and forwards to port 10443 on the platform pods. Kubernetes NetworkPolicy matches the pod port rather than the Service port, so an egress rule targeting platform pods directly has to allow 10443.

The cluster path is the same connection used for the rest of platform communication, so it needs no licensing-specific firewall rules. For the full connectivity model, see Platform communication and connectivity.

Cache and refresh behavior​

The online platform and cluster paths cache their last successful results. These caches keep a temporary licensing outage from immediately taking those components down.

ComponentRefresh triggerCache locationCache lifetime
Platform in online or license server modeEvery 6 hoursloft-license-cache Secret in the platform namespaceReloaded at startup, with a 7-day grace period after retrieval starts failing
Platform in offline license key modeRe-evaluated locally every minuteNone needed, the key is in the environmentUntil the key expires, plus a 30-day grace period
Cluster, onlineAt startup, and whenever the credentials Secret changes<vcluster-name>-features-list Secret in the host namespace, encrypted7 days
Standalone deployment, online typeAt startup, and whenever the credentials Secret changesplatform/feature-cache.json in the data directoryDoesn't expire
Standalone typePolled with backoff starting at 30 seconds, capped at 5 minutesNoneNot applicable
Cluster, in-cluster APIPolled with backoff starting at 30 seconds, capped at 5 minutesNoneNot applicable
Cluster, offlineRe-checked locally every hourNone needed, the key is in the environmentUntil the key expires

The online cluster path isn't polled. The control plane retrieves features at startup and then only when the credentials Secret changes. A license change made in the platform reaches a running cluster on the next control plane restart, not on a timer.

The platform's one-minute cycle in offline license key mode doesn't contact anything. It re-reads current resource usage, so limits are enforced against live counts.

The cluster feature cache is encrypted with a key derived from the vCluster Service UID. A tampered Secret therefore can't grant features the platform never returned.

What happens when licensing is unavailable​

Behavior depends on which component lost access and for how long.

Component / scenarioWhat happens
The platform can't reach the license serverKeeps running on the cached license and retries every 6 hours. Exits if no retrieval succeeds within 7 days of the last success (a restart mid-outage gets up to 6 hours less, since the measurement starts from a trailing timestamp). A platform that has never retrieved a license has nothing to fall back on, so it exits immediately rather than starting unlicensed.
The platform's offline license key is near or past expiryRaises an expiration warning starting 90 days before expiry. After it expires, a 30-day grace period follows during which the platform keeps working, then the instance blocks all create requests once that period ends.
The platform is over a resource limitBlocks creating more of that resource until usage drops below the limit; deleting enough of the resource clears the block automatically. In offline license key mode, the platform evaluates limits itself and warns once usage passes 90%. In online mode, the license server decides which limits apply and which announcements to return.
A cluster on the online type can't reach the platformA control plane that's already running is unaffected, since the online path holds its feature set in memory and doesn't poll. At startup or a credentials-triggered reload, it tries the platform first and falls back to the cache. A cache older than 7 days, or a control plane that has never retrieved features at all, both fail initialization rather than starting unlicensed.
A cluster on the standalone or in-cluster API type can't reach the platformUnaffected, since these types read Feature resources from the control plane cluster rather than from the platform. What changes their feature set is the platform updating those resources, which a disconnected platform stops doing. On the standalone type, Enterprise features stay enabled either way, so only the unlicensed-use warning changes.
A cluster's own offline license key expiresThe control plane warns for the last 30 days, then exits once the key expires. This path has no grace period, unlike the platform's offline key mode.
The licensed feature set changesThe platform restarts itself to apply the new set.

Expiration, limit, and feature warnings surface in the platform UI. See Inbox.

Inspect and debug licensing​

In the platform​

View the current license in the platform UI under Admin > Platform Config > License and Billing.

Retrieve the same data through the API. This reports the license the platform currently holds, without contacting the license server.

Read the platform's current license
kubectl get licenses.management.loft.sh license -o yaml

A license the platform holds shows a non-empty status.license.entity (your organization's name) and a status.license.plans list. An empty or missing status.license means the platform has never successfully retrieved one.

To make the platform retrieve a fresh license first, add the extended=true query parameter. It's a URL parameter rather than a field, so kubectl get can't set it and the request has to go through --raw.

Force a license refresh, then read the result
kubectl get --raw '/apis/management.loft.sh/v1/licenses/license?extended=true'

The response is the same License object as above, so check it against the same entity and plans fields. If the platform can't reach the license server, this request fails with an error instead of returning the cached license. Drop extended=true and re-run the first command to read the cached license without forcing a refresh.

Confirm which license server the platform is using. The platform logs this once at startup, so this also tells you which mode it's in.

Check the configured license server
kubectl logs -n vcluster-platform -l app=loft | grep "Using license server"

This logs once per process, on every mode, with the server it's using as the endpoint field, for example admin.loft.sh in online mode or the in-cluster address in offline license server mode. No match usually means the platform pod restarted since and rotated the line out of its log buffer, not that the platform failed to start.

Check the cached license and the instance identity Secrets.

Inspect licensing Secrets
kubectl get secret loft-license-cache -n vcluster-platform -o jsonpath='{.data.updatedAt}' | base64 -d
kubectl get secret loft-cert -n vcluster-platform

Use updatedAt to confirm the cache exists, not to work out how much grace period is left. The platform writes the timestamp it held before the current retrieval, so the stored value trails the real one by a refresh cycle. After the very first write it reads 0001-01-01T00:00:00Z, because there was no previous retrieval to record. A missing loft-cert Secret means the platform generated a new identity, as described in Instance identity above, and any existing license no longer matches it.

Grace period is measured from the stored value

On restart, the platform reloads updatedAt and measures the 7-day grace period from it. Because the value trails, a restart during a license server outage gets up to 6 hours less grace than the constant implies. A 0001-01-01T00:00:00Z value is handled safely. The platform treats it as unset and falls back to the Secret's creation timestamp, so the grace period still runs from roughly the first successful retrieval. Tracked as ENGPLAT-1069.

Look for retrieval failures in the platform logs. Each of these lines carries a full stack trace in an errorVerbose field, so truncate them to keep the output readable.

Check for license retrieval errors
kubectl logs -n vcluster-platform -l app=loft | grep -i "error retrieving license" | cut -c1-260

An occasional failure here normally needs no action. The platform keeps serving from its cached license and retries on the next cycle. Outside the restart and timestamp edge cases above, only failures spanning more than 7 days make the platform exit.

In a cluster​

If you've just fixed credentials or the platform now grants a different feature set, restart the control plane before checking anything below. The online path only re-reads features at startup or when the credentials Secret changes, not on a timer, so a fix that's correct won't show up until then.

Confirm which license type the control plane selected, and which features it enabled.

Modify the following with your specific values to generate a copyable command:
Check the cluster's license type and features
kubectl logs -n my-namespace my-vcluster-0 | grep -A5 "detected license type\|Enabled features"

A healthy online cluster logs the host it connected to, followed by the features it enabled.

Startup log on a licensed control plane
loader/loader.go Using online license with host loft.vcluster-platform
online/license.go Enabled features:
online/license.go dra-sync

Confirm the credentials Secret and the feature cache both exist.

Modify the following with your specific values to generate a copyable command:
Verify the credentials Secret and feature cache
kubectl get secret vcluster-platform-api-key -n my-namespace
kubectl get secret my-vcluster-features-list -n my-namespace

A NotFound error on the credentials Secret means the cluster was never connected to the Platform, or points at a different Secret name or namespace than configured. The feature cache is encrypted and can't be read directly, so its presence and age are what matter rather than its contents: a missing cache means the control plane has never successfully retrieved features, and the cache expires 7 days after it was last written.

A control plane can crashloop with an error naming a feature the license doesn't allow. The cause is usually a missing credentials Secret rather than a plan limitation. See Resolve Pro feature license errors.