Set variables and secrets
Get configuration and credentials into a running service, and know when a change takes effect.
A variable is a named value Lessly passes into your container as an environment variable. Everything a service is configured with — the port it listens on, a connection string, a log level, an API key — is a variable, and variables are the only channel through which your configuration reaches a running container.
Choose the level
The same key can be defined at three levels. When a service is deployed, Lessly merges all three into one set, and the narrowest definition wins: a service-level value overrides an environment-level one, and an environment-level value overrides a product-level one. Define a shared value once at the widest level that is true, and override it narrowly where it is not.
| Level | Who gets it | Reach for it when |
|---|---|---|
| Product | Every service in every environment of the product. | The value is true everywhere — APP_NAME, an error-reporting DSN. |
| Environment | Every service in that one environment. | The value belongs to the environment — REDIS_URL, a feature flag. |
| Service (Recommended) | One service in one environment. | One service owns the value, or you are overriding a shared one — PORT, LOG_LEVEL, WORKER_CONCURRENCY. |
Precedence only applies to keys that are yours to define. The keys Lessly reserves for itself — every LESSLY_ key and PORT — cannot be defined at any of the three levels at all, so the question of which level wins never comes up for them. See Reserved keys.
Environments are otherwise isolated. An environment-level REDIS_URL in production and an environment-level REDIS_URL in staging are two separate records; changing one does not touch the other.
Set a variable
- Pick the level from the table above, then add the key and the value. A key is written in the usual environment-variable style — capital letters, digits and underscores, starting with a letter or an underscore, up to 128 characters.
DATABASE_URLandLOG_LEVELare valid;databaseUrland2ND_TRYare not. - Paste the value as-is. A value is a string of up to 256 KiB and may span several lines, so a PEM key or a JSON blob reaches the container with its line breaks intact.
- Mark it as a secret if the value is a credential. The value is encrypted and becomes write-only from that point.
- Redeploy the service. Adding, editing or deleting a variable does not restart anything on its own — the running container keeps its current values until the next deploy.
Listing a service’s variables shows the merged result, so you see what the service will actually run with rather than three lists to combine in your head. That merged list is Service → Variables in the Product App: an inherited row is tagged product or env and is edited where it is defined, which for an environment-level variable is Environment → Settings → Variables.
Through the Lessly MCP server, an agent reads and writes the same records: deployment_variable_list and deployment_variable_set at service level, deployment_variable_set_env and deployment_variable_set_product at the wider levels, deployment_variable_unset to remove one, and deployment_service_redeploy to apply the batch. Token scope: a key with write access to the product’s variables.
Secrets
Marking a variable as a secret encrypts its value. The interface shows it masked rather than in the clear, and listing variables returns the key with no value attached. You can overwrite a secret whenever you like, but you cannot read the old value back out of Lessly — keep your own copy of anything you cannot regenerate.
Two consequences are worth planning for.
| Behaviour | What it means for you |
|---|---|
| Marking a variable as a secret is irreversible. | There is no way to turn a secret back into a plain variable; the attempt is rejected. If you need the value visible again, delete the secret and create a plain variable in its place. |
| Secrets are not copied when an environment is forked. | Plain variables come along to the new environment at both environment and service level. Secrets are left behind entirely — not even the key is carried over. After a fork, set the secrets the new environment needs before you deploy into it. |
The second is deliberate: it keeps production credentials from spreading into short-lived copies by accident.
Refer to another variable
A value can point at another variable instead of repeating it. Write ${{KEY}} inside the value and Lessly substitutes the real string when it builds the release:
GRAPHQL_ENDPOINT=http://localhost:${{PORT}}/graphqlReferences may be combined with ordinary text and with each other, and a value may contain as many as you like. A reference can also point at a value published by a managed database in the same environment, written as the database’s slug, a dot, and the key:
DATABASE_URL=${{postgres.DATABASE_URL}}That is the intended way to wire a service to a managed database: you never handle the credentials yourself, and the next deploy picks up the current connection details. The database’s own page lists every key with the exact reference to copy — see Choose where state lives.
References are resolved before anything starts, and four situations stop the deploy rather than producing a broken container:
| The deploy is refused when | Fix |
|---|---|
| The reference points at a key that does not exist. | Define the key, or correct the reference. |
The references form a cycle — A needs B and B needs A. | Break the cycle. |
| The chain of references nests more than ten deep. | Flatten it. |
| A plain variable refers to a secret. | Mark the referring variable as a secret too, and it resolves normally. Resolving it otherwise would write the secret’s value into a non-secret entry. |
Variables Lessly sets for you
Every container also receives a small set of variables describing where it is running:
| Key | Value |
|---|---|
PORT | The container port configured on the service |
LESSLY_SERVICE_ID | The service being run |
LESSLY_ENVIRONMENT_ID | The environment it belongs to |
LESSLY_ENVIRONMENT_NAME | That environment’s slug, for example production |
LESSLY_PRODUCT_ID | The product the environment belongs to |
LESSLY_DEPLOYMENT_ID | The release currently running |
These are regenerated on every deploy and you cannot edit or delete them. PORT in particular always tracks the container port configured on the service — change the port setting, not the variable. You can refer to all six from your own values like any other variable, for example ${{PORT}}.
A service built from a git repository receives LESSLY_GIT_COMMIT_SHA and LESSLY_GIT_BRANCH as well, so a running container can report exactly which commit it was built from.
Those two are the one part of the set you cannot refer to with ${{ }}. The commit is not known until the build has finished, which is after references are resolved, so ${{LESSLY_GIT_COMMIT_SHA}} stops the deploy as a reference to a key that does not exist. Their values still reach the container like the rest.
Reserved keys
Lessly reserves a whole namespace, not just the keys it happens to inject today. Two things are reserved:
- every key beginning with
LESSLY_; - the exact key
PORT.
A variable with a reserved key cannot be created at any level — service, environment or product. The attempt is refused at the moment you save it, and the message names the key you tried to use:
variable LESSLY_REGION is reserved by the platform and cannot be set: the "LESSLY_" prefix is reserved for platform-injected variablesPORT is refused with its own reason, because there is a proper place to set it:
variable PORT is reserved by the platform and cannot be set: it is derived from the service containerPort — set the port on the service insteadThe match is case-sensitive, and only the beginning of the key counts. lessly_foo, port, MY_PORT and PORTAL_URL are ordinary keys and remain yours to use; only LESSLY_-prefixed keys and PORT itself are refused.
The deploy is the second line of defence, for anything that reached storage before the rule existed. A variable of your own on a reserved key never wins: Lessly’s value is what the container gets, yours is dropped from the release entirely, and a deployment.variable.ignored event is recorded on the deployment so the loss is visible in the Events feed. That covers the git provenance keys too — a container cannot be made to report a commit it was not built from.
When a change takes effect
At the moment a deploy starts, Lessly takes a snapshot: it merges the three levels, decrypts the secrets, resolves every reference, and freezes the result into that release.
- From then on the release is fixed. Editing a variable afterwards does not reach the running container, and neither does a change in something a reference points at — those wait for the next deploy.
- That is intentional. It lets you stage a batch of related configuration changes and apply them together in a single release rather than one restart at a time.
- A redeploy needs no new commit. It builds a release from the current source and the current variables.
A rollback does not undo a variable change. Rolling back restores the image, not the configuration. Variables are read fresh every time a release is built, so the rolled-back release runs with today’s values, not with the values that were in place when the original release was deployed. If a variable change is what broke you, change the variable back yourself and deploy.
Next steps
- Choose where state lives: provision a managed database and wire it with
${{slug.KEY}}instead of pasting credentials. - Run a command inside a service: check what the running process actually sees, with the service’s own variables.
- Watch a service: find the
deployment.variable.ignoredevent when a key of yours collided with one Lessly owns. - Roll back a deploy: promote the previous release when the image, not the configuration, is what broke.
- Understand how deployment works: the product, environment and service model these three levels sit on.