Basic substitution - error if not set
Variable Substitution
Overview
Hyve workflows support full shell variable substitution, allowing you to create dynamic, reusable workflows. Variables can be defined at the workflow level, job level, or loaded from the environment.
Shell Syntax
Standard bash variable syntax (${VAR})
Multiple Sources
Environment, workflow, and job variables
Default Values
Fallback values with ${VAR:-default}
Dynamic
Variables evaluated at runtime
Variable Syntax
Basic Substitution
env: APP_NAME: my-app
jobs: - name: deploy steps: - name: deploy-app command: kubectl apply -f ${APP_NAME}/manifests/With Default Values
steps: - name: deploy command: kubectl apply -f manifests/ -n ${NAMESPACE:-default} # Uses 'default' if NAMESPACE is not setNested Variables
env: REGISTRY: docker.io USERNAME: myuser IMAGE: my-app TAG: v1.0.0
steps: - name: push command: docker push ${REGISTRY}/${USERNAME}/${IMAGE}:${TAG} # Expands to: docker push docker.io/myuser/my-app:v1.0.0Lifecycle Hook Variables
When a workflow is assigned to a cluster lifecycle hook, Hyve automatically injects
variables from the cluster definition before the workflow runs. These are available in
every step without declaring them in spec.inputs:
Definition variables (all hooks)
There’s no fixed, hyve-defined set of provider-specific variables — injection is the same simple convention every module operation gets:
| Variable | Value | Notes |
|---|---|---|
HYVE_CLUSTER_NAME | Cluster name | |
HYVE_CLUSTER_REGION | spec.region | |
HYVE_PARAM_<KEY> | Each spec.params entry, key uppercased | One per param — entirely module-specific, no fixed list |
<key> (as-is) | Each spec.driverOutputs entry | Whatever a prior module operation or beforeCreate step emitted, under whatever name it chose — a module’s own convention, not hyve’s |
Live cluster variables (onCreate, afterCreate, onDelete)
| Variable | Value |
|---|---|
KUBECONFIG | Path to the cluster’s merged kubeconfig |
beforeCreate and afterDelete run without a live cluster — no kubeconfig is available at those points. Anything resembling HYVE_CLUSTER_STATUS/HYVE_CLUSTER_ID is a driverOutputs entry a specific module chose to emit (a common convention, e.g. in the Civo module), not a value hyve itself guarantees or computes.
beforeCreate output variables
A beforeCreate step’s HYVE_KEY=value output lines are captured and merged into spec.driverOutputs before creation proceeds — there’s no fixed list of “supported” output variable names or a mapping to specific spec fields. Whatever key a step outputs becomes available as that same env var name to every later step/operation for this cluster (see the definition-variables row above).
Example — a beforeCreate step that provisions an EKS IAM role and exports it:
steps: - name: create-role script: | ROLE_ARN=$(aws iam create-role --role-name eks-cluster-role \ --assume-role-policy-document file://trust.json \ --query 'Role.Arn' --output text) echo "HYVE_EKS_ROLE_NAME=eks-cluster-role" echo "HYVE_EKS_ROLE_ARN=${ROLE_ARN}"Variable Sources
Variables can come from multiple sources, with a defined priority:
1. Environment Variables (Highest Priority)
Variables from shell environment or .env files
export APP_NAME=my-apphyve workflow run deploy-app2. Job-Level Variables
Variables defined in specific job
jobs: - name: deploy env: NAMESPACE: production steps: - name: deploy command: kubectl apply -f manifests/ -n ${NAMESPACE}3. Workflow-Level Variables
Variables defined in workflow spec
spec: env: APP_NAME: my-app jobs: - name: deploy steps: - name: deploy command: kubectl apply -f ${APP_NAME}/4. Default Values (Lowest Priority)
Fallback values in substitution syntax
command: kubectl apply -f manifests/ -n ${NAMESPACE:-default}Workflow-Level Variables
Define variables that apply to all jobs:
apiVersion: v1kind: Workflowmetadata: name: deploy-appspec: env: APP_NAME: my-app REGISTRY: docker.io/myuser VERSION: v1.0.0 NAMESPACE: production
jobs: - name: build steps: - name: docker-build command: docker build -t ${REGISTRY}/${APP_NAME}:${VERSION} .
- name: deploy steps: - name: kubectl-apply command: kubectl set image deployment/${APP_NAME} ${APP_NAME}=${REGISTRY}/${APP_NAME}:${VERSION} -n ${NAMESPACE}Job-Level Variables
Override or add variables for specific jobs:
spec: env: APP_NAME: my-app REPLICAS: "1"
jobs: - name: deploy-dev env: NAMESPACE: development REPLICAS: "1" steps: - name: deploy command: kubectl scale deployment ${APP_NAME} --replicas=${REPLICAS} -n ${NAMESPACE}
- name: deploy-prod env: NAMESPACE: production REPLICAS: "3" steps: - name: deploy command: kubectl scale deployment ${APP_NAME} --replicas=${REPLICAS} -n ${NAMESPACE}Environment Files
Load variables from .env files:
APP_NAME=my-appREGISTRY=docker.io/myuserVERSION=v1.0.0DOCKER_TOKEN=secret-tokenHyve automatically loads .env files when running workflows.
apiVersion: v1kind: Workflowmetadata: name: deploy-appspec: # No need to define variables here # They're loaded from .env jobs: - name: deploy steps: - name: build command: docker build -t ${REGISTRY}/${APP_NAME}:${VERSION} .# Create .env filecat > .env <<EOFAPP_NAME=my-appVERSION=v2.0.0EOF
# Run workflow (loads .env automatically)hyve workflow run deploy-appDefault Values
Provide fallback values when variables might not be set:
steps: - name: deploy script: | # Use 'default' namespace if NAMESPACE not set kubectl apply -f manifests/ -n ${NAMESPACE:-default}
# Use 'latest' tag if VERSION not set kubectl set image deployment/app app=${REGISTRY}/${APP_NAME}:${VERSION:-latest}
# Use '3' replicas if REPLICAS not set kubectl scale deployment/app --replicas=${REPLICAS:-3}Syntax Variations
${VAR} string ${VAR:-default} string Use default if VAR is unset or empty
${VAR:=default} string Set VAR to default if unset, then substitute
${VAR:?error message} string Show error message if VAR is unset
Common Patterns
Registry Configuration
env: REGISTRY: docker.io REGISTRY_USER: myuser IMAGE_NAME: my-app IMAGE_TAG: v1.0.0
jobs: - name: build-push steps: - name: build command: docker build -t ${REGISTRY}/${REGISTRY_USER}/${IMAGE_NAME}:${IMAGE_TAG} . - name: push command: docker push ${REGISTRY}/${REGISTRY_USER}/${IMAGE_NAME}:${IMAGE_TAG}Multi-Environment Deployment
env: APP_NAME: my-app VERSION: v1.0.0
jobs: - name: deploy-dev env: ENVIRONMENT: development NAMESPACE: dev REPLICAS: "1" steps: - name: deploy script: | kubectl apply -f manifests/ -n ${NAMESPACE} kubectl scale deployment ${APP_NAME} --replicas=${REPLICAS} -n ${NAMESPACE}
- name: deploy-prod env: ENVIRONMENT: production NAMESPACE: prod REPLICAS: "3" steps: - name: deploy script: | kubectl apply -f manifests/ -n ${NAMESPACE} kubectl scale deployment ${APP_NAME} --replicas=${REPLICAS} -n ${NAMESPACE}Dynamic Resource Names
env: APP_NAME: my-app ENVIRONMENT: production REGION: us-east
jobs: - name: create-resources steps: - name: create-namespace command: kubectl create namespace ${APP_NAME}-${ENVIRONMENT}-${REGION} - name: create-deployment command: kubectl create deployment ${APP_NAME}-${ENVIRONMENT} -n ${APP_NAME}-${ENVIRONMENT}-${REGION}Version Management
env: APP_NAME: my-app MAJOR: "1" MINOR: "0" PATCH: "0"
jobs: - name: tag-version steps: - name: build command: docker build -t ${APP_NAME}:${MAJOR}.${MINOR}.${PATCH} . - name: tag-latest command: docker tag ${APP_NAME}:${MAJOR}.${MINOR}.${PATCH} ${APP_NAME}:latest - name: tag-minor command: docker tag ${APP_NAME}:${MAJOR}.${MINOR}.${PATCH} ${APP_NAME}:${MAJOR}.${MINOR}Secret Variables
Load secrets from encrypted database:
apiVersion: v1kind: Workflowmetadata: name: build-pushspec: requirements: secrets: - name: DOCKER_TOKEN provider: docker - name: GITHUB_TOKEN provider: github
env: REGISTRY: docker.io/myuser IMAGE: my-app
jobs: - name: build-push steps: - name: login script: | echo "$DOCKER_TOKEN" | docker login -u myuser --password-stdin
- name: build command: docker build -t ${REGISTRY}/${IMAGE} .
- name: push command: docker push ${REGISTRY}/${IMAGE}Advanced Substitution
Conditional Logic
steps: - name: conditional-deploy script: | if [ "${ENVIRONMENT}" = "production" ]; then kubectl apply -f manifests/production/ else kubectl apply -f manifests/development/ fiArray Variables
env: NAMESPACES: "dev staging prod"
jobs: - name: deploy-all steps: - name: deploy-to-all-namespaces script: | for ns in ${NAMESPACES}; do echo "Deploying to $ns" kubectl apply -f manifests/ -n $ns doneString Manipulation
env: IMAGE_FULL: "docker.io/myuser/my-app:v1.0.0"
jobs: - name: extract-version steps: - name: get-version script: | # Extract version from image tag VERSION=${IMAGE_FULL##*:} echo "Version: $VERSION"Complete Example
apiVersion: v1kind: Workflowmetadata: name: complete-deployment description: Complete deployment with variable substitutionspec: requirements: tools: - name: docker - name: kubectl secrets: - name: DOCKER_TOKEN provider: docker
env: # Registry configuration REGISTRY: docker.io REGISTRY_USER: myuser
# Application configuration APP_NAME: my-app VERSION: v1.0.0
# Deployment configuration NAMESPACE: production REPLICAS: "3"
jobs: - name: build steps: - name: docker-login script: | echo "$DOCKER_TOKEN" | docker login ${REGISTRY} -u ${REGISTRY_USER} --password-stdin
- name: docker-build command: docker build -t ${REGISTRY}/${REGISTRY_USER}/${APP_NAME}:${VERSION} .
- name: docker-push command: docker push ${REGISTRY}/${REGISTRY_USER}/${APP_NAME}:${VERSION}
- name: deploy dependsOn: [build] steps: - name: apply-manifests command: kubectl apply -f manifests/ -n ${NAMESPACE}
- name: set-image command: kubectl set image deployment/${APP_NAME} ${APP_NAME}=${REGISTRY}/${REGISTRY_USER}/${APP_NAME}:${VERSION} -n ${NAMESPACE}
- name: scale command: kubectl scale deployment/${APP_NAME} --replicas=${REPLICAS} -n ${NAMESPACE}
- name: wait-rollout command: kubectl rollout status deployment/${APP_NAME} -n ${NAMESPACE}
- name: verify script: | echo "Verifying deployment..." READY=$(kubectl get deployment ${APP_NAME} -n ${NAMESPACE} -o jsonpath='{.status.readyReplicas}') if [ "$READY" = "${REPLICAS}" ]; then echo "✅ Deployment successful: ${READY}/${REPLICAS} replicas ready" else echo "❌ Deployment failed: ${READY}/${REPLICAS} replicas ready" exit 1 fiBest Practices
1. Use Descriptive Variable Names
# Goodenv: APP_NAME: my-app DATABASE_HOST: postgres.example.com MAX_CONNECTIONS: "100"
# Badenv: NAME: my-app HOST: postgres.example.com MAX: "100"2. Provide Default Values
# Good - provides fallbackcommand: kubectl apply -f manifests/ -n ${NAMESPACE:-default}
# Risky - fails if NAMESPACE not setcommand: kubectl apply -f manifests/ -n ${NAMESPACE}3. Document Variables
env: APP_NAME: my-app # Application name VERSION: v1.0.0 # Application version NAMESPACE: production # Kubernetes namespace REPLICAS: "3" # Number of replicas4. Group Related Variables
env: # Registry configuration REGISTRY: docker.io REGISTRY_USER: myuser REGISTRY_TOKEN: secret
# Application configuration APP_NAME: my-app APP_VERSION: v1.0.0
# Deployment configuration NAMESPACE: production REPLICAS: "3"5. Use Environment Files for Secrets
# .env (not committed to Git)DOCKER_TOKEN=secret-tokenDATABASE_PASSWORD=secret-password
# .env.example (committed to Git)DOCKER_TOKEN=your-docker-token-hereDATABASE_PASSWORD=your-database-password-hereTroubleshooting
Variable not substituted
Problem: ${VAR} appears literally in output
Solution:
# Ensure variable is setecho $VAR
# Or define in workflowenv: VAR: valueEmpty variable value
Problem: Variable expands to empty string
Solution:
# Use default valuecommand: kubectl apply -f manifests/ -n ${NAMESPACE:-default}
# Or require variablecommand: kubectl apply -f manifests/ -n ${NAMESPACE:?NAMESPACE is required}Variable precedence issues
Problem: Wrong variable value is used
Solution: Check precedence order:
- Environment variables (highest)
- Job-level variables
- Workflow-level variables
- Default values (lowest)