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 value

Environment 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 src

Example: 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_KEY

Example: User-Specific Paths

1resources:
2  cache-mounts:
3    mounts:
4      - source: ${{ env.HOME }}/.cache/models
5        target: /home/shai/.cache/models
6        mode: rw

Behavior

  • Missing variables cause failure: If OPENAI_API_KEY is 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
Best Practice: Always use ${{ 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 src

Example: 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 infrastructure

Multiple 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 --var flag
  • Missing vars: Cause config loading to fail

Configuration Variables

Reference resolved configuration values with ${{ conf.NAME }}.

Available Variables

VariableDescriptionDefault
TARGET_USERThe resolved target usershai
WORKSPACEThe 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: rw

With 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: ro

With 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 type field
  • The version field
  • 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_VAR

Missing 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.TAG

Solution

Ensure all referenced variables are defined:

1export MISSING_VAR=value
2shai --var TAG=v1.0.0 -rw src

Best Practices

✅ Do

Use env vars for secrets:

1vars:
2  - source: DATABASE_PASSWORD

Use vars for parameterization:

1image: ghcr.io/app:${{ vars.VERSION }}

Use conf vars for user-specific paths:

1target: /home/${{ conf.TARGET_USER }}/.config

Fail 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_KEY

Don’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 instead

Don’t rely on default values:

1# ❌ Bad - assumes HOME is set (it usually is, but be explicit)
2# ✅ Good - document required env vars in README

Common 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 infrastructure

User-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: rw

Dynamic 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 --verbose

Output shows:

  • Raw config before expansion
  • Expanded config after template processing
  • Which variables were used

Next Steps