Skip to content

Environments

An app owns any number of environments (e.g. staging, production), and every Keel command targets exactly one (app, environment) pair. Environments are declared under environments: in keel.yml; each environment's effective config is the app-level base config deep-merged with that environment's overrides.

yaml
name: acme-api
region: us-east-1                 # app default region

environments:
  staging:
    # no account override → shares the app's account, isolated by name prefix
    services:
      web:
        desired_count: 1

  production:
    region: us-east-1
    account: "111111111111"       # opts into a separate AWS account
    services:
      web:
        desired_count: 3
        cpu: 1024
    database:
      instance: db.r6g.large

default_environment: production

If environments: is omitted, Keel operates on a single synthetic default environment.

Isolation models

Two isolation models are supported, selectable per environment:

  • Namespace isolation (default) — environments live side-by-side in one AWS account, separated by an <app>-<env> name prefix. Cheap and fast. Credentials are shared per app: the Keel roles are account-level, so switching between two same-account environments never asks you to log in again.
  • Account isolation — an environment that declares an account (and optionally region) targets its own AWS account/region, the Well-Architected recommendation for production. An account-isolated environment keeps its own cached credentials under ~/.keel/contexts/<app>/<env>/, and keel auth setup/join must be run per account.

Every resource name, OpenTofu state key, SSM path, log group, and registry entry is qualified by <app>-<env>, so environments never collide.

How overrides merge

Overrides are merged field by field, so a block only has to name what differs:

SectionMerge rule
database, cache, domainfield by field; unset fields inherit the base
services.<name>field by field
services.<name>.environment, top-level environment, tagsmerged per key; the override wins
secrets (app or service)union by name; order follows the base
bindingsmerged by resource; an override replaces that one entry
permissionsappended
resourcesmerged per resource name
release, wafreplaced whole

So a database: instance: override keeps the base block's engine, version, storage, and multi_az. This matters most for booleans and counts: before Keel merged these blocks, an override that omitted multi_az silently turned off the standby, and one that omitted num_nodes silently dropped a cache replica — in both cases the config still validated and keel up still succeeded.

Selecting an environment

Precedence, highest first:

  1. --env <name> flag
  2. KEEL_ENV environment variable
  3. The active environment set with keel env use <name> (persisted under ~/.keel)
  4. default_environment in keel.yml
bash
keel env list                    # list environments and see which one is active
keel env use staging             # persist the active environment for this app
keel env current                 # print the environment the current context targets
keel deploy web --env production # override for a single command

Adding an environment

keel env add declares a new environment by editing keel.yml in place — comments and key order survive:

bash
keel env add staging
keel env add production --env-region us-east-1 --account 111111111111

Adding the first environment to a config that declares none also writes out the implicit default environment it has been using and makes it the default, so the stack that already exists keeps its name. Nothing is provisioned until keel up --env <name>. The dashboard's environment picker (e) can do the same.

Keel — the AWS CLI you've always wanted.