Deployment
Sparkwing is unopinionated about how your pipelines deploy. It provides the infrastructure - controller, runners, cache, registries - and your pipeline code decides what to do with it.
Run Targets
sparkwing run executes locally; sparkwing pipeline trigger dispatches
to a cluster via a profile's controller:
| Source | Target | Command |
|---|---|---|
| Local code | Local machine | sparkwing run build |
| Local working tree at a git ref | Local machine | sparkwing run build --sw-ref main |
| Local code | Any cluster | sparkwing pipeline trigger build --profile dev |
| Controller-registered source | Any cluster | sparkwing pipeline trigger build --profile prod |
The --profile flag resolves a profile - a named cluster endpoint.
Every profile with a controller follows the same dispatch flow:
sparkwing pipeline trigger <pipeline> --profile <profile>
→ upload code to controller (or trigger by SHA if clean)
→ controller enqueues run
→ dispatcher creates k8s Job
→ runner executes pipeline
The runner does not care which cluster it lives in. The same pipeline binary runs everywhere - the only differences are the controller URL and the registries available.
Profiles
Profiles map cluster names to controller URLs. Stored in
~/.config/sparkwing/profiles.yaml:
profiles:
dev:
controller:
url: http://localhost:9001
token: <api-token>
prod:
controller:
url: https://api.example.com
token: <api-token>
Register profiles with sparkwing configure profiles add.
Deploy Strategies
What happens after a pipeline builds images is entirely up to the pipeline author. Common patterns:
kubectl (simple, works everywhere)
func (j *Deploy) Run(ctx context.Context) error {
_, err := sparkwing.Bash(ctx, "kubectl rollout restart deploy/myapp -n default").Run()
return err
}
GitOps + ArgoCD
Push updated image tags to a gitops repo, let ArgoCD sync:
func (j *Deploy) Work(w *sw.Work) (*sw.WorkStep, error) {
update := sw.Step(w, "update-gitops", func(ctx context.Context) error {
return patchKustomization(ctx)
})
sw.Step(w, "sync-argocd", func(ctx context.Context) error {
_, err := sw.Bash(ctx,
"kubectl annotate application.argoproj.io/myapp -n argocd "+
"argocd.argoproj.io/refresh=hard --overwrite").Run()
return err
}).Needs(update)
return nil, nil
}
Helm
_, err := sparkwing.Bash(ctx,
"helm upgrade myapp ./charts/myapp --set image.tag="+tag).Run()
S3 Static Sites
_, err := sparkwing.Bash(ctx, "aws s3 sync out/ s3://my-bucket/ --delete").Run()
Anything else
Pipelines are Go functions. If you can script it, you can deploy it -
Terraform, Pulumi, rsync, custom APIs, etc.
Container Registries
Sparkwing creates an in-cluster registry at NodePort 30500. Pipelines can push to any registry they want:
| Registry | Example |
|---|---|
| In-cluster | localhost:30500/myapp:latest |
| ECR | <account>.dkr.ecr.<region>.amazonaws.com/myapp:v1 |
| Docker Hub | docker.io/myorg/myapp:v1 |
| GCR / GAR | gcr.io/myproject/myapp:v1 |
The SDK provides sparkwing.Exec() and sparkwing.Bash() - use whatever
Docker / registry commands your pipeline needs.
Change Detection
Pipelines can implement their own change detection. A common pattern is mapping file paths to images:
var appMapping = []struct {
prefix string
images []string
}{
{"web/", []string{"frontend"}},
{"cmd/api/", []string{"api-server"}},
{"pkg/", []string{"api-server", "worker"}},
}
// Compare prefixes against rc.Git.ChangedFiles(ctx, base)
Sources for changed files:
rc.Git.ChangedFiles(ctx, base)- repo-relative paths changed between a base ref and HEAD (a git diff; see sdk-reference.md)- An explicit
--all-style input on your pipeline to deploy everything