Context Inheritance
Avoid repeating configuration by using inheritance. Create base contexts with shared settings, then extend them for specific environments.
Basic Inheritance
Base context (~/.config/ctx/contexts/acme-base.yaml):
name: acme-base
abstract: true # Cannot be used directly, only extended
description: Base config for ACME Corp
aws:
region: us-east-1
kubernetes:
context: acme-eks-cluster
ssh:
bastion:
host: bastion.acme.com
user: deploy
identity_file: ~/.ssh/acme_rsa
env:
COMPANY: acme
TEAM: platform
urls:
jira: https://acme.atlassian.net
wiki: https://wiki.acme.com
Child contexts extend the base:
# acme-dev.yaml
name: acme-dev
extends: acme-base
environment: development
aws:
profile: acme-dev # Adds to inherited aws config
kubernetes:
namespace: dev # Adds to inherited kubernetes config
env:
LOG_LEVEL: debug # Merged with inherited env vars
urls:
app: https://dev.acme.com # Merged with inherited URLs
# acme-prod.yaml
name: acme-prod
extends: acme-base
environment: production
aws:
profile: acme-prod-admin
kubernetes:
namespace: production
vpn:
type: openvpn
config_file: ~/.config/ctx/acme-prod.ovpn
auto_connect: true
env:
LOG_LEVEL: warn
How Inheritance Works
Deep merge with child values taking precedence:
| Field Type | Behavior |
|---|---|
| Strings | Child overrides parent if non-empty |
| Booleans | Parent's true is inherited (see note below) |
| Sections (aws, kubernetes, etc.) | Deep merged field-by-field |
| Maps (env, urls) | Merged - child values override matching keys |
| Slices (tunnels, databases) | Child replaces parent if non-empty |
| Tags | Merged and deduplicated |
Boolean Inheritance
Due to how YAML/Go works, a parent's use_vault: true cannot be overridden to false by a child context. Boolean fields should only be set to true at the level where they're actually needed, not in base contexts.
Multi-Level Inheritance
Contexts can extend other contexts that also extend a base:
# acme-base.yaml (abstract)
name: acme-base
abstract: true
aws:
region: us-east-1
# acme-prod-base.yaml (abstract)
name: acme-prod-base
abstract: true
extends: acme-base
environment: production
vpn:
type: openvpn
auto_connect: true
# acme-prod-us.yaml (usable)
name: acme-prod-us
extends: acme-prod-base
aws:
profile: acme-prod-us
vpn:
config_file: ~/.config/ctx/acme-us.ovpn
Abstract Contexts
Mark contexts as abstract: true to:
- Hide from
ctx list- Only show usable contexts - Prevent direct use -
ctx use acme-basereturns an error - Document intent - Clear that it's a template
# Lists only usable contexts
ctx list
NAME ENVIRONMENT CLOUD ORCHESTRATION
acme-dev development aws kubernetes
acme-prod production aws kubernetes
# Shows all including abstract (marked with ~)
ctx list --all
NAME ENVIRONMENT CLOUD ORCHESTRATION
~ acme-base - aws kubernetes
acme-dev development aws kubernetes
acme-prod production aws kubernetes
# Cannot use abstract context
ctx use acme-base
# Error: context 'acme-base' is abstract and cannot be used directly
# Show displays merged configuration
ctx show acme-prod
# Name: acme-prod
# Extends: acme-base
# Environment: production
# ...
Example: Multi-Region Setup
# company-base.yaml
name: company-base
abstract: true
aws:
sso_login: true
kubernetes:
namespace: default
env:
COMPANY: acme
# company-us.yaml
name: company-us
abstract: true
extends: company-base
aws:
region: us-east-1
# company-eu.yaml
name: company-eu
abstract: true
extends: company-base
aws:
region: eu-west-1
# company-us-prod.yaml
name: company-us-prod
extends: company-us
environment: production
aws:
profile: acme-us-prod
# company-eu-prod.yaml
name: company-eu-prod
extends: company-eu
environment: production
aws:
profile: acme-eu-prod
This creates a hierarchy:
company-base (abstract)
├── company-us (abstract)
│ ├── company-us-dev
│ ├── company-us-staging
│ └── company-us-prod
└── company-eu (abstract)
├── company-eu-dev
├── company-eu-staging
└── company-eu-prod
Variable Expansion
Combine inheritance with ${VAR} expansion for maximum reuse. Variables defined in env: are expanded in all config string fields after inheritance is resolved.
This is ideal when managing many clusters that follow the same pattern:
# acme-cloud.yaml (abstract base)
name: acme-cloud
abstract: true
cloud: acme-cloud
environment: production
kubernetes:
context: "acme-cloud-${CLUSTER_NAME}-cluster"
kubeconfig: "~/clusters/acme-cloud/${CLUSTER_NAME}/config"
namespace: default
tunnels:
- name: bastion
remote_host: "bastion.${CLUSTER_NAME}.acme-cloud.example.com"
remote_port: 8080
local_port: 8080
urls:
dashboard: "https://${CLUSTER_NAME}.example.com"
argocd: "https://argocd.${CLUSTER_NAME}.example.com"
Each cluster context is just 4 lines:
# alpha.yaml
name: alpha
extends: acme-cloud
env:
CLUSTER_NAME: alpha
# beta.yaml
name: beta
extends: acme-cloud
env:
CLUSTER_NAME: beta
# gamma.yaml
name: gamma
extends: acme-cloud
env:
CLUSTER_NAME: gamma
After ctx use alpha, all ${CLUSTER_NAME} references resolve to alpha:
kubernetes.context→acme-cloud-alpha-clusterkubernetes.kubeconfig→~/clusters/acme-cloud/alpha/configtunnels[0].remote_host→bastion.alpha.acme-cloud.example.comurls.dashboard→https://alpha.example.com
Rules:
- Only
${VAR}syntax is supported - Variables come from
env:map only (not OS environment) - Undefined variables are left as
${VAR}(no error) name,extends, andenvvalues themselves are not expanded