My Journey from GitHub Actions Back to Makefiles

Why did I delete all my GitHub Actions and return to 50-year-old Makefiles? The surprising journey from cloud dependency back to local control, and how it changed everything.

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

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

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

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

After: Logic-First Development

Developer -> Makefile -> Direct Execution
         ↑          ↑
       Logic     Control

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

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

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

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

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

Continuous Testing

Instead of CI/CD:

watch:
	@fswatch -o . | xargs -n1 -I{} make test

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.

Last updated

Was this helpful?