Custom Flows

Hook into env0 deployment cycle using custom flows

Custom Flows are a way to run your own commands during the deployment of your environment. These will run outside the IaC of your choice and allow you to perform supplemental actions. For example, injecting custom variables into your IaC code before you apply it or calling external APIs with the outputs of your deployment.

Custom Flows allow you to run whatever you want (bash, Python, Gcloud, Ansible, CloudFormation, etc.) at any point in the deployment processโ€“ before or after Terraform init/plan/apply, destroy/error and even tasks.

To attach a custom flow to a single template:
Create a file named env0.yml under the template folder. env0 will use this file if itโ€™s found in your git repository.

For sharing a custom flow between templates:
Create a file named env0.yml under the root folder. env0 will use this file if it is defined, and an env0.yml file is not found in the template folder.

For sharing custom flow for all environments under the project under a particular project:
Create a custom flow file in any repository and configure it as a project policy.

We support the suffixes env0.yml and env0.yaml in the same way.

๐Ÿ“˜

Working Directory for env0.yml

env0 will run Custom Flow commands with the working directory being the one containing the YAML file. Based on that, you can reference your commands to other scripts/commands by using ./script.sh.

๐Ÿ“˜

Project level Custom Flows

Only the env0.yaml is "cloned" for project level Custom Flows, this means that scripts and other files referenced by your Custom Flow will either need to be cloned into the working directory, or you must specify the repo path location of your scripts and files.

Below is an example of a custom flow that creates a more "dynamic" Terraform file, based on an environment variable value, and uses Python and Jinja before the Terraform Init (the full example of this template git repo (including the env0.yml file) is here):

version: 2

deploy:
  steps:
    terraformInit:
      before:
        - name: Hydrate Template
          run: |
            pip install --user jinjanator
						echo "Generating \"stage.auto.tfvars\" for \"$STAGE\""
						jinjanate stage.auto.tfvars.j2 > stage.auto.tfvars
						echo -e "Generated \"stage.auto.tfvars\":\n$(cat stage.auto.tfvars)"
						echo "Generating \"elb.tf\" for \"$STAGE\""
						jinjanate elb.tf.j2 > elb.tf
						echo -e "Generated \"elb.tf\":\n$(cat elb.tf)"

You can include the following possible hooks in an env0.yml file.

๐Ÿ“˜

OpenTofu backward-compatibility

Terraform's Custom Flow format is interchangeable with OpenTofu's.

๐Ÿšง

Task hooks

Task hook uses the stored working directory source code to run, it will not fetch an updated version from you VCS provider. If you need updated files on your working directory to run the task hooks you will need to first run a deployment to update the files.

Hook Stages

version:
  type: number
shell: sh (default) or bash

deploy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    terraformInit:
      before:
        - type: string
      after:
        - type: string

    terraformPlan:
      before:
        - type: string
      after:
        - type: string

    terraformApply:
      before:
        - type: string
      after:
        - type: string

    storeState:
      before:
        - type: string
      after:
        - type: string

    terraformOutput:
      before:
        - type: string
      after:
        - type: string

destroy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    terraformInit:
      before:
        - type: string
      after:
        - type: string

    terraformPlan:
      before:
        - type: string
      after:
        - type: string

    terraformDestroy:
      before:
        - type: string
      after:
        - type: string
version:
  type: number
shell: sh (default) or bash

deploy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    opentofuInit:
      before:
        - type: string
      after:
        - type: string

    opentofuPlan:
      before:
        - type: string
      after:
        - type: string

    opentofuApply:
      before:
        - type: string
      after:
        - type: string

    storeState:
      before:
        - type: string
      after:
        - type: string

    opentofuOutput:
      before:
        - type: string
      after:
        - type: string

destroy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    opentofuInit:
      before:
        - type: string
      after:
        - type: string

    opentofuPlan:
      before:
        - type: string
      after:
        - type: string

    opentofuDestroy:
      before:
        - type: string
      after:
        - type: string
version:
  type: number
shell: sh (default) or bash

