DEV Community

Yunus Emre Ak
Yunus Emre Ak

Posted on • Originally published at docs.yemreak.com

Makefile Over GitHub Actions -> Total Control

Makefile Over GitHub Actions -> Total Control

Problem

I became a slave to platforms. GitHub Actions, CI/CD pipelines, cloud services—they all promise convenience but deliver dependency. You push code, wait for workflows, hope they succeed. You're not in control; the platform is.

The Awakening

Today, I deleted .github/workflows/. All of it. Gone.

Why? Because I realized something fundamental: Platforms are just interfaces. Logic should be mine.

The Makefile Renaissance

What I Had (Platform Slavery)

# .github/workflows/publish.yml
name: Publish Packages
on:
  push:
    branches: [main]
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm publish
Enter fullscreen mode Exit fullscreen mode

Problems:

  • Vendor lock-in to GitHub
  • Debugging requires pushing code
  • Waiting for runners
  • Limited to GitHub's environment
  • No local testing

What I Built (Total Control)

# Makefile
publish:
    @for dir in $$(fd package.json packages/ -x dirname {}); do \
        pkg=$$(jq -r '.name' $$dir/package.json); \
        version=$$(jq -r '.version' $$dir/package.json); \
        published=$$(npm view $$pkg version 2>/dev/null || echo "0.0.0"); \
        if [ "$$version" != "$$published" ]; then \
            echo "Publishing $$pkg@$$version..."; \
            (cd $$dir && npm publish); \
        fi \
    done
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Works anywhere (local, CI, anywhere with make)
  • Instant execution
  • Full transparency
  • No external dependencies
  • Complete control

The Philosophy

This connects to my core principle: Zero Documentation -> Living Code

Now extended: Zero Platform Dependency -> Total Control

Before: Interface-Driven Development

Developer -> GitHub -> Actions -> npm
         ↑          ↑         ↑
      Interface  Platform  Service
Enter fullscreen mode Exit fullscreen mode

Each arrow is a dependency. Each dependency is a potential failure point.

After: Logic-First Development

Developer -> Makefile -> Direct Execution
         ↑          ↑
       Logic     Control
Enter fullscreen mode Exit fullscreen mode

No intermediaries. No waiting. No surprises.

Real Implementation

Here's my actual Makefile that replaced all GitHub Actions:

.PHONY: help readme publish publish-all list clean test

# Generate README dynamically from packages
readme:
    @echo "# vibe-coding" > README.md
    @echo "" >> README.md
    @echo "| Command | Package | Description |" >> README.md
    @echo "|---------|---------|-------------|" >> README.md
    @for dir in $$(fd package.json packages/ -x dirname {} | sort); do \
        name=$$(basename $$dir); \
        pkg=$$(jq -r '.name' $$dir/package.json); \
        desc=$$(jq -r '.description' $$dir/package.json); \
        echo "| [\`$$name\`](packages/$$name) | \`$$pkg\` | $$desc |" >> README.md; \
    done

# Version bump all packages
bump:
    @for dir in $$(fd package.json packages/ -x dirname {}); do \
        pkg=$$(jq -r '.name' $$dir/package.json); \
        old=$$(jq -r '.version' $$dir/package.json); \
        new=$$(echo $$old | awk -F. '{print $$1"."$$2"."$$3+1}'); \
        jq ".version = \"$$new\"" $$dir/package.json > $$dir/package.json.tmp && \
        mv $$dir/package.json.tmp $$dir/package.json; \
        echo "$$pkg: $$old -> $$new"; \
    done

# Test all tools
test:
    @for dir in $$(fd package.json packages/ -x dirname {}); do \
        name=$$(basename $$dir); \
        echo "Testing $$name..."; \
        (cd $$dir && bun run ./index.ts -h) || true; \
    done
Enter fullscreen mode Exit fullscreen mode

The Power of Make

Make is 48 years old (1976). It survived because it follows Unix philosophy:

  • Do one thing well
  • Everything is a file
  • Compose simple tools
  • Text is the universal interface

GitHub Actions is 5 years old. It won't survive 48 years. Make will.

Practical Benefits

1. Local-First Development

make test     # Test locally
make publish  # Publish locally
# No pushing to GitHub to test workflows
Enter fullscreen mode Exit fullscreen mode

2. Platform Independence

Works on:

  • Local machine
  • Any CI/CD platform
  • SSH servers
  • Docker containers
  • Anywhere with make

3. Speed

  • GitHub Actions: Push -> Wait for runner -> Execute -> Results (2-5 minutes)
  • Makefile: Execute -> Results (instant)

4. Transparency

make -n publish  # See what will run without executing
Enter fullscreen mode Exit fullscreen mode

You can't do this with GitHub Actions.

The Broader Pattern

This is part of a larger realization:

  1. Platforms are interfaces -> Control the logic
  2. Services are conveniences -> Own the capability
  3. Clouds are computers -> Your computer is enough
  4. Workflows are just scripts -> Write scripts

Migration Guide

Step 1: Identify Platform Dependencies

  • GitHub Actions -> Makefile
  • Vercel -> Static hosting
  • AWS Lambda -> Local server
  • Heroku -> Systemd service

Step 2: Extract the Logic

What does the platform actually do? Write it yourself.

Step 3: Create Local-First Tools

Every cloud service can be replaced with:

  • A script
  • A Makefile target
  • A local service
  • A simple binary

Real-World Examples

Dynamic README Generation

Instead of a static README that gets outdated:

readme:
    @for package in packages/*; do \
        echo "- [$$(basename $$package)]($$package)" >> README.md; \
    done
Enter fullscreen mode Exit fullscreen mode

Auto-Publishing

Instead of GitHub Actions:

publish: readme
    @git diff --exit-code || (git add -A && git commit -m "auto: update" && git push)
    @make publish-npm
Enter fullscreen mode Exit fullscreen mode

Continuous Testing

Instead of CI/CD:

watch:
    @fswatch -o . | xargs -n1 -I{} make test
Enter fullscreen mode Exit fullscreen mode

The Unix Way Wins

I returned to basics:

  • Make instead of CI/CD platforms
  • Shell scripts instead of workflows
  • Git hooks instead of GitHub Apps
  • Cron instead of scheduled actions

Each tool does one thing well. Each tool is under my control.

Conclusion

Deleting .github/workflows/ was liberating. I'm no longer waiting for runners, debugging YAML, or tied to GitHub's infrastructure.

The Makefile is my declaration of independence. It's code I control, logic I own, and automation that works everywhere.

Stop configuring platforms. Start writing Makefiles.

The best workflow is no workflow—just make.


Part of the Zero Documentation -> Living Code philosophy. See also: @yemreak/culture - my tool for discovering patterns from code instead of reading documentation.


Read the original: Makefile Over GitHub Actions -> Total Control

Top comments (0)