Skip to content
Docs

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 set

Nested 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.0

Lifecycle 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:

VariableValueNotes
HYVE_CLUSTER_NAMECluster name
HYVE_CLUSTER_REGIONspec.region
HYVE_PARAM_<KEY>Each spec.params entry, key uppercasedOne per param — entirely module-specific, no fixed list
<key> (as-is)Each spec.driverOutputs entryWhatever 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)

VariableValue
KUBECONFIGPath 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

Terminal window
export APP_NAME=my-app
hyve workflow run deploy-app

2. 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: v1
kind: Workflow
metadata:
name: deploy-app
spec:
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:

.env
APP_NAME=my-app
REGISTRY=docker.io/myuser
VERSION=v1.0.0
DOCKER_TOKEN=secret-token

Hyve automatically loads .env files when running workflows.

apiVersion: v1
kind: Workflow
metadata:
name: deploy-app
spec:
# 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} .
Terminal window
# Create .env file
cat > .env <<EOF
APP_NAME=my-app
VERSION=v2.0.0
EOF
# Run workflow (loads .env automatically)
hyve workflow run deploy-app

Default 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

Basic substitution - error if not set

${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: v1
kind: Workflow
metadata:
name: build-push
spec:
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/
fi

Array 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
done

String 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: v1
kind: Workflow
metadata:
name: complete-deployment
description: Complete deployment with variable substitution
spec:
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
fi

Best Practices

1. Use Descriptive Variable Names
# Good
env:
APP_NAME: my-app
DATABASE_HOST: postgres.example.com
MAX_CONNECTIONS: "100"
# Bad
env:
NAME: my-app
HOST: postgres.example.com
MAX: "100"
2. Provide Default Values
# Good - provides fallback
command: kubectl apply -f manifests/ -n ${NAMESPACE:-default}
# Risky - fails if NAMESPACE not set
command: 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 replicas
4. 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
Terminal window
# .env (not committed to Git)
DOCKER_TOKEN=secret-token
DATABASE_PASSWORD=secret-password
# .env.example (committed to Git)
DOCKER_TOKEN=your-docker-token-here
DATABASE_PASSWORD=your-database-password-here

Troubleshooting

Variable not substituted

Problem: ${VAR} appears literally in output

Solution:

Terminal window
# Ensure variable is set
echo $VAR
# Or define in workflow
env:
VAR: value
Empty variable value

Problem: Variable expands to empty string

Solution:

# Use default value
command: kubectl apply -f manifests/ -n ${NAMESPACE:-default}
# Or require variable
command: kubectl apply -f manifests/ -n ${NAMESPACE:?NAMESPACE is required}
Variable precedence issues

Problem: Wrong variable value is used

Solution: Check precedence order:

  1. Environment variables (highest)
  2. Job-level variables
  3. Workflow-level variables
  4. Default values (lowest)