Getting Started
Two paths
Local is the product. Sparkwing is a program on your machine: it
compiles .sparkwing/ and runs each job as a host subprocess, keeps state
in SQLite under ~/.sparkwing/, and serves its own dashboard. No account
and no cloud. After one successful run, a pipeline whose sparks are
pinned to exact tags runs with the network unplugged; see
offline after the first build for what
still needs it.
Sparkwing Cloud is the hosted controller. A controller gives a team
one dashboard, one run history, and one queue: a triggered run waits there
until a runner claims it, and fails with queue_timeout at the queue
deadline when none does (see scheduling.md). The
Cloud profile selects the hosted controller:
sparkwing profiles add --controller https://api.sparkwing.example --token-stdin
Start local. Read Install and Quick start, then Sparkwing Cloud when you want a team to see the same runs. Self-hosted runners adds capacity from your own machines to Cloud.
Install
macOS / Linux
curl -fsSL https://sparkwing.dev/install.sh | sh
It detects your OS and architecture, pulls the matching release binary,
and drops sparkwing into ~/.local/bin. For a specific version:
curl -fsSL https://sparkwing.dev/install.sh | sh -s -- --version vX.Y.Z.
A machine that runs work for a controller, through sparkwing runners add,
also needs sparkwing-runner. --runner installs it beside sparkwing from
the same release after the same checks:
curl -fsSL https://sparkwing.dev/install.sh | sh -s -- --runner.
sparkwing update updates only the CLI. An installed runner updates itself
from verified release assets when the signed version index names a newer
release.
To place the binary yourself, download the asset for your platform from GitHub Releases:
# macOS Apple Silicon
curl -fsSL https://github.com/sparkwing-dev/sparkwing/releases/latest/download/sparkwing-darwin-arm64 \
-o ~/.local/bin/sparkwing && chmod +x ~/.local/bin/sparkwing
# macOS Intel
curl -fsSL https://github.com/sparkwing-dev/sparkwing/releases/latest/download/sparkwing-darwin-amd64 \
-o ~/.local/bin/sparkwing && chmod +x ~/.local/bin/sparkwing
# Linux x86_64
curl -fsSL https://github.com/sparkwing-dev/sparkwing/releases/latest/download/sparkwing-linux-amd64 \
-o ~/.local/bin/sparkwing && chmod +x ~/.local/bin/sparkwing
# Linux ARM64
curl -fsSL https://github.com/sparkwing-dev/sparkwing/releases/latest/download/sparkwing-linux-arm64 \
-o ~/.local/bin/sparkwing && chmod +x ~/.local/bin/sparkwing
Keep to one location: sparkwing doctor reports competing copies on
PATH, and a second copy shadows the first.
The install script checks what it downloads. It verifies the ed25519
signature over SHA256SUMS, then the asset's digest against its line in
that file, then the asset's own signature, all against a public key
built into the script, and installs nothing that fails. It needs OpenSSL
3.0 or newer; on macOS run brew install openssl@3, because the system
openssl is LibreSSL and cannot verify an ed25519 signature.
A hand-placed download skips every one of those checks. SHA256SUMS
reaches you from the same origin as the binary, so comparing the two
proves the transfer finished and nothing else. The check that carries a
guarantee is SHA256SUMS.sig, published beside it with one .sig per
asset and signed with the key sparkwing update carries.
Each release publishes the install script itself as install.sh with an
install.sh.sig under the same tag, so a copy taken from anywhere can be
checked before it runs. The trusted keys are
internal/releaseauth.TrustedPublicKeys in the sparkwing repository.
Or, if Go is on PATH, build from source:
go install github.com/sparkwing-dev/sparkwing/cmd/sparkwing@latest
This installs the sparkwing binary, which is the single CLI for
both admin / inspection (sparkwing dashboard start,
sparkwing pipeline list) and pipeline invocation
(sparkwing run <pipeline>).
Note:
go installdoes not include the Next.js dashboard bundle, which is a generated artifact and not checked into the repository. A source-built binary will refuse to startsparkwing dashboardwith a clear message pointing back to the release binary. CLI-only commands (run,pipeline,runs, etc.) work fine. If you want the dashboard from a source checkout, runbash bin/build-web.shfirst to generate the bundle, thengo install ./cmd/sparkwingfrom the repo root.
Windows
Prebuilt Windows binaries are published to GitHub Releases:
sparkwing-windows-amd64.exe and sparkwing-windows-arm64.exe. They
embed the dashboard bundle, so sparkwing dashboard start runs locally
just as it does on macOS/Linux. Download the one for your architecture,
rename it to sparkwing.exe, and put it on PATH. Install
Git for Windows as well -- pipelines
call out to sparkwing.Bash / sparkwing.Exec at runtime.
To build from source instead, with a Go toolchain on PATH, in a Git Bash terminal:
go install github.com/sparkwing-dev/sparkwing/cmd/sparkwing@latest
A source build does not include the Next.js dashboard bundle (it is a
generated artifact, not checked into the repository), so
sparkwing dashboard start on a source-built CLI refuses to start unless
you run bash bin/build-web.sh from a repo checkout first. The prebuilt
release binary has no such limitation.
The bundled sparkwing-runner service installer supports Linux and macOS.
On Windows, supervise sparkwing-runner.exe agent yourself or use the Linux
installer inside WSL when systemd user services are enabled.
Quick Start
# 1. Set up your repo
cd your-project
sparkwing pipeline new --pipeline release # single-node minimal template by default
# 2. Run your first pipeline
sparkwing run release
# 3. (Optional) Watch runs in the browser
sparkwing dashboard start # detached local dashboard + API on :4343
sparkwing dashboard open # one-time sign-in link for the browser
In a new Git repository, preview the scaffold before your first commit with
sparkwing pipeline plan --pipeline release. Running requires an initial
commit so intake can record a commit identity. Review and commit your files,
or run git commit --allow-empty -m 'Initial commit', then retry
sparkwing run release.
The scaffold adds sparkwing-pipeline to .sparkwing/.gitignore so Git
ignores an untracked generated binary. Git does not untrack a binary already
in its index; remove that with git rm --cached .sparkwing/sparkwing-pipeline.
The scaffold preserves other entries in the local ignore file and leaves the
repository's root .gitignore untouched. If .sparkwing/.gitignore points
outside .sparkwing/, it prints a note asking you to add the binary to an
ignore file.
For a build/test/deploy DAG instead of a single node, pass
--template build-test-deploy:
sparkwing pipeline new --pipeline release --template build-test-deploy
The five Go shapes are structure only -- they run green in any repo, and
you replace the echo bodies with real work. For how a real pipeline is
written, read a finished one: sparkwing examples lists the registry
(static-site deploys, containerized deploys to Kubernetes,
migrate+deploy, CI-hygiene gates, and more), and --example <example> --body prints the source. Usually sparkwing docs search --search "<task>"
gets you there in one step, since it ranks examples alongside the docs.
For a TypeScript project, scaffold a minimal pipeline with:
sparkwing pipeline new --pipeline hello --template typescript
sparkwing run hello
This creates .sparkwing/package.json with an exact @sparkwing/sdk pin,
index.ts, jobs/hello.ts and sparkwing.yaml. It uses the same --on,
--hidden and --short flags as the Go shapes. Repeat the command with another
pipeline name to add a pipeline to the TypeScript entry. Sparkwing installs
the SDK dependencies when the pipeline runs.
Examples are for reading, not scaffolding: --template takes a shape,
not an example name. See the
template catalog for the full picture.
If you want to own and edit a spark library's helper code directly rather
than importing it, sparkwing pipeline sparks inflate --library <name>
copies its source into your repo. See
inflating a spark library.
Each sparkwing invocation compiles .sparkwing/ and runs the pipeline as a host
subprocess. Run state lives under ~/.sparkwing/ (SQLite + log files).
The local controller starts on demand and the dashboard commands manage its
lifecycle. See Local dashboard for service installation.
Dashboard records include url when known. Use --output plain for a single
running or stopped value, or --output pretty for the terminal layout.
A stopped status still exits 1; stopping an absent server exits 0.
If you want a local Kubernetes cluster as a deploy target for user apps (not for sparkwing itself), bring your own - any local Kubernetes setup works. Sparkwing does not run in-cluster locally; the controller is a prod-only component.
Storage class
The controller's PersistentVolumeClaim and the storageClassName a cluster
without a default StorageClass needs are covered in
Self-hosting.
Offline after the first build
After one successful sparkwing run in a checkout, a pipeline whose
sparks are pinned to exact tags runs with the network unplugged. The Go
modules sit in the module cache, the compiled pipeline binary sits in
~/.sparkwing/cache/pipelines/, and the run's state, logs, dashboard,
and admission daemon are files and sockets on your own machine.
The first run is the one that reaches out. It downloads the SDK and every
spark library your .sparkwing/go.mod requires, then compiles. A run
whose sources and pins have not changed reuses the cached binary and
compiles nothing.
What still wants the network is visible in advance:
- A
latestor range pin. Asparks:entry pinned tolatest,^v0.24.0, or~v0.10.3asks the module proxy for the newest matching tag on every run. Pass--sw-no-updateto skip resolution and compile against the overlay already on disk, or pin exact tags and pay nothing. Without the flag an unreachable proxy fails the run and the error names it. The resolution rules are in sparks.md. - A profile.
sparkwing pipeline trigger <pipeline> --profile NAMEand read verbs that name a controller profile need that controller over HTTPS.sparkwing runreads and writes only local surfaces. - What the pipeline itself does. A step that pulls an image, fetches a ref, or deploys needs whatever that step needs. Sparkwing does not change it.
- Updating sparkwing.
sparkwing updateand the install script fetch a release.
What sparkwing pipeline new Creates
.sparkwing/
.gitignore # ignores the compiled sparkwing-pipeline binary
sparkwing.yaml # registry of every pipeline this repo defines
main.go # thin entrypoint; blank-imports jobs/ and calls sparkwing.Main
README.md # generated package README
go.mod # Go module for pipeline code
go.sum # dependency checksums (from go mod tidy)
jobs/ # pipeline implementations
The Model
A pipeline is anything sparkwing (or sparkwing run) can invoke. Two
shapes share the same surface:
- triggered pipeline - a YAML entry with an
on:trigger; runs itself on push / webhook / schedule. Implemented as a Go type whose factory is registered viasparkwing.Register. - manual pipeline - a YAML entry with no
on:trigger. Runs only when explicitly invoked. Same Go registration; it's "triggered" vs "manual" distinguishes auto-firing from operator-initiated.
Both produce a Run in the local store on each invocation. The dashboard's
runs list and sparkwing runs list surface them uniformly. (One-shot
repo-local shell chores -- formatters, port-forwards, the small
Makefile-style stuff -- do not need a run record; keep them in your
existing task runner.)
A Go pipeline is a struct that implements
Plan(ctx context.Context, plan *sw.Plan, in Inputs, run sw.RunContext) error.
It registers jobs on the passed-in plan and returns; it does not build
and return one. A one-node pipeline registers a single sw.Job. See
sdk.md for the SDK reference and
pipelines.md for the Plan/Work model.
# .sparkwing/sparkwing.yaml
pipelines:
- name: build-deploy
entrypoint: BuildDeploy
description: Build and deploy the app
on:
push:
branches: [main]
// .sparkwing/jobs/build_deploy.go
import sw "github.com/sparkwing-dev/sparkwing/sparkwing"
type BuildDeploy struct{ sw.Base }
func (p *BuildDeploy) Plan(ctx context.Context, plan *sw.Plan, _ sw.NoInputs, run sw.RunContext) error {
test := sw.Job(plan, "test", &Test{})
sw.Job(plan, "build", &Build{}).Needs(test)
return nil
}
type Test struct{ sw.Base }
func (j *Test) Work(w *sw.Work) (*sw.WorkStep, error) {
sw.Step(w, "run", func(ctx context.Context) error {
_, err := sw.Bash(ctx, "go test ./...").Run()
return err
})
return nil, nil
}
type Build struct{ sw.Base }
func (j *Build) Work(w *sw.Work) (*sw.WorkStep, error) {
sw.Step(w, "run", func(ctx context.Context) error {
_, err := sw.Bash(ctx, "docker build -t myapp .").Run()
return err
})
return nil, nil
}
// At the bottom of .sparkwing/jobs/build_deploy.go:
// func init() {
// sw.Register[sw.NoInputs]("BuildDeploy", func() sw.Pipeline[sw.NoInputs] { return &BuildDeploy{} })
// }
Sparkwing Cloud includes the Docker CLI, buildx, and Compose on amd64 and arm64.
docker build, docker run, and docker compose use the job's Docker sidecar
through the supplied DOCKER_HOST. Job commands run as root inside a user
namespace, and /tmp is writable by package-manager users, so apt-get install
works. The pod and sidecar remain unprivileged. On a self-hosted runner, install
the Docker client and plugins and configure access to a Docker daemon.
Trivial single-step pipelines pass a func(ctx) error straight to sw.Job:
type Lint struct{ sw.Base }
func (p *Lint) Plan(_ context.Context, plan *sw.Plan, _ sw.NoInputs, rc sw.RunContext) error {
sw.Job(plan, rc.Pipeline, func(ctx context.Context) error {
_, err := sw.Bash(ctx, "go vet ./...").Run()
return err
})
return nil
}
Step boundaries inside a Work() are emitted automatically by each
sw.Step as structured step_start / step_end events; the
dashboard surfaces them as a collapsible bucket. For DAG-level
composition (parallel, sequence, needs, modifiers), use the Plan.
Run Targets
sparkwing run executes on this machine and records to its local store,
logs, and cache. sparkwing pipeline trigger <pipeline> --profile NAME hands execution to that profile's controller. sparkwing run also takes
--sw-ref <branch|tag|sha> to compile a git ref instead of the working
tree (trigger runs the source registered with the controller, or the
checkout's uncommitted changes with --working-tree):
sparkwing run build # run locally with local code
sparkwing run build --sw-ref main # build the main ref locally
sparkwing pipeline trigger build --profile dev # run on the "dev" cluster
sparkwing pipeline trigger build --profile dev --working-tree # uncommitted changes on "dev"
sparkwing pipeline trigger build --profile prod # run on the "prod" cluster
Cluster names are profiles. sparkwing profiles add writes one; see
Sparkwing Cloud. Sparkwing itself
does not run in-cluster locally - clusters named by --profile are
user-managed deploy targets, not local sparkwing deployments.
Sparkwing Cloud
Sparkwing Cloud is the hosted controller. A controller owns the shared dashboard, run history, scheduling, webhooks, and tokens, and machines reach it over outbound HTTPS. Self-hosted runners register to Cloud and supply capacity without operating a shared controller.
sparkwing profiles add --controller https://api.sparkwing.example --token-stdin
Use an existing member credential for team commands. Cloud operators can create a team and its first administrator without OAuth:
sparkwing operator teams create --team support-test --display-name Support --profile deployment -o json
The deployment profile requires operator.tokens. The first team credential is
returned once. See Cloud operations.
It reads the token from stdin, checks health, authenticates through
/api/v1/auth/whoami, and saves the profile in
~/.config/sparkwing/config.yaml. The result contains the controller URL and a
redacted token prefix. Use sparkwing profiles show --profile NAME to inspect
it and sparkwing profiles set --profile NAME --token-stdin to replace its token.
If you set SPARKWING_HOME to isolate runtime state, also set SPARKWING_CONFIG
to a private config path before profiles add. The runtime home alone does not
relocate the machine config.
The profile name comes from the controller host unless --profile names it.
An existing profile is replaced only with --force. Removing a profile with
sparkwing profiles remove --profile NAME revokes its token first.
--keep-token removes only the saved connection. Project configuration cannot
supply connection profiles or credentials.
Use --profile NAME to select a connection, or save one with
sparkwing profiles set --profile prod --default. Optional-profile commands
use that default when the flag is omitted. With no saved default they use the
local controller. --profile= selects local for one command.
sparkwing profiles show --profile prod # saved connection, with token redacted
sparkwing profiles remove --profile prod # revoke and remove
Removal first verifies that the saved credential is a member credential and
confirms its revocation. A failed revocation leaves the connection saved.
--keep-token removes only the connection and leaves its credential live.
To run work on this machine for that controller, add it as a runner with
sparkwing runners add --profile prod --runner this-laptop.
Start a run
On a controller with teams, a team's runners fetch each run's source from
GitHub themselves, so every run names a repository. A runner claims only
runs whose repository matches one of its --repos patterns, and a run
nothing may build stays pending. Start one from the dashboard or a terminal.
From the dashboard. On the Runs page, + Start a run opens a form that
takes a pipeline name, a repository such as https://github.com/acme/app,
and a branch (main unless you change it). The runner that claims it fetches
the branch tip.
Run submission and Run now stay pending while the controller admits the run, including repository access checks. They have no browser request deadline. If the connection drops, retry the same form or action to reuse its idempotency key and recover the admitted run without creating another. Keep the form open and its inputs unchanged for that retry. A new form or changed inputs start a new request.
Selecting a run opens its detail beside the compact runs list. Switching to another run keeps both panes in place while its detail loads; clearing the selection closes the detail.
When viewing a run, the chevron above the runs list collapses both the runs and nodes columns into status-dot rails. Hover or focus a dot to see its full label; select it to open that run or node. Screens narrower than 1100px start with the rails collapsed. Your choice to expand or collapse them is saved in this browser.
From a terminal. Under Team > Machines > CLI access, Create CLI token mints a personal token and shows it once with two commands:
sparkwing profiles add --controller https://api.sparkwing.example --profile acme --token-stdin
sparkwing pipeline trigger <pipeline> --profile acme
The first prompts for the token without echoing it and saves a profile named
after the team. Run the second inside a checkout whose commit is pushed: the
run records the checkout's origin repository and commit, and a team runner
fetches that commit. Private GitHub identity lookup accepts GH_TOKEN or
GITHUB_TOKEN with repository read access. The controller credential needs
runs.write. Add --working-tree to capture tracked and unignored untracked
files into an immutable Git snapshot, upload it through shared storage, and seal
its digest into the run. The original checkout and index stay untouched.
Private GitHub repositories also require the controller's GitHub App or a
configured team HTTPS source credential; the CLI's environment token stays local.
Runs started by a push come with the Sparkwing GitHub App.
Advanced deployments
The launch offering is Local and Cloud. Self-hosted runners register to Cloud. Operating a shared controller is deferred to a future licensed offering. The deployment modes page compares the two modes. The controller chart stays available for Cloud operators.