Skip to content
Kavachकवच

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 to
secret.kav: Kavach code, then what running it prints

A 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"
kavach.toml: TOML code
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")}")
}
src/main.kav: Kavach code
terminal
kavach run src/main.kav
port 8080, debug true, hosts ["a.example", "b.example"]
mode unset
kavach run --env SHOP_PORT=9000 --env SHOP_MODE=test src/main.kav
port 9000, debug true, hosts ["a.example", "b.example"]
mode test
kavach run --native src/main.kav
port 8080, debug true, hosts ["a.example", "b.example"]
mode unset
  • Values are text, as environment variables are on every system: 8080 in [env.vars] arrives as "8080". The program converts it: Int(…), Bool(…), or split for a list written as text.
  • --env NAME=value beats [env.vars]: here the port became 9000 for one run.
  • A variable that isn't set fails, and catch gives a default, as SHOP_MODE does.

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)
kavach.toml: TOML code, then what running it prints

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 set
missing.kav: Kavach code, then what running it prints

A .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"
.env: Text code
  • Lines are NAME=value; export NAME=value works 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: $HOME stays as written.
  • Any other line is an error that names the file and the line.
  • Another file: --env-file <path>, or file in [env]. A dependency's .env is never read.
[env]
file = ".env.local"                # instead of .env
vars = { LOG_LEVEL = "info" }      # overrides the file; not for secrets, as kavach.toml is committed
kavach.toml: TOML code

Which value wins

Strongest first:

  1. --env given to kavach run or kavach test;
  2. the real environment: the shell, docker run -e, a service manager;
  3. [env.vars] in kavach.toml;
  4. the .env file.

So the place a built program runs always has the last word.

Built programs

  • kavach build copies the file, with [env.vars] and --env written into it, beside the executable: .kavach/bin/.env. Its values are never compiled in, since an executable is shared and copied. --no-env-file builds without one.
  • The executable reads that file the first time it reads a variable, so a program that never calls env pays 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_FILE names another file, so a container can keep it elsewhere.
terminal
kavach run --env-file .env.test --env PORT=9000
kavach build --env LOG_LEVEL=warn
docker run -e PORT=80 my-app # beats the copied .env

[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.