How a Simple Open Source Tool Drastically Improved Our Time to Ship GitHub Actions

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:

  1. 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
  1. 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
  1. 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 a workflow_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.