Skip to main content

Variables Overview

Variables provide dynamic configuration values that can be reused throughout your Xec configuration. They enable flexible, DRY (Don't Repeat Yourself) configurations that adapt to different environments and contexts.

Variable Types​

Simple Variables​

vars:
# Strings
appName: myapp
environment: production

# Numbers
port: 8080
replicas: 3
timeout: 30000

# Booleans
debug: false
enableCache: true

Complex Variables​

vars:
# Objects
database:
host: db.example.com
port: 5432
name: production_db
credentials:
username: dbuser
password: ${secrets.db_password}

# Arrays
servers:
- web1.example.com
- web2.example.com
- web3.example.com

# Mixed structures
config:
features:
- name: feature-a
enabled: true
- name: feature-b
enabled: false
settings:
maxConnections: 100
timeout: 30s

Variable Interpolation​

Basic Interpolation​

vars:
name: myapp
version: "1.2.3"
tag: "${name}:${version}" # Result: myapp:1.2.3

tasks:
deploy:
command: docker run ${tag}

Nested Variables​

vars:
env: production
region: us-east-1
endpoint: "https://api.${region}.example.com/${env}"

database:
host: db.example.com
port: 5432
url: "postgres://${database.host}:${database.port}/mydb"

Array Access​

vars:
servers:
- primary.example.com
- secondary.example.com

primaryServer: ${servers[0]}
backupServer: ${servers[1]}

Variable Sources​

1. Configuration Variables​

Defined in vars section:

vars:
appName: myapp
version: "2.0.0"

2. Environment Variables​

Access system environment:

vars:
home: ${env.HOME}
user: ${env.USER}
customPath: ${env.CUSTOM_PATH}

# With defaults
apiUrl: ${env.API_URL:-http://localhost:3000}
logLevel: ${env.LOG_LEVEL:-info}

3. Secrets​

Access secure values:

vars:
apiKey: ${secrets.api_key}
dbPassword: ${secrets.database_password}
sshKey: ${secrets.deploy_key}

4. Task Parameters​

Access task parameters:

tasks:
deploy:
params:
- name: version
required: true
command: |
docker pull myapp:${params.version}
docker run myapp:${params.version}

5. Runtime Variables​

Dynamic values during execution:

tasks:
info:
command: |
echo "Profile: ${profile}"
echo "Target: ${target.name}"
echo "Task: ${task.name}"
echo "Date: ${runtime.date}"

Variable Scope​

Global Scope​

Available everywhere:

vars:
globalVar: "available-everywhere"

tasks:
use-global:
command: echo ${globalVar}

profiles:
prod:
vars:
url: "https://${globalVar}.example.com"

Profile Scope​

Override global variables:

vars:
environment: development

profiles:
production:
vars:
environment: production # Overrides global

tasks:
show-env:
command: echo ${environment} # Uses profile value

Task Scope​

Task-specific variables:

tasks:
scoped:
vars:
taskVar: "only-in-this-task"
command: echo ${taskVar}

other:
command: echo ${taskVar} # Error: not defined

Step Scope​

Step-level variables:

tasks:
multi-step:
steps:
- command: echo "test"
register: output

- command: echo "Result: ${output.stdout}"
# output only available after registration

Default Values​

Using Defaults​

vars:
# With pipe operator
port: ${env.PORT:-8080}

# Nested defaults
database:
host: ${env.DB_HOST:-localhost}
port: ${env.DB_PORT:-5432}
name: ${env.DB_NAME:-development}

Conditional Defaults​

vars:
environment: ${env.ENV:-development}

# Different defaults per environment
apiUrl: |
${environment == 'production'
? 'https://api.example.com'
: 'http://localhost:3000'}

Variable Functions​

String Functions​

vars:
name: "My App"

# String manipulation
lower: ${name.toLowerCase()} # my app
upper: ${name.toUpperCase()} # MY APP
slug: ${name.replace(' ', '-')} # My-App

Array Functions​

vars:
servers:
- web1
- web2
- web3

serverCount: ${servers.length} # 3
firstServer: ${servers[0]} # web1
lastServer: ${servers[-1]} # web3
serverList: ${servers.join(',')} # web1,web2,web3

Object Functions​

vars:
config:
host: localhost
port: 8080

configKeys: ${Object.keys(config)} # ['host', 'port']
configValues: ${Object.values(config)} # ['localhost', 8080]

Computed Variables​

Simple Computation​

vars:
base: 8080
offset: 100
port: ${base + offset} # 8180

replicas: 3
totalReplicas: ${replicas * 2} # 6

Complex Computation​

vars:
environment: production
region: us-east-1

# Computed URL
apiEndpoint: |
https://api-${environment}.${region}.example.com

# Conditional computation
replicas: |
${environment === 'production' ? 5 : 1}

Variable Validation​

Type Validation​

vars:
port: ${env.PORT} # Must be number

tasks:
validate:
script: |
if (typeof port !== 'number') {
throw new Error('Port must be a number');
}

Required Variables​

vars:
required: ${env.REQUIRED_VAR} # Fails if not set
optional: ${env.OPTIONAL_VAR:-default} # Has default

Variable Resolution Order​

Variables are resolved in this precedence:

  1. Command-line - Highest priority
  2. Environment variables - XEC_VARS_*
  3. Profile variables - Active profile
  4. Task parameters - Task-specific
  5. Global variables - Config vars
  6. Defaults - Lowest priority

Advanced Patterns​

Variable Templates​

vars:
template:
url: "https://${service}.${environment}.example.com"

services:
api: ${template.url.replace('${service}', 'api')}
web: ${template.url.replace('${service}', 'web')}

Dynamic Variable Loading​

tasks:
load-config:
script: |
const config = await loadExternalConfig();
xec.setVars({
dynamicVar: config.value,
computedVar: config.computed
});

Variable Inheritance​

vars:
base:
timeout: 30000
retries: 3

extended:
$merge: [base]
timeout: 60000 # Override
newProp: value # Add new

Escaping Variables​

Literal Dollar Signs​

vars:
# Escape with backslash
literal: "Price: \$100"

# Or use single quotes
command: 'echo $HOME' # Not interpolated

Preventing Interpolation​

tasks:
no-interpolation:
command: |
# Use $$ for literal $
echo "$$HOME"

# Or disable interpolation
$raw: true
command: echo $HOME ${var}

Best Practices​

1. Use Descriptive Names​

# Good
vars:
apiEndpoint: https://api.example.com
maxRetries: 3

# Bad
vars:
url: https://api.example.com
n: 3
vars:
database:
host: db.example.com
port: 5432
name: myapp

cache:
host: cache.example.com
port: 6379

3. Provide Defaults​

vars:
# Always provide sensible defaults
port: ${env.PORT:-8080}
environment: ${env.NODE_ENV:-development}

4. Document Variables​

vars:
# Maximum number of retry attempts for API calls
maxRetries: 3

# API endpoint URL (must include protocol)
apiUrl: https://api.example.com

5. Validate Early​

tasks:
validate-config:
script: |
// Validate required variables
const required = ['apiKey', 'dbPassword'];
for (const key of required) {
if (!vars[key]) {
throw new Error(`Missing required variable: ${key}`);
}
}

Common Issues​

Circular References​

# This causes infinite loop
vars:
a: ${b}
b: ${a} # Error: circular reference

Undefined Variables​

vars:
# This fails if MISSING is not defined
value: ${env.MISSING}

# Use default to prevent failure
value: ${env.MISSING:-default}

Type Mismatches​

vars:
port: "8080" # String

tasks:
connect:
# May fail if expecting number
command: connect --port ${port}

# Better: ensure correct type
command: connect --port ${parseInt(port)}

Debugging Variables​

Show Resolved Values​

# Show all variables
xec config show --vars

# Show specific variable
xec config get vars.database.host

# Debug interpolation
xec --debug run task

Trace Resolution​

tasks:
debug-vars:
script: |
console.log('All vars:', vars);
console.log('Environment:', env);
console.log('Profile:', profile);

Next Steps​

See Also​