opsdocsConsole

Packages / ops-sdk

Boot time env

Read the env file ops renders for a service, in process, before anything else.

Updated 13 Oct 2026View as Markdown

Feature: env.

Ops renders a service's secret group into one env file when it deploys, and hands it to the container as a secret mounted at /run/secrets/<name>. It is never put into the process environment by ops. env::load() reads that file into the process environment, so the rest of your code reads plain variables with std::env::var.

rust
fn main() {
    ops_sdk::env::load().expect("the deploy env file");
    let url = std::env::var("DATABASE_URL").expect("DATABASE_URL");
    // ...
}

Call it as the very first line of main, before a runtime starts or a thread reads a variable.

What it reads

situationwhat load() does
ENV_SECRET is set (a deployed service)reads /run/secrets/$ENV_SECRET. A missing or broken file is an error
ENV_SECRET is unset or empty (your machine, tests)reads .env in the working directory if there is one. A missing file is fine

SECRETS_DIR moves the directory the secret is read from, for a setup that mounts secrets somewhere other than /run/secrets.

A variable that is already set in the process wins over the file. That is how a plain container variable overrides a group key of the same name.

Treat the error as fatal

load() returns EnvFileError when the deploy file is missing or does not parse. Stop the process: a service that boots with half its configuration silently absent is worse than one that does not boot. The error names the file. A file that does not parse is reported with the line it could not read, value included, so the boot log of a service whose env file is broken can hold a secret. Rotate that variable once the file is fixed.

Databases

A Database resource writes its connection into the secret group it names, so after load() it is an ordinary variable. For the default key DATABASE_URL:

enginewhat the group holds
postgresDATABASE_URL=postgres://<login>:<password>@<server>:5432/<database>
mariadbDATABASE_URL=mysql://<login>:<password>@<server>:3306/<database>
libsqlDATABASE_URL, plus DATABASE_URL_HTTP_URL and DATABASE_URL_AUTH_TOKEN for clients that take the address and the token apart
sqliteno group key: the service that mounts the database gets DATABASE_URL=sqlite://<path>/<name>.sqlite as a plain container variable

The server names resolve only inside the project's own environment, so these addresses never connect from your machine. Run a local database there and point your own .env at it.

A rotation rewrites the key in the group and restarts the service, so reading the variable once at boot is enough.