Minimizing Your GitHub Workflow Files

GitHub Actions workflows often become unmaintainable "fat" workflows as logic is incrementally embedded directly into YAML. This creates a divide between developers who "program" in YAML, resulting in 500–1000 line files full of complex conditionals, and those who "configure" it, keeping workflows around 50–60 lines by pushing logic into scripts.

Minimizing Your GitHub Workflow Files

The Problem With “Fat” Workflows

Here's a pattern that is almost common to every org:

name: Deploy
on: [push]
env:
  IMAGE: registry.example.com/myapp
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set environment
        id: cfg
        run: |
          case "${{ github.ref }}" in
            refs/heads/main)     env=production; replicas=3; region=us-east-1 ;;
            refs/heads/release*) env=staging;    replicas=2; region=us-west-2 ;;
            *)                   env=dev;        replicas=1; region=us-west-2 ;;
          esac
          echo "env=$env replicas=$replicas region=$region" | tr ' ' '\n' >> $GITHUB_OUTPUT

      - name: Configure credentials
        run: aws configure set region ${{ steps.cfg.outputs.region }}
        # add prod-specific setup via: if [[ "${{ steps.cfg.outputs.env }}" == "production" ]]; then ...

      - name: Build and push
        run: |
          docker build -t $IMAGE:${{ github.sha }} .
          docker tag $IMAGE:${{ github.sha }} $IMAGE:${{ steps.cfg.outputs.env }}
          docker push $IMAGE:${{ steps.cfg.outputs.env }}

      - name: Deploy
        run: |
          kubectl set image deployment/myapp myapp=$IMAGE:${{ steps.cfg.outputs.env }}
          kubectl scale deployment/myapp --replicas=${{ steps.cfg.outputs.replicas }}

This isn't a deploy workflow. It's a deploy script that someone pasted into YAML instead of putting it where it belongs. None of this needs to live in your workflow file. Moreover, you can't test this locally and can't reuse outside of GitHub Actions.

Fat workflows also turn debugging into a nightmare and troubleshooting GitHub Actions frequently sinks into a cycle of git commit -m "wtfqwehsjsidbfjdi". By embedding logic directly within YAML, you lose the ability to test locally, forcing you to push every minor change and wait for the CI results.

Workflows as Orchestrators

The principle is simple: your workflow YAML should handle orchestration. This includes triggers, runner selection, job dependencies, secret injection, and artifact management. Everything else should live in scripts within your repository.

Your workflow file then becomes:

# .github/workflows/deploy.yml
name: Deploy
on: [push]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        run: ./scripts/deploy.sh
        env:
          GIT_REF: ${{ github.ref }}
          GIT_SHA: ${{ github.sha }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

You can run ./scripts/deploy.sh on your laptop and watch exactly what it does. You can write unit tests for determining the environment or you can pipe it through shellcheck. None of that is possible when the logic lives in YAML.

While the goal is minimization, this isn't a must do rule. Certain tasks require vendor-specific features like job dependencies, artifact management, or actions that wrap complex REST APIs. Standardized utilities, such as actions/checkout, do not need to be rewritten; you shouldn't feel obligated to replicate their functionality manually.

Use this heuristic: if a step involves blocks, loops, or string parsing, it should be moved to a script. Conversely, if it uses a reliable action or a simple one-liner for tool setup, keep it in the workflow.

Your YAML should focus on CI platform-specific logic:

  • Trigger definitions and runner selection
  • Job and step dependencies
  • Caching and artifact handling
  • Secret injection via env:

Move everything else including build logic, test orchestration, deployment steps, environment detection, and notifications into dedicated scripts.

Don't Repeat Yourself Across Repos

Duplication across repositories becomes the next hurdle after refining individual workflows. Within an organization, it is common for every repository to maintain its own nearly identical CI pipeline which covers the same linting, Docker builds, and deployment scripts. These copies inevitably diverge over time, as a minor adjustment made to one repository months ago rarely makes its way to the others.

GitHub's reusable workflows solve this. You can define a workflow in a central repository and call it from any other repo in your org. 

The real benefit here is governance. When you need to add a tool to every workflow, you can update just one shared workflow. Every downstream repo picks it up on the next run. Now, there is no need to raise separate PRs across multiple repos.

A couple of things worth knowing is that reusable workflows can nest up to four levels deep, the caller can't access intermediate outputs unless you explicitly define them, and pinning to @main instead of a versioned tag makes it harder to test changes before they hit every consumer. Use tags for stability.

But What About Speed?

There's a catch that none of this addresses. You can have the cleanest, most minimal workflow files in the world, and your builds are still going to be slow. For example, if the runner takes 30 seconds to spin up, your Docker layer cache gets evaporated between runs, and your 6-minute build is actually 12 minutes by the time you account for queue time and cold cache rebuilds.

For small projects, this doesn't matter. For teams shipping multiple times a day, it adds up fast and it starts to undermine the whole "push logic into scripts" philosophy, because the feedback loop is still slow even when the scripts are correct.

This is the problem we're solving with Monk CI. Monk CI provides drop-in replacement runners for GitHub Actions. The migration is a one-line change:

# Before
runs-on: ubuntu-latest

# After
runs-on: monkci-ubuntu-24.04-4

Same workflow files, same actions, same secrets. Your minimal workflows stay minimal and run faster. If you're tired of waiting on cold runners and blown caches, take a look at Monk CI.

Written by

nitin mandale

Last updated June 4, 2026