deploy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    pulumiPreview:
      before:
        - type: string
      after:
        - type: string

    pulumiUp:
      before:
        - type: string
      after:
        - type: string

    storeState:
      before:
        - type: string
      after:
        - type: string

    pulumiOutput:
      before:
        - type: string
      after:
        - type: string

destroy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    pulumiPreview:
      before:
        - type: string
      after:
        - type: string

    pulumiDestroy:
      before:
        - type: string
      after:
        - type: string
version:
  type: number
shell: sh (default) or bash

deploy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    cfCreateChangeSet:
      before:
        - type: string
      after:
        - type: string

    cfDeploy:
      before:
        - type: string
      after:
        - type: string

    cfOutput:
      before:
        - type: string
      after:
        - type: string

    storeState:
      before:
        - type: string
      after:
        - type: string

destroy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    cfListStackResources:
      before:
        - type: string
      after:
        - type: string

    cfDeleteStack:
      before:
        - type: string
      after:
        - type: string
version:
  type: number
shell: sh (default) or bash

deploy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string
    
    k8sDiff:
      before:
        - type: string
      after:
        - type: string

    k8sApply:
      before:
        - type: string
      after:
        - type: string

    storeState:
      before:
        - type: string
      after:
        - type: string

destroy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    k8sDiff:
      before:
        - type: string
      after:
        - type: string

    k8sDelete:
      before:
        - type: string
      after:
        - type: string
version:
  type: number
shell: sh (default) or bash

deploy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string
    
    helmDiff:
      before:
        - type: string
      after:
        - type: string

    helmUpgrade:
      before:
        - type: string
      after:
        - type: string

    storeState:
      before:
        - type: string
      after:
        - type: string

destroy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    helmDiff:
      before:
        - type: string
      after:
        - type: string

    helmUninstall:
      before:
        - type: string
      after:
        - type: string
version:
  type: number
shell: sh (default) or bash

deploy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    ansibleGalaxy:
      before:
        - type: string
      after:
        - type: string
    
    ansibleCheck:
      before:
        - type: string
      after:
        - type: string

    ansiblePlaybook:
      before:
        - type: string
      after:
        - type: string


destroy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string
version:
  type: number
shell: sh (default) or bash

task:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    taskCommands:
      before:
        - type: string
      after:
        - type: string

    storeState:
      before:
        - type: string

Specifying the Shell

Currently, env0's Custom Flow features allow you to select the shell: sh (default) or bash
To use bash, add the following snippet into your env0.yaml:

shell: bash

Handling Errors

If any command in your custom flow returns a non-zero exit code, the env0 deployment will stop on a 'Failed' status. You can leverage this to perform validation. For instance, asserting that a certain environment variable was supplied.

In such a case, env0 will display any output from stderr as the deployment error. If no output is found in stderr, it will be shown as an โ€™Unknown Errorโ€™.

๐Ÿ“˜

Error Message Handling

Use the 1>&2 notation to redirect stdout messages to stderr. This allows env0 to display the message in its UI (see I/O Redirection)

Here is an example:

version: 2
deploy:
  steps:
    setupVariables:
      after:
        - if [ -z "$MY_VAR" ]; then echo "MY_VAR must be supplied" 1>&2 && exit 1; fi

Exposed env0 System Environment Variables

env0 exposes the following environment variables for you to use:

Variable Name

Value Description

ENV0_ENVIRONMENT_ID

The deployed Environment ID

ENV0_PROJECT_ID

The Project ID of the deployed Environment

ENV0_PROJECT_NAME

The Project Name of the deployed Environment

ENV0_DEPLOYMENT_LOG_ID

The deployment ID

ENV0_DEPLOYMENT_TYPE

The deployment type.
One of deploy / destroy / prPlan / driftDetection / task

ENV0_DEPLOYMENT_REVISION

The revision
(available only when deployment revision is defined)

ENV0_WORKSPACE_NAME

The Terraform Workspace name used in the Environment

ENV0_ROOT_DIR

The root repository path

ENV0_ORGANIZATION_ID

