Table of Contents
Introduction
Every DevOps team knows the frustration: you push a change to your GitHub Actions workflow, then wait several minutes for the pipeline to execute, fail, and force another round of commits. And then it goes on in a loop.
At KubeNine, we encountered this same delay while iterating on CI/CD workflows for our projects. The feedback loop was painfully slow and led to a loss of a lot of time. Developers used to hate working on complex Github actions and as we kept building bigger and tougher things for our customers - it became even more painful.
The solution? A lightweight open-source tool called act. By enabling us to run GitHub Actions locally, act
has significantly shortened our development feedback loop and helped us ship workflows faster and with more confidence.
In this blog, we’ll explore how we integrated act
into our workflow, highlight practical use cases, and demonstrate how it helped KubeNine accelerate shipping reliable GitHub Actions.
Accelerating Workflow Development with Act
What is Act?
Act is an open source command-line tool that allows developers to run GitHub Actions locally using Docker. It simulates the GitHub Actions environment so you can test your workflows without pushing code to GitHub.
Running Workflows Locally
Here’s an example of how we used act
to test a CI workflow for our containerized microservice:
# .github/workflows/ci.yaml
name: CI
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.21
- name: Run tests
run: go test ./...
Using act
, we were able to run this workflow locally and catch an import error within seconds:
act -j build
This instant feedback prevented multiple git commits and pushed iterations, saving valuable time.
Real-World Use Case at KubeNine
At KubeNine, we use Act to test our deployment workflows before pushing them to GitHub. Here's how we test our manual deployment workflow:
Our Workflow Structure
Our deployment process uses a reusable workflow pattern:
- Main Workflow (
manual-deploy.yaml
):
name: Manual Deploy (staging and prod)
on:
workflow_dispatch:
inputs:
image_tag:
description: "Tag for the image"
required: true
default: "latest"
environment:
description: "Deployment Environment (staging or prod)"
required: true
default: "prod"
options:
- staging
- prod
jobs:
deploy:
uses: ./.github/workflows/deploy.yaml
with:
image_tag: ${{ inputs.image_tag }}
environment: ${{ inputs.environment }}
secrets: inherit
- Reusable Workflow (
deploy.yaml
):
name: Reusable Deploy Workflow
on:
workflow_call:
inputs:
image_tag:
required: true
type: string
description: "Docker image tag to deploy"
environment:
required: true
type: string
description: "Deployment environment (e.g., staging, prod)"
secrets:
SUBMODULE_GITHUB_PASS:
required: true
description: "GitHub token for submodule access"
KUBECONFIG:
required: true
description: "Base64 encoded kubeconfig file"
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code Repository
uses: actions/checkout@v3
with:
token: ${{ secrets.SUBMODULE_GITHUB_PASS }}
submodules: true
depth: 1
- name: Debug Submodules
run: |
echo "Checking submodule content"
ls -la
ls -la ./your-helm-chart-submodule
- name: Setup kubectl
uses: azure/setup-kubectl@v3
with:
version: 'v1.31.1' # Update to your desired kubectl version
- name: Configure kubectl
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG }}" | base64 --decode > $HOME/.kube/config
kubectl config current-context
kubectl get nodes
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: 'v3.10.0' # Update to your desired Helm version
- name: Deploy using Helmfile
run: |
.....
- name: Wait for Deployment Rollout
run: |
echo "Waiting for deployment to stabilize..."
sleep 10
- name: Check Deployment Rollout and run smoke tests
run: |
....
- Handles the actual deployment logic
- Uses Helm and Helmfile for Kubernetes deployments
- Requires access to our Helm chart submodule
- Helm Chart Submodule (
kubenine-helm-chart
):
- Stored as a Git submodule
- Contains our Kubernetes deployment templates
- Referenced in our Helmfile configurations
Testing with Act
To test this workflow locally, we use Act with the following command:
act workflow_dispatch -e input.json -W .github/workflows/manual-deploy.yaml --secret-file secrets.env -P ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-20.04
Let's break down this command:
1. Command Components
act workflow_dispatch
: Tells Act to simulate aworkflow_dispatch
event-e input.json
: Specifies the event payload file containing our inputs-W .github/workflows/manual-deploy.yaml
: Points to our workflow file--secret-file secrets.env
: Provides secrets needed by the workflow-P ubuntu-latest=ghcr.io/catthehacker/ubuntu:act-20.04
: Uses a GitHub runner-compatible Docker image
2. Input File
Our input.json
file contains the parameters we want to pass to the workflow:
{
"inputs": {
"image_tag": "0.48.0",
"environment": "staging"
}
}
3. Secrets File
The secrets.env
file contains the secrets needed by our workflow:
SUBMODULE_GITHUB_PASS=your_github_token
KUBECONFIG=base64_encoded_kubeconfig
4. GitHub Runner Image
We're using ghcr.io/catthehacker/ubuntu:act-20.04
, which is a community-maintained image that closely matches GitHub's Ubuntu 20.04 runner environment. This ensures our local testing is as close as possible to the actual GitHub environment.
What Act Does Behind the Scenes
When you run the Act command:
- Act reads the workflow file and the event payload
- It creates a Docker container using the specified runner image
- It mounts your repository code into the container
- It injects the secrets and environment variables
- It simulates the workflow_dispatch event with your inputs
- It executes each step of the workflow inside the container
- It reports the results back to your terminal
Benefits of Using Act
- Local Testing: Test workflows without pushing to GitHub
- Faster Iteration: Get immediate feedback on workflow changes
- Cost Savings: Avoid consuming GitHub Actions minutes during development
- Offline Development: Work on workflows without internet access
- Debugging: Easily debug workflow issues in a local environment
Common Challenges and Solutions
- Missing Dependencies: Use the
-P
flag to specify a more complete runner image - Secret Management: Create a secrets file with mock values for local testing
- Submodule Issues: Initialize submodules locally or use the
--bind
flag - Permission Issues: Ensure Docker has the necessary permissions to run containers
Conclusion
By integrating act
into our workflow, we drastically reduced the time required to develop and validate GitHub Actions. This has led to faster iterations, fewer CI errors, and improved developer productivity.
While act
does not support all GitHub-hosted services (like GITHUB_TOKEN
or hosted runners), it provides a pragmatic and fast feedback loop that fits seamlessly into our development toolkit.
If you're spending too much time waiting for CI pipelines to validate your GitHub Actions, give act
a try. You might find, like we did, that a small tool can make a significant impact.