Design and Philosophy

This document describes the core design principles, mental model, and non-goals of envstack.

It is intended to explain why envstack behaves the way it does, not to document every feature or command-line option.

Design goals

envstack is designed to make environment configuration:

envstack assumes that real-world environments are hierarchical and that configuration should reflect that structure.

Mental model

envstack treats environment configuration as a stack.

Each layer contributes variables, defaults, or overrides. Layers are applied in a defined order, and precedence is explicit.

You can think of envstack as:

Environment activation + configuration layering

or:

Policy-driven composition of environment variables

envstack does not attempt to infer intent or solve dependency graphs. All composition is declared explicitly.

Environment stacks

An environment stack is an ordered collection of environment definitions.

Stacks may be composed by:

Variables flow through the stack according to precedence rules.

flowchart TD base[base.env] prod[prod.env] project[project.env] task[task.env] keys[keys.env] base --> prod --> project --> task keys -. include .-> prod keys -. include .-> project

Precedence and overrides

Precedence in envstack is explicit and ordered.

There is no implicit merging or magic behavior. If a value changes, it is because a later layer overrides it.

This makes environment behavior predictable and debuggable.

Hierarchy and inheritance

envstack environments are typically arranged hierarchically:

Downstream environments inherit upstream configuration and apply targeted overrides. This avoids duplication while preserving clarity.

ENVPATH

envstack discovers environment definitions using the ENVPATH environment variable.

ENVPATH is an ordered, colon-separated list of directories that envstack searches when resolving environment names.

envstack searches ENVPATH left-to-right; earlier entries win when the same env name exists in multiple locations:

ENVPATH=/mnt/tools/dev/env:/mnt/tools/prod/env

Resolution follows these rules:

ENVPATH is intentionally simple and filesystem-backed. It allows:

ENVPATH plays the same role for envstack that PATH plays for executables.

Includes

Environment definitions may include other environments explicitly.

Includes allow:

Includes are declarative and resolved as part of the stack.

Variable resolution

Variables may:

Resolution follows shell-like semantics and is performed explicitly.

envstack allows:

This makes configuration errors visible early.

What envstack does not do (non-goals)

envstack intentionally does not:

Those concerns are left to other tools or to policy defined by the user.

envstack focuses solely on composition and activation of configuration.

Composability with other systems

envstack is designed to compose cleanly with other systems.

It can be layered on top of:

envstack does not require a specific packaging or deployment model. It assumes only that environments can be described declaratively.

Early binding vs late binding environments

envstack uses a late-binding environment model.

Environments are resolved at activation time, not at install time, build time, or package-resolution time.

This means:

This model contrasts with:

envstack treats environments as live configuration, composed explicitly each time they are activated.

Late binding is a deliberate design choice. It favors:

envstack provides policy-defined isolation rather than runtime-enforced isolation, relying on explicit configuration and directory layout.

Opinionated by design

envstack is intentionally opinionated:

These constraints exist to keep complex environments understandable at scale.

Summary

envstack exists to make hierarchical environment configuration:

It embraces complexity where it exists, rather than hiding it behind implicit behavior.