Your env0 organization ID

ENV0_TEMPLATE_ID

The deployed Template ID

ENV0_TEMPLATE_PATH

The deployed template path to its Terraform configuration within the VCS repo

ENV0_TEMPLATE_REPOSITORY

The repository of the template

ENV0_TEMPLATE_REVISION

The value of this variable might be empty if no revision is set for the template. It will be different from ENV0_DEPLOYMENT_REVISION if a different revision is configured for this environment.

ENV0_TEMPLATE_DIR

The deployed Template path to its terraform configuration during the deployment

ENV0_TEMPLATE_NAME

The deployed Template name

ENV0_ENVIRONMENT_NAME

The deployed Environment name

ENV0_ENVIRONMENT_CREATOR_NAME

The name of the Environment creator

ENV0_ENVIRONMENT_CREATOR_EMAIL

The email of the Environment creator

ENV0_DEPLOYER_NAME

The name of the deployer

ENV0_DEPLOYER_EMAIL

The email of the deployer

ENV0_VCS_PROVIDER

The name of the version control that triggered the deployment
(Available only for deployments triggered by a VCS webhook event)

ENV0_REVIEWER_NAME

The name of the reviewer
(Available only after 'Approve' was clicked)

ENV0_REVIEWER_EMAIL

The email of the reviewer
(Available only after 'Approve' was clicked)

ENV0_PR_AUTHOR

GitHub- the username PR author
GitLab- the username commit author
(Available only in PR plan)

ENV0_PR_NUMBER

The identifier of the PR
(Available only in PR plan)

ENV0_PR_SOURCE_REPOSITORY

The source repository of the PR
(Available only in PR plans for the VCSs supporting fork PR Plans)

ENV0_PR_SOURCE_BRANCH

The source branch of the PR
(Available only in PR plan)

ENV0_PR_TARGET_BRANCH

The target branch of the PR
(Available only in PR plan)

ENV0_COMMIT_HASH

The commit hash of the deployed Environment
(Available only in PR plan)

ENV0_OIDC_TOKEN

The OIDC Token - read more here on how to enable it and use it

ENV0_VCS_ACCESS_TOKEN

When using a native VCS integration, this will represent the access token we use to clone the repository

ENV0_TF_PLAN_JSON

The file path to a JSON representation of a Terraform Plan file
(output of terraform show -json)

ENV0_CLI_ARGS_PLAN

The additional CLI arguments when running a remote plan with options

See this example for using these variables in our public templates repo.

๐Ÿ“˜

Note

You can use exposed env0 variables in the UI using the ${...} notation:

env0 System Files

env0 generates a few files during runtime that can be used in a custom flow (and terraform) to help you programmatically access environment variables and Terraform variables.

env0.system-env-vars.json

This file contains all the environment variables generated by env0 through metadata. See above for list of avaialble environment variables and their respective descriptions.

env0.auto.tfvars.json

This file contains all the variables defined through the UI in json format. For example, you can parse the file using jq.

cat env0.auto.tfvars.json | jq -r '.'

env0.env-vars.json

This file similar to the terraform variables, contains all the environment variables defined through the env0 UI. This can also be parsed using jq.

๐Ÿ“˜

Accessing Environment Variables in Terraform

You can use this file to access environment variables within your Terraform code. Simply read and jsondecode the env0.system-env-vars.json file to get access to the metadata. See example:

locals {
  env_vars = jsondecode(file("env0.system-env-vars.json"))
}

output "env0_environment_id" {
  value = local.env_vars.ENV0_ENVIRONMENT_ID
}

Using Additional CLI Tools in Custom Flows

Custom flows execute your shell commands within a dedicated deployment container. This container comes pre-loaded with a standard set of utilities, including common OS tools, package managers.

However, if your custom flow hooks require specific command-line interface (CLI) tools, such as the AWS CLI or kubectl, you must explicitly configure the deployment to install them.

To add these tools, set the ENV0_INSTALLED_TOOLS environment variable with a comma-separated list of the tools you need.

