Skip to content

keel.yml Reference

Every field of the config file, from internal/config/config.go and its validators. Validation reports all problems at once. Optional sections (database, cache, domain, waf, resources, environments) can be omitted entirely.

keel.yml.example

keel init writes an annotated keel.yml.example alongside your config — every option Keel understands, with notes, kept as a sidecar because Keel's own edits to keel.yml round-trip through the YAML parser. A test suite loads and validates the example, and a reflection test fails if any config field is missing from it, so it cannot drift from this reference.

Top level

KeyTypeRequiredDefaultValidation
namestringyesRFC 1123 hostname
regionstringyesus-east-1non-empty
modeenumnoadvancedbasic | advanced | custom
servicesmapyesat least one; names match ^[a-z][a-z0-9_-]*$, no collisions after -_ normalization
resourcesmapnosee below
databasesectionnosee below
cachesectionnosee below
vpcsectionnosee below
networking.public_tasksbool?nofrom modepointer: unset ≠ false
load_balancer.enabledbool?nofrom modepointer: unset ≠ false
domainsectionnosee below
wafsectionnosee below
pipelinesectionyessee below
releasesectionnosee below
environmentmap[string]stringnomerged into every service; service-level wins
secretslist of stringnoadded to every service (union by name)
logs.retention_daysintno14a CloudWatch-accepted value (1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827, 2192, 2557, 2922, 3288, 3653) or -1 for never
tagsmap[string]stringnoapplied to every managed AWS resource (ManagedBy=keel is never overridable). Keys that differ only in case — from Keel's App/Environment/ManagedBy tags or from each other — are rejected: IAM treats tag keys case-insensitively
settingssectionnoboth falseverbose, cautious
environmentsmapnosee below
default_environmentstringnorequired (or resolvable) when multiple environments are declared

Mode defaults

Modeload_balancer.enablednetworking.public_tasksvpc.nat_gateway
basicfalsetruenone
advanced (default)truefalsesingle
customtruefalsesingle

Explicit settings always win over the mode default.

services.<name>

