DOC_INDEX
THEME
DOCS/Learn OrchStep/OrchShell: dotenv++ for applications

OrchShell: dotenv++ for applications

OrchShell (orchstep do) runs any command or script with your environment's application config — structured per-env vars and dotenv — no orchstep.yml required.

OrchShell

OrchShell is orchstep do pointed at a folder of environment files. It's dotenv++ for applications: dotenv hands your process flat KEY=value strings; OrchShell additionally hands your commands structured application config — whole objects, per environment, layered by group — and then runs them.

No orchstep.yml. No task. Just environment files and a command.

orchstep do --env prod-eu 'helm upgrade checkout ./chart \
  --set image={{ env.REGISTRY }}/checkout:{{ vars.version }} \
  --set replicas={{ vars.replicas }} \
  --set resources.cpu={{ vars.resources.cpu }} \
  --set ingress.host={{ vars.ingress.host }}'

A project with no orchstep.yml

OrchShell discovers a conventional environments/ directory (rich application vars) and the dotenv files declared in orchstep_config.yml (primitive env vars):

environments/defaults.yml
# Always loaded. Values can be whole OBJECTS, not just strings.
app_name: checkout
version: "1.4.2"
replicas: 1
resources:
  cpu: "250m"
  memory: 256Mi
ingress:
  host: localhost
  tls: false

There is no orchstep.yml in that folder. OrchShell builds the context from the files it finds.

How the layering works

Environment files merge hierarchically by name. Asking for prod-eu loads:

defaults.yml  ->  prod.yml (group)  ->  prod-eu.yml (environment)

Later files win; objects replace (they don't deep-merge). So for --env prod-eu:

VariableResolved valueFrom
vars.app_namecheckoutdefaults
vars.version1.4.2defaults
vars.replicas6prod (group)
vars.resources.cpu2prod (group)
vars.ingress.hosteu.checkout.example.comprod-eu
vars.regioneu-west-1prod-eu
env.REGISTRYregistry.example.comcommon.env (via orchstep_config.yml)

Why this beats plain dotenv

dotenv/direnv give you export KEY=value — flat strings only. To pass an application a structure (resources: {cpu, memory}) you'd flatten it into a dozen RESOURCES_CPU=… variables and reassemble them downstream.

OrchShell keeps the structure. Your command references {{ vars.resources.cpu }} and {{ vars.ingress.host }} directly, with real per-environment layering — and still gets the flat env vars ($REGISTRY) from the declared dotenv for free. It's the config layer your apps actually want, available to anything you can run in a shell.

Profile dotenv files — declared in orchstep_config.yml

The classic layered dotenv pattern — a shared file, a per-profile file, and an optional local-secrets file — is declared once in orchstep_config.yml's dotenv: list (shown in the file tree above), not passed on the CLI:

# orchstep_config.yml
dotenv:
  - common.env                          # shared by every profile (always)
  - "deploy_{{ vars.environment }}.env"  # the selected profile
  - "secrets.local.env?"                 # local-only, git-ignored, optional (?)

--env dev loads deploy_dev.env; --env prod loads deploy_prod.env. The ? suffix skips a file that isn't there.

Two things make this work:

  • It loads after --env. The config's dotenv: is applied once the environment is selected, so a path keyed on the selection (deploy_{{ vars.environment }}.env) resolves. (A workflow's own dotenv: loads earlier, before --env, so use the config for profile selection.)
  • It's declared, not magic. OrchStep does not auto-load a stray .env from the current directory — loading only what a committed config file declares is a trust boundary, so a directory you didn't write can't slip env vars (like a poisoned PATH) into the commands you run.

Use it

# One-off, against an environment:
orchstep do --env prod-eu 'echo deploying to {{ vars.ingress.host }}'

# A whole script (or make it executable with the shebang above):
./deploy

# Preview the expansion without running it:
orchstep do --env prod-us --render 'kubectl scale --replicas={{ vars.replicas }} deploy/checkout'

# Interactive — drop into a shell where the config is already loaded:
orchstep do --env prod-eu
# orchstep> echo {{ vars.region }} -> {{ vars.ingress.host }}
# eu-west-1 -> eu.checkout.example.com

See also

  • Ad-hoc Commands — the full orchstep do reference (all input modes, REPL, exit codes).
  • Environments — the hierarchical external-file format in depth.