For example, to make the AWS CLI and kubectl available in your deployment container, you would set the following variable:

ENV0_INSTALLED_TOOLS=aws,kubectl

You can set this variable at any level. For instance, if one project uses kubectl and another uses the aws CLI, it's best to set the variable at the project level to ensure each environment has only the tools it requires.

Available tools

ToolDescription
kustomizeA Kubernetes native tool for customizing application configuration.
infracostShows cloud cost estimates for Terraform, helping to manage budgets.
kubeloginA kubectl plugin for non-interactive authentication with OIDC providers.
opaOpen Policy Agent, a general-purpose policy engine for cloud native environments.
awsThe official command-line tool for Amazon Web Services.
gcloudThe primary command-line tool for Google Cloud Platform.
azThe official command-line tool for Microsoft Azure.
helmThe package manager for Kubernetes, used to find, share, and use software.
dyffA diff tool for YAML files, especially useful for Kubernetes manifests.
kubectlThe command-line tool for controlling Kubernetes clusters.
terratagA CLI tool for managing tags across all resources in a Terraform project.
pwshPowerShell, a cross-platform task automation and configuration management framework.
gke-gcloud-auth-pluginA plugin to handle GKE authentication for kubectl and other clients.
๐Ÿšง

Version Updates

env zero will periodically update tool versions to address security considerations or end-of-life (EoL) versions.
Customers will be notified in advance whenever an update introduces breaking changes.

You can always check the currently installed versions under the Init Packages step.

Exporting your own environment variables

In order to export your own Environment Variable to use in any downstream custom flow you should write a shell command in the format: echo KEY=VALUE >> $ENV0_ENV.
In later steps of the same deployment, you will be able to use $KEY to access the stored value.

๐Ÿ“˜

Note

For env0.yml files that use schema version 2, when using multiple commands within a given step e.g., terraform:plan-before, all commands are executed inside the same shell. Exported Environment variables like these are only available for the steps that follow.

For values within the same shell, using the usual export key=value still works.

Forcing Manual Approvals

Whenever ENV0_REQUIRES_APPROVAL=true occurs, env0 will force deployment to require approval, regardless of the Auto Approval setting in the Environment.

For example, instead of failing a deployment based on some policies, we can use this field to help ensure that others can still review and proceed with the deployment.

Usage:
- echo ENV0_REQUIRES_APPROVAL=true >> $ENV0_ENV

FAQ

Q. How can I use Custom Flows to add new Terraform variables?

A. The recommended approach to adding Terraform variables is to use variable definitions files, specifically the .auto.tfvars or .auto.tfvars.json files, to pass additional variables to your Terraform code.

Note: Please be aware of the Terraform Variable Definition Precedence order, defined here.

Another way is to create new Environment Variables inside the custom flow in the form of TF_VAR_key=value. To do that, please follow our guide for exporting your environment variables.

Q. I noticed that env0 runs init again after approval. How can I run my custom flow only once per deployment?

A. You can take advantage of the ENV0_REVIEWER_NAME field to see if the plan has already been approved.

For example:

deploy:
  steps:
    terraformInit:
      before: # run ./myScript.sh only on pre-approval
        - if [[ -z $ENV0_REVIEWER_NAME ]]; then ./myScript.sh; fi  

Q. Do env0 SaaS runners use static IP addresses? I would like to whitelist env0's runners to access my network.
A. Yes, env0 uses those IP addresses for all the outbound requests.

Q. How do I export a multi-line string into an environment variable?
A. Utilize \n and sed for example: "MY_PEM_FILE='$(cat ${PEM_FILE} | sed -e 's/\n/\\n/g')'" >> $ENV0_ENV

๐Ÿ“˜

GitHub API limits

When downloading resources from GitHub (e.g. tflint), make sure you use an authenticated call (e.g. curl -u USER:TOKEN). Otherwise, you will likely run into API limits, as unauthenticated calls are pooled with other SaaS users, with a much lower API limit. See GitHub Rate Limiting.

๐Ÿ“

Additional Content