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
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:
- Platforms are interfaces -> Control the logic
- Services are conveniences -> Own the capability
- Clouds are computers -> Your computer is enough
- 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.
Read the original: Makefile Over GitHub Actions -> Total Control
Top comments (0)