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 TargetsSection anchor link

sparkwing run executes locally; sparkwing pipeline trigger dispatches to a cluster via a profile's controller:

SourceTargetCommand
Local codeLocal machinesparkwing run build
Local working tree at a git refLocal machinesparkwing run build --sw-ref main
Local codeAny clustersparkwing pipeline trigger build --profile dev
Controller-registered sourceAny clustersparkwing 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.

ProfilesSection anchor link

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 StrategiesSection anchor link

What happens after a pipeline builds images is entirely up to the pipeline author. Common patterns:

kubectl (simple, works everywhere)Section anchor link

func (j *Deploy) Run(ctx context.Context) error {
    _, err := sparkwing.Bash(ctx, "kubectl rollout restart deploy/myapp -n default").Run()
    return err
}

GitOps + ArgoCDSection anchor link

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
}

HelmSection anchor link

_, err := sparkwing.Bash(ctx,
    "helm upgrade myapp ./charts/myapp --set image.tag="+tag).Run()

S3 Static SitesSection anchor link

_, err := sparkwing.Bash(ctx, "aws s3 sync out/ s3://my-bucket/ --delete").Run()

Anything elseSection anchor link

Pipelines are Go functions. If you can script it, you can deploy it - Terraform, Pulumi, rsync, custom APIs, etc.

Container RegistriesSection anchor link

Sparkwing creates an in-cluster registry at NodePort 30500. Pipelines can push to any registry they want:

RegistryExample
In-clusterlocalhost:30500/myapp:latest
ECR<account>.dkr.ecr.<region>.amazonaws.com/myapp:v1
Docker Hubdocker.io/myorg/myapp:v1
GCR / GARgcr.io/myproject/myapp:v1

The SDK provides sparkwing.Exec() and sparkwing.Bash() - use whatever Docker / registry commands your pipeline needs.

Change DetectionSection anchor link

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:

  1. rc.Git.ChangedFiles(ctx, base) - repo-relative paths changed between a base ref and HEAD (a git diff; see sdk-reference.md)
  2. An explicit --all-style input on your pipeline to deploy everything