Environment Variables
● Intermediate5 minBefore this: Settings, SecretsSpec 10.13
env(name) reads an environment variable. Its value is a secret, so it can't be printed by mistake. A .env file, kavach.toml or --env can give the values.
env needs the Env capability, and it fails when the variable isn't set. What it gives is a secret, because keys and passwords usually come this way:
fn main() ! Error uses Env, Console {
const key = try env("SHOP_KEY")
print(key)
}The compiler refused the program:
kavach run secret.kav
error: `key` is secret, so it can't go into `print`; use `reveal(…)` if you really mean toA value that isn't secret, such as a port, is opened with reveal, where review can see it:
[package]
name = "shop"
version = "0.1.0"
edition = "2026"
[env.vars]
SHOP_PORT = 8080
SHOP_DEBUG = true
SHOP_HOSTS = "a.example,b.example"fn main() ! Error uses Env, Console {
const port = try Int(reveal(try env("SHOP_PORT"))) // "8080" → 8080
const debug = try Bool(reveal(env("SHOP_DEBUG") catch "false"))
const hosts = reveal(try env("SHOP_HOSTS")).split(",")
print("port {port}, debug {debug}, hosts {hosts}")
print("mode {reveal(env("SHOP_MODE") catch "unset")}")
}port 8080, debug true, hosts ["a.example", "b.example"] mode unset
port 9000, debug true, hosts ["a.example", "b.example"] mode test
port 8080, debug true, hosts ["a.example", "b.example"] mode unset
- Values are text, as environment variables are on every system:
8080in[env.vars]arrives as"8080". The program converts it:Int(…),Bool(…), orsplitfor a list written as text. --env NAME=valuebeats[env.vars]: here the port became 9000 for one run.- A variable that isn't set fails, and
catchgives a default, asSHOP_MODEdoes.
A list or a table in [env.vars] is refused, since a variable holds only text:
[package]
name = "shop"
version = "0.1.0"
edition = "2026"
[env.vars]
SHOP_HOSTS = ["a.example", "b.example"]The compiler refused the program:
error: `SHOP_HOSTS` in `[env.vars]` of kavach.toml: an environment variable holds text, so write a list as text, such as "a,b,c", and split it in the program (spec 10.13)Without a value and without a default, the program ends with an error that names the variable:
fn main() ! Error uses Env, Console {
const key = try env("SHOP_KEY")
print("got a key of {reveal(key).len()} characters")
}The compiler refused the program:
kavach run missing.kav
error: environment variable `SHOP_KEY` is not setA .env file
A .env file beside kavach.toml gives a program its variables without setting them in the shell. Never commit it: kavach new puts it in .gitignore.
# .env
STRIPE_KEY=sk_test_123
PORT=8080
GREETING="Hello, world"- Lines are
NAME=value;export NAME=valueworks too.#starts a comment line. A value in double quotes may hold\n,\"and\\; in single quotes it is taken as written. Nothing is expanded:$HOMEstays as written. - Any other line is an error that names the file and the line.
- Another file:
--env-file <path>, orfilein[env]. A dependency's.envis never read.
[env]
file = ".env.local" # instead of .env
vars = { LOG_LEVEL = "info" } # overrides the file; not for secrets, as kavach.toml is committedWhich value wins
Strongest first:
--envgiven tokavach runorkavach test;- the real environment: the shell,
docker run -e, a service manager; [env.vars]inkavach.toml;- the
.envfile.
So the place a built program runs always has the last word.
Built programs
kavach buildcopies the file, with[env.vars]and--envwritten into it, beside the executable:.kavach/bin/.env. Its values are never compiled in, since an executable is shared and copied.--no-env-filebuilds without one.- The executable reads that file the first time it reads a variable, so a program that never calls
envpays nothing for it. A file it can't read, or a wrong line in it, ends the program there with exit code 2. - The variable
KAVACH_ENV_FILEnames another file, so a container can keep it elsewhere.
[env.vars] is committed with kavach.toml, so it is for settings such as a log level, never for keys. Keys go in .env, or in the real environment where the program runs.