Packages / ops-sdk
Boot time env
Read the env file ops renders for a service, in process, before anything else.
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.
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
| situation | what 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:
| engine | what the group holds |
|---|---|
| postgres | DATABASE_URL=postgres://<login>:<password>@<server>:5432/<database> |
| mariadb | DATABASE_URL=mysql://<login>:<password>@<server>:3306/<database> |
| libsql | DATABASE_URL, plus DATABASE_URL_HTTP_URL and DATABASE_URL_AUTH_TOKEN for clients that take the address and the token apart |
| sqlite | no 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.