RUST Contents

Configuration and Environment Basics

Load configuration from environment variables with clear boundaries between build-time and runtime config, fail fast on invalid state, and prepare your Rust service for containerized production environments.

On this page

Configuration Is a Runtime Concern

In production systems, configuration must not be hardcoded. Ports, database URLs, API keys, timeouts, and feature toggles change between environments. A Rust service must separate build-time decisions from runtime configuration and fail fast if required configuration is missing.

Environment Variables as the Baseline

The most portable and container-friendly configuration mechanism is environment variables. They work across Linux servers, Docker, Kubernetes, and CI systems.

use std::env;

fn main() {
    let database_url = env::var("DATABASE_URL")
        .expect("DATABASE_URL must be set");

    println!("Connecting to {}", database_url);
}

Production principle: if a required variable is missing, crash early during startup. Do not allow partially configured services to run.

Strongly Typed Configuration Struct

A production service should parse configuration once at startup and expose a typed struct to the rest of the application.

use std::env;

pub struct Config {
    pub database_url: String,
    pub port: u16,
    pub request_timeout_ms: u64,
}

impl Config {
    pub fn from_env() -> Result<Self, String> {
        let database_url = env::var("DATABASE_URL")
            .map_err(|_| "DATABASE_URL missing".to_string())?;

        let port = env::var("PORT")
            .unwrap_or_else(|_| "8080".to_string())
            .parse()
            .map_err(|_| "PORT must be a valid number".to_string())?;

        let request_timeout_ms = env::var("REQUEST_TIMEOUT_MS")
            .unwrap_or_else(|_| "5000".to_string())
            .parse()
            .map_err(|_| "REQUEST_TIMEOUT_MS must be a number".to_string())?;

        Ok(Self {
            database_url,
            port,
            request_timeout_ms,
        })
    }
}

This ensures:

  • Parsing happens once
  • Validation is centralized
  • Business logic never reads environment variables directly

Using dotenv in Development Only

In local development, it is convenient to load environment variables from a .env file. This should not replace real environment configuration in production.

# .env
DATABASE_URL=postgres://localhost/dev
PORT=8080
dotenvy = "0.15"
fn main() {
    dotenvy::dotenv().ok();
    let config = Config::from_env().expect("Invalid configuration");
}

Rule: do not rely on .env in production containers. Use real environment injection.

Fail Fast, Not Lazily

Never lazily read environment variables deep inside request handlers. That makes configuration errors appear under load instead of at startup.

Correct approach:

  • Parse configuration at startup
  • Validate it completely
  • Pass Config via dependency injection

Configuration vs Secrets

Not all configuration is equal. Secrets like database passwords or API tokens must never be logged.

Guidelines:

  • Never print secrets in debug logs
  • Keep secret values out of panic messages
  • Use separate secret management in Kubernetes or cloud environments

Build-Time vs Runtime Configuration

Rust supports compile-time configuration using env! and option_env! macros, but these embed values into the binary.

const BUILD_VERSION: &str = env!("CARGO_PKG_VERSION");

Acceptable use cases:

  • Embedding build metadata
  • Feature flags decided at compile time

Not acceptable:

  • Embedding database URLs
  • Embedding API keys

Container-Aware Configuration

In containerized deployments:

  • Environment variables are injected by Docker or Kubernetes
  • Config maps hold non-sensitive config
  • Secrets are injected separately

Your Rust service should assume it is stateless and externally configured.

Production Checklist

  • All required env vars validated at startup
  • No lazy env reads inside handlers
  • Strongly typed Config struct
  • .env used only in development
  • Secrets never logged
  • Clear separation between build-time and runtime config

Configuration discipline prevents subtle runtime failures and makes your Rust service predictable across environments. Production reliability starts at process startup.