KeyTypeRequiredDefaultValidation
typeenumyesweb | worker | scheduled
portintweb onlyweb services must specify a port
health_checkstring or blockweb onlyweb services must specify a path; timeout < interval
dockerfilestringnoDockerfile
commandstring or listnoimage CMDargv, not shell; scalar split on whitespace honoring quotes; shell metacharacters (| & ; < > ( ) $ \``) rejected — use ["sh", "-c", "..."]`
cpuintno256256 | 512 | 1024 | 2048 | 4096
memoryintno512required
desired_countintno1
autoscalingblocknomin ≥ 1, max > min, target_cpu 1–100
environmentmapnowins over app-level
secretslistnounion with app-level
bindingslistnosee below
permissionslistnoeach needs actions and resources; effect defaults to Allow

Only one type: web service may exist — path-based routing is not implemented, so a second one is rejected at validation.

scheduled is not fully implemented

Validation accepts type: scheduled, but no schedule is provisioned yet — the service runs continuously like a worker.

health_check block

KeyDefaultRange
path/required for web
grace_period600+ seconds before ECS may replace a new task
interval305–300
timeout52–120; must be < interval
healthy_threshold32–10
unhealthy_threshold32–10
matcher"200-299"HTTP code range
deregistration_delay300–3600

vpc

KeyDefaultValidation
cidr10.0.0.0/16
availability_zones22 or 3
nat_gatewaysingle (none in basic mode)single | per_az | none
private_aws_endpointsfalsenat_gateway: none with private tasks requires this to be true

database

KeyRequiredDefaultValidation
engineyespostgres | mysql | aurora-postgres | aurora-mysql
versionyesnon-empty
instanceyesnon-empty
namenokeeldb^[A-Za-z][A-Za-z0-9_]*$
usernamenokeelusersame pattern; not a reserved name (admin, root, postgres, …)
storageyes20≥ 20
multi_aznofalsepointer: an environment override that omits it inherits the base
retainnotruedeletion protection + guaranteed final snapshot
backup_retention_daysno70–35; 0 requires retain: false
backup_windownoAWS choosese.g. "04:00-05:00"

cache

KeyRequiredDefaultValidation
engineyesvalkey | redis
versionno
node_typeyesnon-empty
num_nodesno1

The endpoint is TLS-only; use the injected CACHE_URL / REDIS_URL (rediss://).

domain

KeyRequiredValidation
nameyesFQDN
zoneyesFQDN

Requires pipeline.source to be set and a load balancer (the DNS alias record points to the ALB).

waf

KeyDefault
enabledfalse
managed_rules— (CLI default when enabling: AWSManagedRulesCommonRuleSet)

Requires a load balancer.

pipeline

KeyRequiredDefaultValidation
sourceyesgithub | codecommit
repogithub: yes; codecommit: nosee accepted forms below
branchnomain
connection_arnnoresolved from SSMgithub only; a CodeConnections connection ARN
build_imagenoaws/codebuild/amazonlinux-x86_64-standard:5.0an image whose name marks the wrong architecture for the CodeBuild environment (aarch64/arm64 vs x86_64/amd64) is rejected at validation; unmarked custom images are accepted

Accepted repo forms: owner/name, https://github.com/owner/name[.git], [email protected]:owner/name.git (github); a bare name or the full git-codecommit URL (codecommit). source: codecommit with repo unset means Keel creates and owns a repository named <app>-<env>.

release

Runs once per deploy, before any service takes traffic. Shorthand forms:

yaml
release: bundle exec rails db:migrate
release: ["sh", "-c", "rails db:migrate && rails db:seed"]
release:
  command: bundle exec rails db:migrate
  service: web
  timeout: 20m
KeyRequiredDefaultValidation
commandyes (when block present)same argv rules as service command
servicenothe sole web service, or the sole servicemust name a service
timeoutno15mpositive Go duration

resources

Each entry declares exactly one of create (Keel owns it) or existing (Keel binds it). type: s3 | sns | sqs | rds | cache | iam-role. Only s3/sns/sqs can be created. retain defaults to true for managed resources.

create options: versioning (s3), encryption (s3, aws-managed), fifo (sns/sqs), visibility_timeout (sqs, 0–43200). A managed s3 resource name cannot contain _.

existing required fields by type: arn (s3/sns/iam-role); arn + url (sqs); endpoint + port + security_group_id (rds/cache). Optional: identifier, secret_arn, secret_env (must match ^[A-Z_][A-Z0-9_]*$), kms_key_arn.

Bindings

yaml
services:
  web:
    bindings:
      - resource: uploads
        access: [list, read, write]
        prefix: user-content   # S3 only
        env: UPLOADS           # override the injected variable base

Capabilities per type: s3 list, read, write, delete; sns publish; sqs send, consume; rds/cache connect; iam-role assume.

environments

Each entry overrides the base config; every field optional. Overridable: region, account, services, resources, database, cache, domain, release, environment, secrets, tags. Declaring account opts the environment into account isolation.

Merge rules: database/cache/domain and services.<name> merge field by field; environment and tags merge per key (override wins); secrets union by name; bindings merge by resource; permissions append; resources merge per name; release is replaced whole.

Selection precedence: --envKEEL_ENVkeel env usedefault_environment. With no environments: block, a single synthetic default environment is used.

Derived names

ConceptValue
Name prefix<app>-<env>
ECS cluster<app>-<env>-cluster
ECS service<app>-<env>-<service>
Log group/ecs/<app>-<env>
Config vars/keel/<app>/<env>/<key>
Database identifier<app>-<env>-db
Cache replication group<app>-<env>-cache
ECR repository<app>-<env> (shared; images tagged <service>-<sha>)
CodeBuild project<app>-<env>-build (per Dockerfile)
OpenTofu state key<app>/<env>/terraform.tfstate

Worked example

The Rails web+worker config that docs/deploying-rails.md walks through lives at internal/config/testdata/rails-web-worker.yml and is loaded, validated, and HCL-generated by the test suite — the docs cannot drift from it. See Deploying Rails for the annotated version.

Keel — the AWS CLI you've always wanted.