Skip to content
Operations · Self-host Intermediate

Renew a license without a restart

An Enterprise license reaches a cell either inline or as a file, and only the file can be renewed in place - how to set that up on the server compose and on Kubernetes, what the cell does with a half-written token, and why term-length contracts make it worth the trouble.

SLSluicio team 6 min read Updated Sep 2026

An Enterprise license reaches a cell one of two ways, and only one of them can be renewed in place.

How the token arrivesRenewing it
SLUICIO_LICENSE_KEYInline, in the environmentEdit the environment, recreate the container
SLUICIO_LICENSE_FILEA path to a file holding the tokenCopy the new file over the old one

SLUICIO_LICENSE_KEY is read once at startup, so replacing it means editing the environment and recreating the container. cell-api runs as a single replica, so that is a short outage of the UI and of alert evaluation.

SLUICIO_LICENSE_FILE points at a file, and cell-api re-reads that file on an interval (LICENSE_RELOAD_INTERVAL, default 1m). Renewing is a file copy, and nothing restarts.

A license issued for the contract period is renewed as often as the contract runs. A quarterly agreement is four renewals a year, per customer, each one landing on a machine you may not control. Minting the key takes seconds; arranging a restart window with the customer’s operations team does not.

If your licenses are perpetual, the inline variable is fine and there is nothing here you need.

Point the two variables at a directory on the host:

.env
LICENSE_DIR=/opt/sluicio/license
SLUICIO_LICENSE_FILE=/etc/sluicio/license/license.key

LICENSE_DIR is the host directory; the compose file mounts it read-only at /etc/sluicio/license, which is where SLUICIO_LICENSE_FILE then finds the token. Put the key in it as license.key and start the cell as usual.

It is a directory rather than a single file on purpose. A bind mount whose source file does not exist yet is silently created as a directory, and the container then sees a directory where it expected a token - a confusing failure at the exact moment you are trying to install a license for the first time. Mounting the parent avoids it.

Renewing is then one command, with no restart and no downtime:

Terminal window
cp license.key /opt/sluicio/license/license.key

Within the reload interval the cell logs license reloaded with the new customer, plan and expiry. You can confirm the same from the API:

Terminal window
curl -s https://sluicio.example.com/api/v1/license

Every failure keeps the license already in force. A file that is missing, empty or half-written is a copy in progress, not a statement that the customer stopped being entitled - and disabling their Enterprise features over a partial cp would be an outage you caused yourself. A token signed by the wrong key is rejected the same way: it is refused, and the running license stays.

A file that stays broken is retried on every interval, which is what makes correcting it take effect without a restart. It is reported only when the problem changes, so one bad token does not write the same warning every minute for as long as the cell runs.

A license that was already invalid when the cell started is retried too, so fixing a typo in the very first install does not need the restart this exists to avoid.

The Helm chart does the same thing through license.mountAsFile:

license:
existingSecret: sluicio-license
mountAsFile: true
reloadInterval: "1m"

The token is now mounted rather than injected, so updating the Secret is the renewal:

Terminal window
kubectl create secret generic sluicio-license \
--from-literal=license=<new-token> \
--dry-run=client -o yaml | kubectl apply -f -

The kubelet refreshes a mounted Secret on its own (up to about a minute) and cell-api re-reads the file on license.reloadInterval. An environment variable, by contrast, never changes in a running container - which is the whole reason the mounted form exists.

The mount deliberately does not use subPath. A subPath mount is copied once and never refreshed, so it would silently reintroduce the restart this is here to avoid.