curl -fsSL https://sparkwing.dev/install.sh | shsparkwing pipeline new --name release --template build-test-deploysparkwing run releasesparkwing dashboard start# then view at localhost:4343
Pipelines in code
not YAML
Local or cloud | Burst capacity wherever | Built for agents
The accelerant your agents need to test and ship with confidence.
Sparkwing shifts your pipelines left, to your local machine. Then, with intelligent admission control, Sparkwing squeezes every machine you've got. It distributes load across your laptop, gaming PC, team's laptops, and free GitHub Actions minutes. Then, burst to Sparkwing Cloud to keep your agents going. Pipelines are Go or TypeScript, so one shared library fixes every service. This is the new era of CI/CD. Cheaper, faster, clearer.
- korey@sparkwing.devOpen in mail app
Run `sparkwing info || curl -fsSL https://sparkwing.dev/install.sh | sh` and follow its guidance. Create a lint pipeline with `sparkwing pipeline new --name lint --on pre_commit`, make it run this repository’s linters and formatters, and get it passing with `sparkwing run lint`. Then install it as a git hook with `sparkwing pipeline hooks install`.
Go pipelines need Go 1.26+. TypeScript pipelines need Node.js 22.18+. macOS, Linux, and Windows with Git Bash.
CI/CD Reimagined - Because it needed to be
Some of my favorite work in my career has been within CI/CD, but the tooling itself was always full of frustrations. I’m not sure why we stepped away from using real code to author pipelines, but I wanted to change that. In fact, there were a lot of things I wanted to change.
My first motivation for making Sparkwing was having sprawling bash scripts for 14 different services that were hard to verify, trace, and change. I thought - wouldn’t it be nice if I could just write Go?
My second motivation was writing GitHub Actions pipelines, and feeling a huge amount of pain with the way their conditionals worked. The strict YAML rules simply couldn’t handle the types of pipelines I was trying to write. I could either write 32 efficient pipelines, or 1 inefficient one. I chose inefficient - because who has the time. Code was also the answer here.
Once I finally had enough frustrations, I decided to make my own platform. And, what an exciting journey it’s been. I’ve been able to load Sparkwing with absolutely unique and amazing features. It’s such a joy to set up, to use, and to develop. It’s easy for people to troubleshoot, but easy for agents too. My agents have gotten way more productive working with my pipelines, and so have I.
I hope you’ll try it. I’m confident you’ll love it. And if you don’t, let me know. I am committed to making this the best system you’ve ever used.
A new way to do CI/CD
Run a pipeline on cloud runners, your own machines, or both. Give each job runner requirements to control where it executes.
Runner requirements
Build arm64
Builds the arm64 image natively on your Apple silicon Mac, where the layers are already warm.
.Requires("arch=arm64", "name=my-mac")name=my-mac selects that authenticated runner. Every requirement must match.
Your computers, connected
Connect your own runners on Linux, macOS, or Windows
Connect a computer to your team’s controller to run selected jobs alongside cloud runners. Choose which repositories it accepts and how much CPU, memory, and concurrency it contributes.
Where the time goes
The fastest job is the one that never runs.
Sparkwing cuts a run down in four passes. The last one is your laptop, where the cache is already warm.
Bar widths are illustrative. 14 seconds observed in a cached local-to-Kubernetes workflow; timing varies with the changes, cache state and cluster.
Security guardrails
Control which code a machine accepts, where controller-dispatched steps run, and what must be approved before deployment.
Checkpoint illustration: approved jobs pass all three gates. Repositories outside the allowlist stop at gate two. Risk-labeled steps wait at gate three for explicit approval.
Choose approved runners
Target your own machine for preview work and an approved runner for sensitive deployment jobs. Team admins reserve runners for a pool and choose which repositories and branches may use it.
.Requires("pool=prod")Limit what a machine accepts
Repository allowlists restrict the source a runner may build. Set concurrency, CPU, and memory limits on each machine, and revoke its runner token when access should end.
--repos 'your-team/*'Gate sensitive operations
Pipeline guards check where a run executes, its branch, and its arguments before dispatch. Risk-labeled steps require explicit authorization before they execute.
step.Risk("prod", ...)Runner selection controls placement; guards do not prohibit every local invocation. Pipeline code runs as the runner’s OS user. Use an isolated environment for shared runners and restrict production credentials to approved machines.
Inside Sparkwing
Your pipeline.
Every point of view.
One CLI and dashboard, built for humans and agents alike.
Recorded on the production console. Credentials and other sensitive values are hidden.
From a run to its job logs
Open a run, inspect its DAG, and read the logs from a job.
Same jobs. A different language.
Skipping a job shouldn’t take a job.
In Actions, deciding to skip costs a runner, and every job after it needs an if: always() to keep going. In Sparkwing, a memoized job never starts one.
jobs: check-cache:Boots a runner just to decide runs-on: ubuntu-latest outputs: hit: ${{ steps.lookup.outputs.cache-hit }} steps: - uses: actions/checkout@v4 - id: key run: echo "key=it-${{ hashFiles('src/**', 'go.sum', 'migrations/**') }}" >> "$GITHUB_OUTPUT" - id: lookup uses: actions/cache/restore@v4 with: path: .it-passed key: ${{ steps.key.outputs.key }} lookup-only: true integration-tests: needs: check-cache if: needs.check-cache.outputs.hit != 'true'Skips the tests steps: # ...run the tests, then leave a marker for next time - run: touch .it-passedRemembers that they passed - uses: actions/cache/save@v4 build-image: needs: [check-cache, integration-tests] if: ${{ always() && !failure() && !cancelled() }}Or the skip cancels the build too deploy: needs: build-image if: ${{ always() && needs.build-image.result == 'success' }}And again for every later jobunchanged := inputs.Files("src/**", "go.sum", "migrations/**")What the tests readtests := sw.Job(plan, "integration-tests", runTests).Memo(unchanged)Skip when it hasn’t changedbuild := sw.Job(plan, "build-image", buildImage).Needs(tests)sw.Job(plan, "deploy", deploy).Needs(build)Downstream jobs read the memoized result and run normally. No guards, no marker files, no runner.
LOCAL CODE. MORE HORSEPOWER.
Your laptop runs what fits.
Your other machines take the rest.
Sparkwing Cloud catches the overflow. It all starts from one local run of your uncommitted code.
sparkwing run <pipeline>Your laptop
Uncommitted code
- linthere
- unit testshere
- buildqueued
- e2e testsqueued
Gaming desktop
Ready for work
Spare server
Ready for work
Sparkwing Cloud
Standing by
Warm Linux runnersAgents are working. Lint, unit tests, the build and e2e tests all queue on your laptop, and the CPU throttles.
Cloud agents get the same local feel. Run pipelines on demand, instead of pushing a GitHub Actions run and polling for it.
Three ways to host.
Choose an option to light up what it provides.
Ready to ditch the YAML?
curl -fsSL https://sparkwing.dev/install.sh | shsparkwing pipeline new --name release --template build-test-deploysparkwing run releasesparkwing dashboard start# then view at localhost:4343
Run `sparkwing info || curl -fsSL https://sparkwing.dev/install.sh | sh` and follow its guidance. Create a lint pipeline with `sparkwing pipeline new --name lint --on pre_commit`, make it run this repository’s linters and formatters, and get it passing with `sparkwing run lint`. Then install it as a git hook with `sparkwing pipeline hooks install`.
Go pipelines need Go 1.26+. TypeScript pipelines need Node.js 22.18+. macOS, Linux, and Windows with Git Bash.
