Skip to content

External dependencies

Status: earlier design, 10 September 2026. The dependency categories and secret references below inform the workflow runtime plan. Its invocation plugin contract and Plan readiness requirements supersede this note’s original extensibility scope. Delivery is now scheduled in M3. Examples below are proposed, not implemented commands.

A stack rarely stands alone. It needs a payments sandbox, a real bucket, an auth provider, or an emulator such as LocalStack. Today the only lever is stack.env_files, which mixes secrets with configuration, says nothing about which environment gets which credentials, and cannot be enabled or checked per environment.

The manifest gains a dependencies list. Each entry names one external system, says how an environment obtains credentials for it, and says which services are injected with them. There are three kinds.

KindMeaningExample
externalA real system reached over the network with per-environment credentials.Stripe test account, a real S3 bucket with a per-env prefix, Auth0 tenant
emulatorA stand-in that runs as extra compose services inside the environment.LocalStack, a mail catcher, a fake OIDC provider
execA script in the repo that produces the variables. The escape hatch for bespoke systems.Minting a short-lived token from an internal service
dependencies:
- name: stripe
kind: external
enabled: true
secrets:
STRIPE_SECRET_KEY: ssm:/envctl/{project}/{env}/stripe/secret_key
STRIPE_WEBHOOK_SECRET: ssm:/envctl/{project}/shared/stripe/webhook_secret
inject:
services: [api, worker]
probe:
http: https://api.stripe.com/v1/balance
auth: bearer:STRIPE_SECRET_KEY
- name: aws
kind: emulator
compose: deploy/local/localstack.compose.yml
inject:
services: [api, worker, bootstrap]
env:
METERGRAPH_CLOUD: localstack
seed: dataset # the dataset service restores this emulator's state
- name: internal-token
kind: exec
command: scripts/envctl-token.sh
inject:
services: [api]
PrefixResolved fromWorks
ssm:AWS Systems Manager Parameter Store, with {project} and {env} templatedlocal with AWS credentials, vm through the instance role
env:the developer’s shelllocal only
file:a gitignored file relative to the repo rootlocal only

Resolved values are written to .envctl/<env>/secrets.env with mode 0600 and referenced from the rendered project through env_file. They never appear in compose.yaml. On a VM the file is delivered over SSM together with the rendered project. A dependency that cannot resolve a secret fails envctl up with the missing key named, before any container starts.

Terminal window
envctl dep list # name, kind, enabled, resolved?, last probe
envctl dep enable stripe # per environment; recorded in env.json
envctl dep disable stripe
envctl dep check # run every probe, non-zero if any fails
envctl up --with stripe # enable for this run only

envctl status --json gains a dependencies array with the same fields as dep list, so an agent can see whether the environment it is about to test against actually has its external systems.

An emulator is a compose file included in the render when the dependency is enabled, so it is namespaced, labelled, and torn down like any other service. The dataset service treats an emulator with seed: dataset as part of the environment’s state: snapshot exports it and seed restores it. LocalStack today becomes the first emulator entry rather than a hardcoded part of the stack.

The workflow design expands this scope to versioned invocation plugins with structured preparation, probing, execution, renewal, and cleanup. A plugin can be attached during Plan without changing project defaults. Dependencies must be resolved and verified for downstream stages before Plan completes. The existing external/emulator/exec categories can be implemented through that protocol; a script producing variables alone does not establish readiness. See Plugins belong to invocations.

  • manifest gains the dependencies type with validation.
  • compose.Render learns to include emulator files and attach an env_file per injected service.
  • A new internal/secrets package resolves the three prefixes.
  • The env file gains ENVCTL_DEP_<NAME>=enabled|disabled.
  • The VM backend, when it arrives, ships secrets.env alongside compose.yaml; nothing else about dependencies is backend-specific.