incus-compose handles environment variables differently than docker-compose for security and reproducibility reasons.
By default, incus-compose loads environment variables from:
.env file in the compose file's directory--env-fileThese .env files can reference OS environment variables for interpolation:
# .env
DB_PASSWORD=secret123
HOME_DIR=${HOME}
CURRENT_USER=${USER}
Only variables explicitly defined in .env files are passed to your compose
project. Your shell's environment (like PATH, EDITOR, etc.) is not
automatically included.
Values are not treated literally: a $ inside a value is itself interpolated
(PASSWORD=abc$def becomes abc, since $def resolves to nothing), and a
single-quoted value has no way to represent a literal '. Only double-quoting
with backslash-escaping (\\, \", \$) round-trips a value containing any of
these characters.
flowchart LR
ENV[".env plus<br/>--env-file files"] --> M[compose model]
OS["your shell environment<br/>PATH, EDITOR, ..."] -.->|"only where a .env line<br/>interpolates it"| ENV
OS -->|"--os-env / -E"| M
--os-env / -E FlagIf you need full docker-compose compatibility, use the --os-env flag:
incus-compose --os-env up
incus-compose -E up
This includes all OS environment variables directly, matching docker-compose behavior.
--os-env resolves before .env/--env-file in the merge order: when a key is
set by both, the shell's value wins and the .env value for that key is dropped
rather than overriding it.
# .env
DATABASE_URL=postgres://localhost/mydb
API_KEY=your-api-key
USER=${USER}
# compose.yaml
services:
app:
environment:
DATABASE_URL: ${DATABASE_URL}
API_KEY: ${API_KEY}
DEPLOYED_BY: ${USER}
incus-compose up
export DATABASE_URL=postgres://localhost/mydb
incus-compose --os-env up
| Method | Variables Available | Use Case |
|---|---|---|
| Default | .env files only (can interpolate OS vars) |
Production, CI/CD |
--os-env |
All OS environment variables | Quick testing, docker-compose compatibility |
Every global flag can be set via an environment variable. Flags given on the command line take precedence over environment variables.
flowchart TD
S([resolve a flag]) --> A{given on the command line?}
A -->|yes| USEF[use the flag]
A -->|no| B{"INCUS_COMPOSE_* variable set?"}
B -->|yes| USEE[use the variable]
B -->|no| USED[use the built-in default]
Every command-specific flag can be set too, scoped per command as
INCUS_COMPOSE_<COMMAND>_<FLAG> - e.g. --timeout on up is
INCUS_COMPOSE_UP_TIMEOUT, --timeout on down is
INCUS_COMPOSE_DOWN_TIMEOUT. Each command gets its own variable even when the
flag name is shared, so setting one never leaks into another command. Each
command's page in the CLI Reference lists its own variables,
or run incus-compose <command> --help - every flag's env var is shown inline
as [$VAR_NAME].
Nine flags are the deliberate exception and have no environment variable, because a forgotten shell variable would silently make every future invocation destructive or a no-op instead of just changing cosmetic output:
| Flag | Command | Why |
|---|---|---|
--recreate |
up |
Would silently recreate containers on every up |
--project |
down |
Would silently delete the whole project |
--volumes |
down |
Would silently delete volumes |
--dry-run |
exec |
Would silently no-op every exec, breaking scripts |
--dry-run |
cp |
Would silently no-op every copy |
--dry-run |
port-forward |
Would silently no-op every forward |
--signal |
kill |
Takes only SIGKILL, which is already the default |
--yes |
backup restore |
Would silently skip the confirmation on a destructive restore |
--dry-run |
backup restore |
Would silently no-op every restore |
| Variable | Flag | Description |
|---|---|---|
INCUS_COMPOSE_FILE |
--file, -f |
Compose configuration files (comma-separated for multiple) |
INCUS_COMPOSE_PROJECT_NAME |
--project-name, -p |
Project name |
INCUS_COMPOSE_PROJECT_DIRECTORY |
--project-directory, -P |
Working directory |
INCUS_COMPOSE_ENV_FILE |
--env-file |
Alternative environment files (comma-separated for multiple) |
INCUS_COMPOSE_PROFILES |
--profile |
Profiles to enable (comma-separated for multiple) |
INCUS_COMPOSE_OS_ENV |
--os-env, -E |
Include OS environment variables for interpolation |
| Variable | Flag | Description |
|---|---|---|
INCUS_REMOTE |
--remote |
Incus remote name from CLI config (e.g., local, myserver) |
INCUS_SERVER_URL |
--server-url |
Direct HTTPS endpoint to connect to (e.g., https://192.168.1.10:8443) |
INCUS_CLIENT_CERT_FILE |
--client-cert-file |
Path to client certificate file (required with --server-url) |
INCUS_CLIENT_KEY_FILE |
--client-key-file |
Path to client key file (required with --server-url) |
INCUS_SERVER_FINGERPRINT |
--server-fingerprint |
Pinned server certificate SHA-256 fingerprint (required with --server-url) |
INCUS_COMPOSE_IMAGE_CACHE |
--image-cache |
Incus project used as image cache (INCUS_COMPOSE_IMAGE_CACHE, default: incus-compose-cache); set "" to disable caching and pull straight into the project, see CLI Reference |
INCUS_COMPOSE_STORAGE_POOL |
--storage-pool |
Default storage pool (default: detect) |
An explicit --remote CLI flag takes precedence over --server-url /
INCUS_SERVER_URL. Otherwise, INCUS_SERVER_URL takes precedence over
INCUS_REMOTE or the default Incus remote.
| Variable | Flag | Description |
|---|---|---|
INCUS_COMPOSE_ANSI |
--ansi |
Control ANSI output: never, always, auto (default: auto) |
INCUS_COMPOSE_DEBUG |
--debug |
Enable debug logging (true/1) |
INCUS_COMPOSE_TRACE |
--trace |
Per-event logging, which implies --debug; read by ic-healthd |
INCUS_COMPOSE_WORKERS |
--workers |
Number of concurrent workers (default: 4) |
NO_COLOR |
-- | Disable color output (no-color.org) |
--builder and --healthd-* are command flags (up, build, pull,
healthd up, healthd down), not global ones - see
up and down,
Images and
Extensions for their
per-command variable names.
The ic-healthd daemon reads a further set of INCUS_COMPOSE_HEALTHD_* variables
of its own, which incus-compose injects into the sidecar - see
The ic-healthd daemon below.
# Use a configured Incus remote
export INCUS_REMOTE=myserver
incus-compose up
# Set project defaults in your shell profile
export INCUS_COMPOSE_FILE=compose.yaml,compose.prod.yaml
export INCUS_COMPOSE_PROJECT_NAME=myapp
incus-compose up
# Debug with extra workers
INCUS_COMPOSE_DEBUG=1 INCUS_COMPOSE_WORKERS=20 incus-compose up
These are read by the ic-healthd binary itself, not by incus-compose. In the
normal flow incus-compose sets them on the sidecar and you never touch them;
they matter when you run the daemon yourself (see
Running the daemon directly).
| Variable | Flag | Default | Description |
|---|---|---|---|
INCUS_COMPOSE_HEALTHD_INCUS |
--incus |
- | Incus API URL the daemon connects to |
INCUS_COMPOSE_HEALTHD_TOKEN |
--token |
- | One-time trust token used to register its cert |
INCUS_COMPOSE_HEALTHD_PROJECTS |
--project |
- | Projects to watch, comma-separated; see below |
INCUS_COMPOSE_HEALTHD_PROJECT_MARKER |
--project-marker |
user.healthcheck.scope=global |
Project config KEY=VALUE consulted when _PROJECTS is unset |
INCUS_COMPOSE_HEALTHD_OWN_PROJECT |
--own-project |
- | Project the daemon's own container runs in |
INCUS_COMPOSE_HEALTHD_OWN_NAME |
--own-name |
- | The daemon's own instance name; empty skips itself |
INCUS_COMPOSE_HEALTHD_DATA_DIR |
--data-dir |
/var/lib/ic-healthd |
Persistent directory for the generated cert/key |
INCUS_COMPOSE_HEALTHD_SECRETS_DIR |
--secrets-dir |
/run/secrets |
Tmpfs directory holding the token file |
INCUS_COMPOSE_HEALTHD_HTTP_ADDRESS |
--http-address |
:9153 |
Address for /metrics, /health and /ready; empty disables |
INCUS_COMPOSE_HEALTHD_DEBUG |
--debug |
false |
Verbose logging |
INCUS_COMPOSE_HEALTHD_TRACE |
--trace |
false |
Per-event logging, which implies --debug |
_PROJECTS may be left unset, in which case the daemon watches every project it
can see whose config matches _PROJECT_MARKER - by default
user.healthcheck.scope=global, which is what incus-compose stamps on the
projects it hands to the shared daemon. A bare key means KEY=true. Set
_PROJECTS explicitly and it is used verbatim, marker ignored. Either way the
daemon's trust token bounds what it can see at all.
Note that INCUS_COMPOSE_HEALTHD_INCUS has two different readers: on up and
healthd up it tells incus-compose what endpoint to configure the sidecar
with, and in the table above it is what the daemon dials. They agree in the
normal flow because the former is how the latter gets set.
env_file support