Appearance
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: productionIf 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 optionallyregion) 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>/, andkeel auth setup/joinmust 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:
| Section | Merge rule |
|---|---|
database, cache, domain | field by field; unset fields inherit the base |
services.<name> | field by field |
services.<name>.environment, top-level environment, tags | merged per key; the override wins |
secrets (app or service) | union by name; order follows the base |
bindings | merged by resource; an override replaces that one entry |
permissions | appended |
resources | merged per resource name |
release, waf | replaced 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:
--env <name>flagKEEL_ENVenvironment variable- The active environment set with
keel env use <name>(persisted under~/.keel) default_environmentinkeel.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 commandAdding 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 111111111111Adding 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.