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)
| Variable | Value | Notes |
|---|---|---|
HYVE_CLUSTER_NAME | Cluster name | |
HYVE_CLUSTER_REGION | Cluster region | |
HYVE_CLUSTER_PROVIDER | Cloud provider (aws, gcp, azure, civo) | |
HYVE_CLUSTER_TYPE | Cluster type (eks, gke, aks, k3s) | |
HYVE_CLUSTER_K8S_VERSION | spec.kubernetesVersion | Empty string if not set |
HYVE_AWS_ACCOUNT | AWS account alias | AWS clusters only |
HYVE_GCP_PROJECT | GCP project alias | GCP clusters only |
HYVE_AZURE_SUBSCRIPTION | Azure subscription alias | Azure clusters only |
HYVE_CIVO_ORG | Civo organization alias | Civo clusters only |
HYVE_CLUSTER_ACCOUNT_ID | Resolved AWS account ID | AWS clusters only |
HYVE_CLUSTER_VPC_ID | spec.awsVpcId | AWS clusters only |
| Provider credentials | Standard CLI env vars (e.g. AWS_ACCESS_KEY_ID) | From provider-configs/*.yaml |
Only the account variable that matches the cluster’s provider is set — the others are left unset.
Live cluster variables (onCreate, afterCreate, onDelete)
| Variable | Value |
|---|---|
HYVE_CLUSTER_IP_ADDRESS | Cluster API endpoint IP |
HYVE_CLUSTER_ACCESS_PORT | Cluster API port |
HYVE_CLUSTER_ID | Cloud provider cluster ID |
HYVE_CLUSTER_STATUS | Cluster status string |
HYVE_CLUSTER_KUBECONFIG | Path to cluster kubeconfig file |
KUBECONFIG | Same as HYVE_CLUSTER_KUBECONFIG |
HYVE_CLUSTER_OIDC_URL | EKS OIDC provider URL (AWS only) |
beforeCreate and afterDelete receive only definition variables — the cluster does not
exist at those points and kubeconfig injection is skipped.
beforeCreate output variables
Output variables printed by a beforeCreate step in HYVE_KEY=value format are captured
by the reconciler and written back into the cluster definition before creation proceeds:
| Output Variable | Cluster Field Updated |
|---|---|
HYVE_VPC_ID | spec.awsVpcId |
HYVE_EKS_ROLE_NAME | spec.awsEksRoleName |
HYVE_NODE_ROLE_NAME | spec.awsNodeRoleName |
HYVE_EKS_ROLE_ARN | spec.awsEksRoleArn |
HYVE_NODE_ROLE_ARN | spec.awsNodeRoleArn |
HYVE_CLUSTER_SG_ID | spec.awsClusterSgId |
HYVE_WORKER_SG_ID | spec.awsWorkerSgId |
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)