background

Configuration Flexibility: Support CLI Flags, Env Vars & Config Files Without Friction [2026]

Good tools support all three: CLI flags for immediacy, env vars for deployment, and config files for persistence. Here's how to design a precedence system that actually works.

You’ve seen this tool: it only reads from a config file, so containerizing it means writing wrapper scripts just to inject values you already have in your environment. Or it only reads environment variables, so local development means polluting your shell with settings that belong in a project-specific file. These problems have the same root cause - the tool made a configuration decision that doesn’t match your deployment context.

Well-designed tools don’t force that choice. They support CLI flags for immediacy, environment variables for deployment, and config files for persistence - with a clear precedence rule that makes all three work together predictably. Here’s how to build that.

The Three Pillars of Configuration

Effective configuration typically comes in three forms, each serving distinct use cases:

CLI Flags: Immediate and Explicit

Command-line flags offer the most explicit form of configuration. When you run dprs --port 8080 --debug, there’s no ambiguity about what’s happening. CLI flags excel in:

The immediacy of CLI flags makes them perfect for exploratory work and situations where you need to see exactly what configuration is being applied.

Environment Variables: Context-Aware Configuration

Environment variables bridge the gap between ephemeral CLI flags and persistent config files. They shine in:

Modern deployment infrastructure is built around environment variables, making them essential for tools that need to work in contemporary DevOps workflows.

Config Files: Persistent and Comprehensive

Configuration files provide the most comprehensive and maintainable approach for complex setups:

Whether YAML, TOML, JSON, or another format, config files offer the structure needed for sophisticated configuration requirements.

Once you implement all three with a clear precedence chain, you can drop your tool into a Kubernetes deployment, a CI pipeline, and a local dev environment without changing a line of code - each context supplies configuration the way it naturally does.

Why All Three Matter

The power isn’t in choosing one method - it’s in supporting all three with clear precedence rules. Users should be able to:

  1. Set sensible defaults in a config file
  2. Override for an environment using environment variables
  3. Fine-tune specific invocations with CLI flags

This precedence chain (config file → environment variables → CLI flags) gives users maximum flexibility while maintaining predictable behavior.

Reference Implementations

If you want to see this pattern in working code, two open source projects demonstrate it across different language ecosystems:

dprs - A Docker container management TUI built in Rust. If you’re working in the Rust ecosystem and want to see how configuration layering integrates with CLI argument parsing libraries like clap, this is a useful reference.

Implementation Patterns

Supporting multiple configuration methods requires thoughtful design:

Precedence is Critical

Users must understand which configuration takes priority. The typical precedence from lowest to highest is:

  1. Hardcoded defaults (in code)
  2. Config file values
  3. Environment variables
  4. CLI flags

This order makes intuitive sense: more specific and immediate configuration methods override more general ones.

Discoverability Matters

Each configuration method should be discoverable:

Validation Should Be Consistent

Whether a value comes from a CLI flag, environment variable, or config file, validation should be identical. A port number is invalid if it’s out of range regardless of how it was specified.

Real-World Scenarios

Consider these common scenarios and how multiple configuration methods solve them:

Local Development: A developer uses config files for their standard setup, but occasionally needs to override the port with a CLI flag when testing multiple instances.

CI/CD Pipeline: The same tool reads from a committed config file for most settings, but uses environment variables for secrets and environment-specific values like API endpoints.

Container Deployment: In Kubernetes, environment variables configure the tool for different namespaces and environments, while a mounted ConfigMap provides the detailed configuration that rarely changes.

Team Onboarding: New team members clone the repository, and the committed config file gives them a working setup immediately. As they learn, they can override settings temporarily with flags before eventually customizing their own config file.

The Cost of Rigidity

Tools that only support one configuration method create unnecessary friction:

For most tools, adding environment variable support on top of existing CLI flags is a few dozen lines of code - typically one lookup per parameter before the CLI default is applied. The engineering cost is modest. The friction you avoid for users deploying into containers or CI environments is not.

Start Simple, Grow Thoughtfully

You don’t need to implement every configuration method on day one. A pragmatic approach:

  1. Start with CLI flags for core parameters
  2. Add config file support when configuration grows complex
  3. Layer in environment variable support when users deploy to different environments
  4. Maintain clear documentation as you add methods

The key is having a plan for supporting all three as your tool matures.

Where This Pattern Ends

The three-layer approach works well for single-process tools and libraries. If your system spans multiple services that need shared configuration - a microservices fleet, for example - you’ll eventually need a config server, a secrets manager like Vault, or a distributed key-value store. That’s a different problem with different trade-offs, and a different article.

For a single tool or library, the precedence chain described here covers the overwhelming majority of real deployment scenarios. Start with it. You can layer additional complexity on top later if your actual usage demands it - but most tools never will.


Want to see this pattern in working code? dprs is open source at github.com/durableprogramming. If your team is working through a configuration design decision and wants a second opinion, reach out.