Environment variables
The agent loads a .env file from its working directory at startup, before
anything else, so these can live there or in a systemd EnvironmentFile.
A flag always wins over its environment variable.
Security
| Variable | Default | Effect |
|---|---|---|
KEYSTONE_API_TOKEN | — | Bearer token for the HTTP API. Required for a non-loopback bind |
KEYSTONE_TRUST_BUNDLE | — | PEM file of CAs used to verify artifact and recipe signatures |
KEYSTONE_LEAF_CERT | — | Provisioned signing leaf certificate |
KEYSTONE_INSECURE_SKIP_VERIFY | false | Disables mandatory artifact integrity. Development only |
KEYSTONE_ALLOW_NO_EKU_SIGNERS | false | Transition only: accepts signing certificates with no extended key usage. Also honoured by keystonectl verify and by the self-update gate (--verify-update), which reads only the variable, not the flag |
KEYSTONE_MAX_REQUEST_BYTES | 4 MiB | HTTP request body cap (413 on overflow) |
KEYSTONE_MAX_EXTRACT_BYTES | 2 GiB | Cap on decompressed archive size, so a small archive cannot fill the disk |
Artifacts
| Variable | Default | Effect |
|---|---|---|
KEYSTONE_ARTIFACT_CACHE_LIMIT_BYTES | 2 GiB | Cache budget; oldest evicted first |
KEYSTONE_DELTA_MAX_BASE_BYTES | 256 MiB | Largest artifact the delta path will attempt. Patching costs memory proportional to the artifact, so past this size downloading it is cheaper than reconstructing it. 0 disables the limit |
KEYSTONE_ARTIFACT_DOWNLOAD_TIMEOUT | 30m | Per-artifact download timeout. Accepts 5m, 1h |
Lifecycle
| Variable | Default | Effect |
|---|---|---|
KEYSTONE_INSTALL_TIMEOUT | 2m | Bound on the install phase of a layer |
KEYSTONE_DEVICE_ID | hostname | Device identity, used by the messaging adapters |
Containers
| Variable | Default | Effect |
|---|---|---|
KEYSTONE_CONTAINERD_SOCKET | /run/containerd/containerd.sock | containerd endpoint |
KEYSTONE_CONTAINERD_NAMESPACE | keystone | containerd namespace. On a host where Docker runs on top of containerd, the images live in moby, not here — see Containers |
KEYSTONE_CONTAINER_SNAPSHOTTER | — | Snapshotter override (e.g. native, overlayfs) |
KEYSTONE_CONTAINER_REGISTRY | — | Default registry for unqualified image names |
KEYSTONE_IMAGE_VOLUME_DIR | — | Where image volumes are materialised |
KEYSTONE_CNI_CONF_DIR | — | CNI configuration directory |
KEYSTONE_CNI_PLUGIN_DIRS | — | CNI plugin search path |
KEYSTONE_CNI_NETNS_DIR | — | CNI network namespace directory |
Datasets
Datasets are declared per recipe, not by environment variable. What the agent exports to the component is one variable per dataset:
| Variable | Value |
|---|---|
KEYSTONE_DATASET_<NAME> | Absolute path to the dataset’s current symlink |
The name is upper-cased with anything that is not a letter or digit replaced by
an underscore, so cve-bundle becomes KEYSTONE_DATASET_CVE_BUNDLE. The path
is absolute because a component runs with its own working directory. See
datasets.
Clock
| Variable | Flag | Default |
|---|---|---|
KEYSTONE_CLOCK_POLICY | --clock-policy | high-water |
high-water verifies certificates against the later of the system clock and the
agent’s own evidence that time has passed — a mark persisted across restarts and
the binary’s build timestamp. It is what lets a gateway with no RTC, whose clock
reads 1970 on first boot, accept a valid certificate instead of calling it “not
yet valid”.
strict refuses to verify anything while the system clock is behind that
evidence. Under it a signature check fails with a retryable error (HTTP 503),
because the condition clears by itself once NTP runs.
There is deliberately no setting that ignores certificate expiry. Expiry is what
makes a compromised signer stop working on its own; --insecure-skip-verify
already exists for development.
Periodic reconcile
Off unless you ask for it. See reconcile and reuse.
| Variable | Flag | Default |
|---|---|---|
KEYSTONE_RECONCILE_INTERVAL | --reconcile-interval | 0 (disabled) |
KEYSTONE_RECONCILE_JITTER | --reconcile-jitter | 10% of the interval |
The jitter offset is derived from the device ID (KEYSTONE_DEVICE_ID, or the
hostname), not drawn at random, so a device lands in the same slot on every run
and a fleet still spreads across the window. Setting the jitter to 0
explicitly is honoured — that is the right value for a single device.
MQTT
Every MQTT flag has an environment equivalent, which is how you normally configure it under systemd:
| Variable | Flag |
|---|---|
KEYSTONE_MQTT_BROKER | --mqtt-broker |
KEYSTONE_MQTT_DEVICE_ID | --mqtt-device-id |
KEYSTONE_MQTT_TENANT | --mqtt-tenant: required with MQTT, unless an enrolment provides it |
KEYSTONE_MQTT_CLIENT_ID | --mqtt-client-id |
KEYSTONE_MQTT_USER / KEYSTONE_MQTT_PASS | --mqtt-user / --mqtt-pass |
KEYSTONE_MQTT_TLS_CERT / _KEY / _CA | the matching --mqtt-tls-* |
KEYSTONE_ENROL_DIR | --enrol-dir: the identity written by keystone enrol, which then provides the TLS files, tenant and device ID above. See Device enrolment |
KEYSTONE_MQTT_TLS_VERIFY | --mqtt-tls-verify |
KEYSTONE_MQTT_QOS | --mqtt-qos |
KEYSTONE_MQTT_COMMAND_DEDUPE_TTL | --mqtt-command-dedupe-ttl |
KEYSTONE_SELF_UPDATE_ROOT | --self-update-root |
KEYSTONE_SELF_UPDATE_CONFIRM_TIMEOUT | --self-update-confirm-timeout |
KEYSTONE_MQTT_STATE_INTERVAL | --mqtt-state-interval |
KEYSTONE_MQTT_HEALTH_INTERVAL | --mqtt-health-interval |
An invalid value (a non-numeric integer, an unparseable duration, a bad boolean) is logged and ignored rather than crashing the agent — but check your logs, because “ignored” means the default is in force.
keystonectl
The client reads three of its own, and they are the ones worth exporting for a
device you work on regularly. They are not part of the agent’s .env.
| Variable | Flag | Default |
|---|---|---|
KEYSTONE_ADDR | --addr | http://127.0.0.1:8080 |
KEYSTONE_API_TOKEN | --token | (none) |
KEYSTONE_SSH | --ssh | (direct connection) |
KEYSTONE_API_TOKEN is shared with the agent, which reads it as the token to
require. See the CLI reference for
what --ssh does and what it needs.