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)

VariableValueNotes
HYVE_CLUSTER_NAMECluster name
HYVE_CLUSTER_REGIONCluster region
HYVE_CLUSTER_PROVIDERCloud provider (aws, gcp, azure, civo)
HYVE_CLUSTER_TYPECluster type (eks, gke, aks, k3s)
HYVE_CLUSTER_K8S_VERSIONspec.kubernetesVersionEmpty string if not set
HYVE_AWS_ACCOUNTAWS account aliasAWS clusters only
HYVE_GCP_PROJECTGCP project aliasGCP clusters only
HYVE_AZURE_SUBSCRIPTIONAzure subscription aliasAzure clusters only
HYVE_CIVO_ORGCivo organization aliasCivo clusters only
HYVE_CLUSTER_ACCOUNT_IDResolved AWS account IDAWS clusters only
HYVE_CLUSTER_VPC_IDspec.awsVpcIdAWS clusters only
Provider credentialsStandard 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)

VariableValue
HYVE_CLUSTER_IP_ADDRESSCluster API endpoint IP
HYVE_CLUSTER_ACCESS_PORTCluster API port
HYVE_CLUSTER_IDCloud provider cluster ID
HYVE_CLUSTER_STATUSCluster status string
HYVE_CLUSTER_KUBECONFIGPath to cluster kubeconfig file
KUBECONFIGSame as HYVE_CLUSTER_KUBECONFIG
HYVE_CLUSTER_OIDC_URLEKS 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 VariableCluster Field Updated
HYVE_VPC_IDspec.awsVpcId
HYVE_EKS_ROLE_NAMEspec.awsEksRoleName
HYVE_NODE_ROLE_NAMEspec.awsNodeRoleName
HYVE_EKS_ROLE_ARNspec.awsEksRoleArn
HYVE_NODE_ROLE_ARNspec.awsNodeRoleArn
HYVE_CLUSTER_SG_IDspec.awsClusterSgId
HYVE_WORKER_SG_IDspec.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

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)