Template Expansion
Shai config files support template variables for dynamic configuration.
Overview
Templates allow you to:
- Reference environment variables
- Parameterize configs with CLI flags
- Use resolved configuration values
- Avoid hardcoding secrets
Template Syntax
Templates use the ${{ ... }} syntax:
1${{ env.VARIABLE_NAME }} # Host environment variable
2${{ vars.VARIABLE_NAME }} # CLI-provided variable
3${{ conf.VARIABLE_NAME }} # Resolved configuration valueEnvironment Variables
Reference host environment variables with ${{ env.NAME }}.
Example: Dynamic Image Selection
1image: ${{ env.DEV_IMAGE }}1export DEV_IMAGE=ghcr.io/my-org/dev:latest
2shai -rw srcExample: API Keys
1resources:
2 api-access:
3 vars:
4 # target is optional - omit when name stays the same
5 - source: OPENAI_API_KEY
6 - source: ANTHROPIC_API_KEYExample: User-Specific Paths
1resources:
2 cache-mounts:
3 mounts:
4 - source: ${{ env.HOME }}/.cache/models
5 target: /home/shai/.cache/models
6 mode: rwBehavior
- Missing variables cause failure: If
OPENAI_API_KEYis not set, config loading fails - This is intentional: You catch missing credentials early instead of at runtime
- Use for secrets: Never hardcode credentials in config files
${{ env.* }} for credentials instead of hardcoding them.User Variables
Provide variables via the --var flag with ${{ vars.NAME }}.
Example: Parameterized Image Tags
1image: ghcr.io/my-org/dev:${{ vars.TAG }}1shai --var TAG=v1.2.3 -rw srcExample: Branch-Specific Configuration
1resources:
2 deployment:
3 calls:
4 - name: deploy
5 description: Deploy to environment
6 command: /usr/local/bin/deploy.sh
7 allowed-args: '^--branch=${{ vars.BRANCH }}$'1shai --var BRANCH=main -rw infrastructureMultiple Variables
1shai --var TAG=v2.0.0 --var ENV=staging --var REGION=us-east-1 1image: ghcr.io/my-org/app:${{ vars.TAG }}
2
3resources:
4 deployment:
5 vars:
6 - source: AWS_CREDENTIALS
7 calls:
8 - name: deploy
9 description: Deploy application
10 command: /usr/local/bin/deploy.sh
11 allowed-args: '^--env=${{ vars.ENV }} --region=${{ vars.REGION }}$'Behavior
- Syntax:
--var KEY=VALUE - Multiple vars: Repeat
--varflag - Missing vars: Cause config loading to fail
Configuration Variables
Reference resolved configuration values with ${{ conf.NAME }}.
Available Variables
| Variable | Description | Default |
|---|---|---|
TARGET_USER | The resolved target user | shai |
WORKSPACE | The resolved workspace path | /src |
Example: User Home Directory
1resources:
2 cache:
3 mounts:
4 - source: ${{ env.HOME }}/.npm
5 target: /home/${{ conf.TARGET_USER }}/.npm
6 mode: rwWith default user (shai):
- Target becomes:
/home/shai/.npm
With custom user:
1shai --user developer- Target becomes:
/home/developer/.npm
Example: Workspace-Relative Paths
1resources:
2 tools:
3 mounts:
4 - source: /host/tools
5 target: ${{ conf.WORKSPACE }}/tools
6 mode: roWith default workspace:
- Target becomes:
/src/tools
Restrictions
You cannot use ${{ conf.* }} in the user or workspace fields themselves:
1# ❌ This doesn't work
2user: ${{ conf.TARGET_USER }}
3workspace: ${{ conf.WORKSPACE }}These fields are used to build the conf variables, so they can’t reference them.
Where Templates Work
Templates can be used in most string fields:
✅ Supported
1# Top-level
2image: ${{ env.IMAGE }}
3user: ${{ env.USER }}
4workspace: ${{ env.WORKSPACE }}
5
6# Resource sets - vars
7vars:
8 - source: API_KEY # Uses same name
9 - source: HOST_KEY
10 target: CONTAINER_KEY # Rename if needed
11
12# Resource sets - mounts
13mounts:
14 - source: ${{ env.HOME }}/.cache
15 target: /home/${{ conf.TARGET_USER }}/.cache
16 mode: rw
17
18# Resource sets - calls
19calls:
20 - name: deploy
21 description: Deploy app
22 command: ${{ env.DEPLOY_SCRIPT }}
23 allowed-args: '^--env=${{ vars.ENV }}$'
24
25# Apply rules
26apply:
27 - path: ./
28 image: ${{ env.BASE_IMAGE }}❌ Not Supported
Templates don’t work in:
- Field names/keys
- The
typefield - The
versionfield - Boolean values
- Numeric values
Combining Templates
You can combine multiple template types:
1resources:
2 custom:
3 mounts:
4 # Combines env and conf variables
5 - source: ${{ env.HOME }}/.config/app
6 target: /home/${{ conf.TARGET_USER }}/.config/app
7 mode: rw
8
9 calls:
10 # Combines env and vars
11 - name: deploy
12 description: Deploy to ${{ vars.ENV }}
13 command: ${{ env.SCRIPTS_DIR }}/deploy.sh
14 allowed-args: '^--env=${{ vars.ENV }}$'Error Handling
Missing Environment Variable
Config:
1image: ${{ env.MISSING_VAR }}Error:
Error: Failed to load config
Cause: Template variable not found: env.MISSING_VARMissing User Variable
Config:
1image: ghcr.io/app:${{ vars.TAG }}Command:
1shai -rw src
2# Missing --var TAG=...Error:
Error: Failed to load config
Cause: Template variable not found: vars.TAGSolution
Ensure all referenced variables are defined:
1export MISSING_VAR=value
2shai --var TAG=v1.0.0 -rw srcBest Practices
✅ Do
Use env vars for secrets:
1vars:
2 - source: DATABASE_PASSWORDUse vars for parameterization:
1image: ghcr.io/app:${{ vars.VERSION }}Use conf vars for user-specific paths:
1target: /home/${{ conf.TARGET_USER }}/.configFail fast with required vars:
1# This will fail immediately if API_KEY is missing
2vars:
3 - source: API_KEY❌ Don’t
Don’t hardcode secrets:
1# ❌ Bad - secret in config file
2vars:
3 - source: "sk-1234567890abcdef"
4 target: API_KEY
5
6# ✅ Good - secret from environment
7vars:
8 - source: API_KEYDon’t use templates where they’re not supported:
1# ❌ Bad - templates in boolean values
2options:
3 privileged: ${{ env.PRIVILEGED }}
4
5# ✅ Good - use conditional config files insteadDon’t rely on default values:
1# ❌ Bad - assumes HOME is set (it usually is, but be explicit)
2# ✅ Good - document required env vars in READMECommon Patterns
Multi-Environment Config
1resources:
2 deployment:
3 vars:
4 # Different credentials per environment
5 - source: AWS_ACCESS_KEY_ID
6 - source: AWS_SECRET_ACCESS_KEY
7
8 calls:
9 - name: deploy
10 description: Deploy to ${{ vars.ENV }}
11 command: /usr/local/bin/deploy.sh
12 allowed-args: '^--env=${{ vars.ENV }}$'Usage:
1# Staging
2shai --var ENV=staging -rw infrastructure
3
4# Production
5shai --var ENV=production -rw infrastructureUser-Specific Caching
1resources:
2 caches:
3 mounts:
4 # npm
5 - source: ${{ env.HOME }}/.npm
6 target: /home/${{ conf.TARGET_USER }}/.npm
7 mode: rw
8
9 # cargo
10 - source: ${{ env.HOME }}/.cargo
11 target: /home/${{ conf.TARGET_USER }}/.cargo
12 mode: rw
13
14 # pip
15 - source: ${{ env.HOME }}/.cache/pip
16 target: /home/${{ conf.TARGET_USER }}/.cache/pip
17 mode: rwDynamic Image Selection
1# Development
2image: ${{ env.DEV_IMAGE }}
3
4# Or with fallback via shell default
5# export DEV_IMAGE=${DEV_IMAGE:-ghcr.io/colony-2/shai-mega}Debugging Templates
Use --verbose to see expanded template values:
1shai -rw src --verboseOutput shows:
- Raw config before expansion
- Expanded config after template processing
- Which variables were used
Next Steps
- See Schema Reference for all available fields
- Browse Complete Example for a full annotated config
- Check Examples for real-world configurations