Env
Luma provides built-in access to environment variables through the env namespace — no imports needed.
Environment variables are the standard way to configure applications across every deployment platform: local development, Docker, CI/CD, cloud services. The env module covers the two things every application needs:
- Read and write system environment variables
- Load from
.envfiles for local development
Reading Environment Variables
env.get returns the value of an environment variable as a string. If the variable is not set, it returns an empty string.
host: str = env.get("DATABASE_HOST")
port: str = env.get("PORT")
secret: str = env.get("JWT_SECRET")Default Values
Pass a second argument to provide a fallback when the variable is not set:
port: str = env.get("PORT", "8080")
host: str = env.get("HOST", "localhost")
mode: str = env.get("APP_MODE", "development")The default is only used when the variable is not set at all. An empty value (PORT=) is still a value — the default won’t apply.
Checking Existence
env.has returns true if the variable is set, false otherwise:
if env.has("API_KEY") {
key: str = env.get("API_KEY")
// use the key
}This distinguishes between “not set” and “set to empty string”, which env.get alone cannot do.
Setting Environment Variables
env.set sets an environment variable for the current process:
env.set("APP_MODE", "testing")
env.set("LOG_LEVEL", "debug")This affects the current process and any child processes it spawns. It does not modify your shell or persist after the program exits.
Loading from .env Files
env.load reads a file and sets environment variables from its contents:
env.load(".env")If the file does not exist, env.load does nothing — no error, no panic, just a silent skip. This is intentional: your code works the same in development (where .env exists) and production (where it doesn’t). One line, zero if checks, deploy everywhere.
// In development: .env exists, variables are loaded
// In production: .env is missing, system env vars are used
// Same code. No changes needed.
env.load(".env")This is the standard dotenv pattern used across every major language ecosystem. Place a .env file in your project root with configuration values, and load it at the start of your program.
File Format
# Database configuration
DATABASE_HOST=localhost
DATABASE_PORT=3306
DATABASE_NAME=myapp
# API keys
JWT_SECRET=super-secret-key-change-in-production
API_KEY=dev-key-12345
# Quoted values (for spaces and special characters)
APP_NAME="My Cool App"
GREETING='Hello World'
# Empty value
EMPTY_VAL=The format rules:
| Syntax | Meaning |
|---|---|
KEY=value |
Set a variable |
KEY="value" |
Quoted value — preserves spaces |
KEY='value' |
Single-quoted value — preserves spaces |
KEY= |
Set to empty string |
# comment |
Comment — ignored |
| Empty lines | Ignored |
export KEY=value |
export prefix is stripped silently (shell compatibility) |
System Variables Take Precedence
env.load does not override variables that are already set in the system environment. This is the correct behavior for production:
- In development, the
.envfile provides defaults - In production, the deployment platform sets real values through the OS
- The
.envfile never silently overrides a production secret
// If DATABASE_HOST is already set in the system environment,
// the value from .env is ignored for that variable.
env.load(".env")
host: str = env.get("DATABASE_HOST") // system value winsMultiple Files
You can load multiple files. Later files fill in gaps but never override earlier values or system variables:
env.load(".env") // base defaults
env.load(".env.local") // local developer overridesA common project layout:
.env # shared defaults, committed to git
.env.local # personal overrides, in .gitignore
.env.production # production values, managed by deploymentGetting All Variables
env.all returns every environment variable as a map:
all: {str: str} = env.all()This includes both system variables and any loaded from .env files.
Practical Patterns
Application configuration
env.load(".env")
mysql = import "mysql"
jwt = import "jwt"
conn = db.open(mysql, env.get("DATABASE_DSN"))
secret: str = env.get("JWT_SECRET")
port: str = env.get("PORT", "8080")
print("Starting on port ${port}")Feature flags
if env.get("ENABLE_DEBUG") == "true" {
print("Debug mode enabled")
}Conditional behavior by environment
env.load(".env")
mode: str = env.get("APP_MODE", "development")
if mode == "production" {
print("Running in production")
}
if mode == "development" {
print("Running in development")
}Quick Reference
| Operation | Code |
|---|---|
| Get a variable | val = env.get("KEY") |
| Get with default | val = env.get("KEY", "fallback") |
| Check if set | env.has("KEY") |
| Set a variable | env.set("KEY", "value") |
Load .env file |
env.load(".env") (skips silently if missing) |
| Get all variables | all = env.all() |
Method Summary
| Method | Arguments | Returns | Description |
|---|---|---|---|
env.get() |
key: str |
str |
Get variable value (empty string if not set) |
env.get() |
key: str, default: str |
str |
Get variable value with fallback |
env.has() |
key: str |
bool |
Check if variable is set |
env.set() |
key: str, value: str |
— | Set variable for current process |
env.load() |
path: str |
— | Load variables from a dotenv file (silent skip if missing) |
env.all() |
— | {str: str} |
Get all variables as a map |
Design Notes
envis a built-in namespace — no import needed, likejson,crypto, anddbenv.getreturns an empty string for missing variables, not a panic — this matches the convention in Go, PHP, and most other languagesenv.loadsilently skips missing files — the same code works in development (.envpresent) and production (.envabsent) without any conditional logicenv.loadnever overrides existing system variables — production environment always wins over.envfilesenv.setonly affects the current process — it doesn’t modify your shell or write to files- The
.envfile format follows the widely-adopted dotenv convention, compatible with Docker, Heroku, Vercel, and most deployment platforms