Setting Environment Variables in act Workflows: A Complete Guide
You can set environment variables in act using CLI flags (-e), .env files, workflow-level env: blocks, job-level definitions, and step-level overrides, with the final map being constructed through a hierarchical merge process implemented in the Go source code.
The nektos/act repository provides a local GitHub Actions runner that must precisely emulate cloud behavior, making setting environment variables in act workflows a multi-layered process. The implementation spans CLI parsing, YAML workflow interpretation, and Docker container orchestration to ensure variable resolution matches GitHub's production environment.
How act Constructs the Runtime Environment
Act builds the execution environment through five distinct stages, each handled by specific components in the codebase. This architecture ensures that variables defined at different scopes (global, job, step) merge correctly before reaching your containers.
Stage 1: CLI and File Parsing
When you execute act run, the command layer immediately begins collecting environment data. In cmd/root.go, the parseEnvs function (lines 20‑30) processes -e flags from the command line, while readEnvsEx (lines 49‑68) loads variables from the default .env file or a custom file specified via --env-file. These values populate an initial map that serves as the foundation for all subsequent merging.
Stage 2: Workflow and Job-Level Merging
The parsed workflow definition undergoes environment resolution through the environment helper in pkg/model/workflow.go (lines 72‑79), which converts YAML env: blocks into Go maps. Later, pkg/runner/run_context.go implements the GetEnv method (lines 77‑88) to perform the first major merge: it combines the global workflow environment, job-specific definitions, and the CLI-provided map from stage one. This method also injects the marker variable ACT=true to identify the local runtime.
Stage 3: Step-Level Preparation and Container Injection
Before any step executes, setupEnv() in pkg/runner/step.go (lines 245‑267) performs the final merge, overlaying step-specific env: entries onto the job-level map and expanding ${{ }} expressions. For Docker-based steps, pkg/container/docker_cli.go (line 186) translates the completed environment map into -e KEY=VALUE flags passed to the Docker CLI via container.NewContainerInput.
CLI and File-Based Environment Configuration
The simplest method for setting environment variables in act workflows involves command-line flags and local files. Act automatically looks for a .env file in your repository root unless you specify an alternative.
Create a standard .env file with key-value pairs:
# .env in the repository root
DATABASE_URL=postgres://user:pass@localhost/db
DEBUG=true
Load a custom file or add individual variables at runtime:
act -e FOO=bar --env-file .custom.env
The -e flag invokes parseEnvs for single variables, while --env-file triggers readEnvs to parse the specified path, as implemented in cmd/root.go.
Workflow and Job-Level Environment Definitions
GitHub Actions workflows support env: blocks at both the workflow and job levels, with act processing these through pkg/model/workflow.go. Job-level definitions override workflow-level values for that specific job.
Define global and job-scoped variables in your workflow YAML:
name: Build
on: [push]
env:
GLOBAL_VAR: "global"
DEBUG: "false"
jobs:
build:
runs-on: ubuntu-latest
env: # job-level env overrides/extends the workflow-wide block
JOB_VAR: "job"
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Echo vars
run: |
echo "GLOBAL_VAR=$GLOBAL_VAR"
echo "JOB_VAR=$JOB_VAR"
echo "DEBUG=$DEBUG"
echo "DATABASE_URL=$DATABASE_URL"
The RunContext.GetEnv() method merges these layers according to GitHub's precedence rules, ensuring job variables take priority over workflow globals while preserving CLI-injected values.
Step-Level Overrides and Special Variables
Individual steps can define their own environment maps that override all higher-level definitions. This mechanism also handles the special GITHUB_ENV and GITHUB_OUTPUT files that GitHub Actions uses for persistence between steps.
Override variables for specific steps:
steps:
- name: Override DEBUG for this step
env:
DEBUG: "true"
run: echo "DEBUG is $DEBUG"
- name: Set persistent env
run: echo "MY_VAR=value" >> $GITHUB_ENV
The setupEnv() function in pkg/runner/step.go executes this merge, evaluates expressions, and configures the special GitHub-specific files before your step code runs.
Programmatic Environment Configuration in Go
If embedding act into your own tooling, you can construct environment maps programmatically using the same internal functions that power the CLI:
// Example snippet from a Go program using act's internals
env := map[string]string{
"FOO": "bar",
}
// Load a .env file
act.ReadEnvs("./my.env", env) // see cmd/root.go readEnvsEx
// Create a RunContext (simplified)
rc := &act.RunContext{
Config: &act.Config{Env: env},
}
finalEnv := rc.GetEnv() // merges workflow + job envs
fmt.Println(finalEnv["FOO"]) // prints "bar"
This approach leverages readEnvsEx from cmd/root.go and GetEnv from pkg/runner/run_context.go to ensure consistency with standard act behavior.
Summary
- CLI flags (
-e) and.envfiles provide the base environment throughcmd/root.gofunctionsparseEnvsandreadEnvsEx. - Workflow-level and job-level
env:blocks merge inRunContext.GetEnv()withinpkg/runner/run_context.go, following GitHub's precedence hierarchy. - Step-level definitions override higher scopes via
setupEnv()inpkg/runner/step.go, which also handles expression expansion and special files likeGITHUB_ENV. - Docker containers receive the final environment map as
-eflags throughcontainer.NewContainerInputinpkg/container/docker_cli.go. - The marker variable
ACT=trueis automatically injected to identify local execution contexts.
Frequently Asked Questions
How do I pass environment variables to act from the command line?
Use the -e flag followed by KEY=VALUE pairs when invoking act. Multiple variables require multiple -e flags: act -e VAR1=val1 -e VAR2=val2. These values are parsed by the parseEnvs function in cmd/root.go and become part of the base environment before workflow parsing begins.
Can I use a custom .env file with act instead of the default one?
Yes, specify an alternative file path using the --env-file flag: act --env-file ./config/production.env. The readEnvsEx function in cmd/root.go handles this file path and populates the environment map before workflow execution starts.
How does act handle environment variable precedence when the same key is defined in multiple places?
Act follows GitHub Actions' standard precedence rules: step-level definitions override job-level, which override workflow-level, which override CLI/file-provided values. The GetEnv() method in pkg/runner/run_context.go performs the workflow and job merging, while setupEnv() in pkg/runner/step.go applies step-specific overrides as the final layer.
Can I set environment variables dynamically during workflow execution?
Yes, write to the GITHUB_ENV file within any step: echo "KEY=value" >> $GITHUB_ENV. The setupEnv() function in pkg/runner/step.go processes these files between steps, making the variables available to subsequent steps in the same job. This mirrors GitHub Actions' behavior for persistent environment modifications.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →