Environment Variables¶
Argos provides seamless integration with environment variables, allowing options to read values from the environment as fallbacks. This enables flexible configuration management for different deployment environments.
Basic Environment Integration¶
Use .fromEnv()
to read from environment variables:
class MyApp : Arguments() {
// Read from environment variable as fallback
val apiKey by option("--api-key")
.fromEnv("API_KEY")
.help("API key for authentication")
// Environment with default fallback
val host by option("--host")
.fromEnv("SERVER_HOST")
.default("localhost")
.help("Server hostname")
// Required option with environment fallback
val database by option("--database")
.fromEnv("DATABASE_URL")
.required()
.help("Database connection URL")
}
Usage Examples¶
# Set environment variables
export API_KEY="abc123"
export SERVER_HOST="api.example.com"
export DATABASE_URL="postgresql://localhost/mydb"
# Use environment values
myapp
# Uses: API_KEY=abc123, SERVER_HOST=api.example.com, DATABASE_URL=postgresql://localhost/mydb
# Override specific environment values
myapp --host staging.example.com
# Uses: API_KEY=abc123, SERVER_HOST=staging.example.com, DATABASE_URL=postgresql://localhost/mydb
# Command line takes precedence over environment
myapp --api-key xyz789 --host localhost
# Uses: API_KEY=xyz789, SERVER_HOST=localhost, DATABASE_URL=postgresql://localhost/mydb
Priority Order¶
Argos follows a clear priority order for option values:
- Command line arguments (highest priority)
- Environment variables
- Default values
- Null/missing (lowest priority)
# Priority demonstration
export PORT=3000
myapp # port = 3000 (from environment)
myapp --port 9000 # port = 9000 (command line overrides environment)
unset PORT
myapp # port = 8080 (default when no environment)
myapp --port 9000 # port = 9000 (command line overrides default)
Collection Environment Variables¶
Environment variables work with collection types:
class MyApp : Arguments() {
// List from environment (space-separated)
val searchPaths by option("--search-path").list()
.fromEnv("SEARCH_PATHS")
.default(listOf("/usr/bin", "/usr/local/bin"))
// Set from environment
val features by option("--feature").set()
.fromEnv("ENABLED_FEATURES")
// Multiple environment sources
val includePaths by option("--include", "-I").list()
.fromEnv("INCLUDE_PATHS")
}
Environment Collection Parsing¶
# Space-separated values (default behavior)
export SEARCH_PATHS="/opt/tools /home/user/bin /usr/local/bin"
export ENABLED_FEATURES="auth logging metrics"
myapp
# searchPaths = ["/opt/tools", "/home/user/bin", "/usr/local/bin"]
# features = {"auth", "logging", "metrics"}
# Override with command line
myapp --search-path /custom/path --feature auth --feature admin
# searchPaths = ["/custom/path"]
# features = {"auth", "admin"}
# Append to environment (if using multiple --option calls)
myapp --search-path /custom/path --search-path /another/path
# searchPaths = ["/custom/path", "/another/path"]
Configuration Patterns¶
Development vs Production¶
class WebApp : Arguments() {
// Database configuration
val dbHost by option("--db-host")
.fromEnv("DB_HOST")
.default("localhost")
val dbPort by option("--db-port").int()
.fromEnv("DB_PORT")
.default(5432)
val dbName by option("--db-name")
.fromEnv("DB_NAME")
.default("myapp_dev")
val dbUser by option("--db-user")
.fromEnv("DB_USER")
.default("postgres")
val dbPassword by option("--db-password")
.fromEnv("DB_PASSWORD")
.help("Database password")
// Application configuration
val serverPort by option("--port").int()
.fromEnv("PORT")
.default(8080)
val logLevel by option("--log-level")
.fromEnv("LOG_LEVEL")
.enum<LogLevel>()
.default(LogLevel.INFO)
// Security configuration
val jwtSecret by option("--jwt-secret")
.fromEnv("JWT_SECRET")
.required()
.hidden()
val corsOrigins by option("--cors-origin").list()
.fromEnv("CORS_ORIGINS")
.default(listOf("http://localhost:3000"))
}
enum class LogLevel { DEBUG, INFO, WARN, ERROR }
Environment Files¶
# .env.development
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp_dev
DB_USER=dev_user
DB_PASSWORD=dev_password
PORT=3000
LOG_LEVEL=DEBUG
JWT_SECRET=dev-secret-key
CORS_ORIGINS="http://localhost:3000 http://localhost:3001"
# .env.production
DB_HOST=prod-db.example.com
DB_PORT=5432
DB_NAME=myapp_prod
DB_USER=prod_user
DB_PASSWORD=super-secure-password
PORT=80
LOG_LEVEL=WARN
JWT_SECRET=production-secret-key-very-long-and-secure
CORS_ORIGINS="https://myapp.com https://www.myapp.com"
# .env.testing
DB_HOST=test-db.example.com
DB_NAME=myapp_test
LOG_LEVEL=ERROR
JWT_SECRET=test-secret
Typed Environment Variables¶
Environment variables work with all type conversions:
class MyApp : Arguments() {
// Numeric types
val maxConnections by option("--max-connections").int()
.fromEnv("MAX_CONNECTIONS")
.default(100)
val timeout by option("--timeout").double()
.fromEnv("TIMEOUT_SECONDS")
.default(30.0)
// Boolean types
val enableMetrics by option("--enable-metrics").bool()
.fromEnv("ENABLE_METRICS")
.default(false)
val debugMode by option("--debug").bool()
.fromEnv("DEBUG")
.default(false)
// Enum types
val environment by option("--env").enum<Environment>()
.fromEnv("ENVIRONMENT")
.default(Environment.DEVELOPMENT)
// Custom transformations
val configFile by option("--config")
.fromEnv("CONFIG_FILE")
.map("readable config file") { path ->
path?.let { File(it).takeIf { f -> f.exists() && f.canRead() } }
}
}
enum class Environment { DEVELOPMENT, STAGING, PRODUCTION }
Environment Type Examples¶
# Numeric environment variables
export MAX_CONNECTIONS=500
export TIMEOUT_SECONDS=45.5
# Boolean environment variables (multiple formats supported)
export ENABLE_METRICS=true
export DEBUG=1 # Also: yes, on
# Enum environment variables
export ENVIRONMENT=PRODUCTION # Case-insensitive
# File path environment variables
export CONFIG_FILE=/etc/myapp/config.json
myapp
# maxConnections = 500, timeout = 45.5, enableMetrics = true,
# debugMode = true, environment = PRODUCTION
Advanced Environment Patterns¶
Hierarchical Configuration¶
class MicroserviceApp : Arguments() {
// Service identity
val serviceName by option("--service-name")
.fromEnv("SERVICE_NAME")
.required()
val serviceVersion by option("--service-version")
.fromEnv("SERVICE_VERSION")
.default("unknown")
// Infrastructure configuration
val kubernetesNamespace by option("--k8s-namespace")
.fromEnv("KUBERNETES_NAMESPACE")
.default("default")
val clusterName by option("--cluster-name")
.fromEnv("CLUSTER_NAME")
.default("local")
// Service discovery
val consulHost by option("--consul-host")
.fromEnv("CONSUL_HOST")
.default("localhost")
val consulToken by option("--consul-token")
.fromEnv("CONSUL_TOKEN")
.hidden()
// Observability
val metricsEndpoint by option("--metrics-endpoint")
.fromEnv("METRICS_ENDPOINT")
.default("/metrics")
val tracingEndpoint by option("--tracing-endpoint")
.fromEnv("JAEGER_ENDPOINT")
val logFormat by option("--log-format")
.fromEnv("LOG_FORMAT")
.oneOf("json", "text")
.default("json")
}
Configuration with Validation¶
class DatabaseApp : Arguments() {
val connectionString by option("--connection-string")
.fromEnv("DATABASE_URL")
.validate("Must be a valid database URL") {
it?.startsWith("postgresql://") == true ||
it?.startsWith("mysql://") == true ||
it?.startsWith("sqlite://") == true
}
val maxPoolSize by option("--max-pool-size").int()
.fromEnv("DB_MAX_POOL_SIZE")
.default(20)
.validate("Pool size must be reasonable") { it in 1..100 }
val connectionTimeout by option("--connection-timeout").int()
.fromEnv("DB_CONNECTION_TIMEOUT")
.default(30)
.validate("Timeout must be positive") { it > 0 }
val sslMode by option("--ssl-mode")
.fromEnv("DB_SSL_MODE")
.oneOf("disable", "require", "verify-ca", "verify-full")
.default("require")
val tlsCertPath by option("--tls-cert")
.fromEnv("DB_TLS_CERT_PATH")
.requireIfValue(::sslMode) { it in listOf("verify-ca", "verify-full") }
.validate("Certificate file must exist") {
it?.let { File(it).exists() } != false
}
}
Secret Management¶
Hidden Environment Options¶
class SecureApp : Arguments() {
// Visible configuration
val serverHost by option("--host")
.fromEnv("SERVER_HOST")
.default("localhost")
val serverPort by option("--port").int()
.fromEnv("SERVER_PORT")
.default(8080)
// Hidden secrets (don't show in help)
val apiKey by option("--api-key")
.fromEnv("API_KEY")
.required()
.hidden()
val dbPassword by option("--db-password")
.fromEnv("DB_PASSWORD")
.password() // Prompts if not in environment
.hidden()
val jwtSigningKey by option("--jwt-key")
.fromEnv("JWT_SIGNING_KEY")
.required()
.hidden()
.validate("JWT key must be at least 32 characters") {
it.length >= 32
}
}
Environment Security Practices¶
# Good: Use secure environment variable management
export API_KEY="$(cat /etc/secrets/api-key)"
export DB_PASSWORD="$(kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d)"
# Good: Different secrets per environment
if [ "$ENVIRONMENT" = "production" ]; then
export JWT_SIGNING_KEY="$(cat /prod/secrets/jwt-key)"
else
export JWT_SIGNING_KEY="dev-jwt-key-not-secure"
fi
# Avoid: Hardcoded secrets in scripts
export API_KEY="hardcoded-secret" # Don't do this
Value Source Tracking¶
Track where option values came from:
fun main(args: Array<String>) {
val app = MyApp().parse(args) ?: return
// Check value sources
val apiKeySource = app.valueSourceOf(app::apiKey)
when (apiKeySource) {
ValueSource.USER -> println("API key from command line")
ValueSource.ENVIRONMENT -> println("API key from environment")
ValueSource.DEFAULT -> println("API key using default")
ValueSource.MISSING -> println("API key not provided")
}
val hostSource = app.valueSourceOf(app::host)
if (hostSource == ValueSource.ENVIRONMENT) {
println("Using host from environment: ${app.host}")
}
}
Complete Examples¶
Cloud Application¶
enum class CloudProvider { AWS, GCP, AZURE }
enum class LogLevel { DEBUG, INFO, WARN, ERROR }
class CloudApp : Arguments(
appName = "cloud-deploy",
appDescription = "Cloud deployment tool with environment configuration"
) {
// Cloud provider configuration
val provider by option("--provider").enum<CloudProvider>()
.fromEnv("CLOUD_PROVIDER")
.default(CloudProvider.AWS)
val region by option("--region")
.fromEnv("CLOUD_REGION")
.default("us-east-1")
// AWS-specific configuration
val awsAccessKey by option("--aws-access-key")
.fromEnv("AWS_ACCESS_KEY_ID")
.hidden()
val awsSecretKey by option("--aws-secret-key")
.fromEnv("AWS_SECRET_ACCESS_KEY")
.hidden()
val awsSessionToken by option("--aws-session-token")
.fromEnv("AWS_SESSION_TOKEN")
.hidden()
// Application configuration
val appName by option("--app-name")
.fromEnv("APP_NAME")
.required()
val appVersion by option("--app-version")
.fromEnv("APP_VERSION")
.default("latest")
val environment by option("--environment")
.fromEnv("DEPLOY_ENVIRONMENT")
.oneOf("dev", "staging", "prod")
.default("dev")
// Deployment configuration
val instances by option("--instances").int()
.fromEnv("INSTANCE_COUNT")
.default(1)
.validate("Instance count must be reasonable") { it in 1..100 }
val enableAutoScaling by option("--auto-scaling").bool()
.fromEnv("ENABLE_AUTO_SCALING")
.default(false)
val healthCheckPath by option("--health-check")
.fromEnv("HEALTH_CHECK_PATH")
.default("/health")
// Logging and monitoring
val logLevel by option("--log-level").enum<LogLevel>()
.fromEnv("LOG_LEVEL")
.default(LogLevel.INFO)
val enableMetrics by option("--enable-metrics").bool()
.fromEnv("ENABLE_METRICS")
.default(true)
val metricsPort by option("--metrics-port").int()
.fromEnv("METRICS_PORT")
.default(9090)
val help by help()
val version by version("1.0.0")
}
Container Application¶
class ContainerApp : Arguments() {
// Container runtime
val runtime by option("--runtime")
.fromEnv("CONTAINER_RUNTIME")
.oneOf("docker", "containerd", "cri-o")
.default("docker")
// Registry configuration
val registryUrl by option("--registry")
.fromEnv("CONTAINER_REGISTRY")
.default("docker.io")
val registryUser by option("--registry-user")
.fromEnv("REGISTRY_USERNAME")
val registryPassword by option("--registry-password")
.fromEnv("REGISTRY_PASSWORD")
.password()
.hidden()
// Image configuration
val imageName by option("--image")
.fromEnv("IMAGE_NAME")
.required()
val imageTag by option("--tag")
.fromEnv("IMAGE_TAG")
.default("latest")
val buildArgs by option("--build-arg").list()
.fromEnv("BUILD_ARGS")
// Runtime configuration
val cpuLimit by option("--cpu-limit")
.fromEnv("CPU_LIMIT")
.default("1.0")
val memoryLimit by option("--memory-limit")
.fromEnv("MEMORY_LIMIT")
.default("512M")
val environmentVars by option("--env", "-e").list()
.fromEnv("CONTAINER_ENV_VARS")
val volumes by option("--volume", "-v").list()
.fromEnv("CONTAINER_VOLUMES")
val ports by option("--port", "-p").list()
.fromEnv("CONTAINER_PORTS")
.validate("Port mapping must be in format host:container") {
it.contains(":")
}
}
Best Practices¶
1. Use Descriptive Environment Variable Names¶
// Good: Clear, descriptive names
val apiKey by option("--api-key").fromEnv("API_KEY")
val dbHost by option("--db-host").fromEnv("DATABASE_HOST")
val maxRetries by option("--max-retries").fromEnv("MAX_RETRY_COUNT")
// Avoid: Ambiguous or short names
val key by option("--api-key").fromEnv("KEY")
val host by option("--db-host").fromEnv("H")
2. Provide Sensible Defaults¶
// Good: Reasonable defaults for local development
val host by option("--host").fromEnv("HOST").default("localhost")
val port by option("--port").int().fromEnv("PORT").default(8080)
val logLevel by option("--log-level").fromEnv("LOG_LEVEL").default("INFO")
// Consider: When should options be required vs have defaults?
val apiKey by option("--api-key").fromEnv("API_KEY").required() // Secrets often required
3. Hide Sensitive Information¶
// Good: Hide secrets from help output
val password by option("--password").fromEnv("PASSWORD").password().hidden()
val apiKey by option("--api-key").fromEnv("API_KEY").hidden()
val token by option("--token").fromEnv("ACCESS_TOKEN").hidden()
// Consider: Use password() for interactive input when env var missing
val dbPassword by option("--db-password").fromEnv("DB_PASSWORD").password()
4. Document Environment Variables¶
// Good: Document environment variables in help text
val apiKey by option("--api-key").fromEnv("API_KEY")
.help("API key for authentication (env: API_KEY)")
val host by option("--host").fromEnv("SERVER_HOST").default("localhost")
.help("Server hostname (env: SERVER_HOST, default: localhost)")
Environment variables provide flexible configuration management that adapts to different deployment environments while maintaining security and ease of use.