Core concepts
SpinUP has three primary domain objects. Understanding how they relate makes everything else in the platform obvious.
Application
An Application is the deployable unit. It maps 1:1 to a Spin app, which maps 1:1 to a K8s pod.
An Application has:
name— DNS-1123 label. Used as the SpinApp CR name, the OCI image name, the K8s Service name.language— one ofgo,js,ts,rust. Determines which builder image runs.- N functions — the HTTP handlers packed into this Application.
Applications are the packing unit: N functions in one Application share one pod, one wasmtime process, and one OCI image.
Function
A Function is one HTTP trigger inside an Application. It has:
name— DNS-1123 label, unique within the Application.route— Spin route pattern. Wildcard (/...), prefix (/api/...), or exact (/health).source— a JSON blob of{filename: content}pairs. Edited in the Monaco editor, or uploaded as.tar.gz.
When you create an Application, SpinUP auto-creates a first Function with the same name. Multi-function apps add more via POST /api/v1/applications/{id}/functions.
Why the extra layer?
Spin apps already support multiple HTTP triggers. The reason to expose "Application" separately is so the UI can present N functions grouped under one deployable, and so the platform can reason about scheduling (one pod per Application, not per Function). This shipped as Option A on the scaling roadmap.
Build
A Build is one attempt to compile + push an Application. It's a row in the builds table plus a K8s Batch Job that materializes it.
Lifecycle:
pending— created, Job not yet scheduledrunning— Job's pod is executingspin build+spin registry pushsucceeded— image pushed,SpinAppCR patchedfailed— Job hit its backoff limit, error text captured
The build ID (a UUID without dashes) is also the OCI image tag: registry/spinup/{app-name}:{buildId}. That gives us immutable, content-addressable deploys.
Only successful builds produce an image. Failed builds retain their pod logs for inspection.
How Applications run
Each Application's build produces a SpinApp custom resource. spin-operator translates that into a Deployment + Service. Pods use runtimeClassName: wasmtime-spin-v2 so the containerd-shim-spin runs the WASM component directly (no Node/JVM in between).
Properties:
- One pod per Application, always running (subject to
spec.replicas, default 1) - Standard K8s isolation — pod boundaries, ResourceQuotas, NetworkPolicies all apply
- CPU/memory limits set per-pod via
SpinApp.spec.resources - Horizontal auto-scaling supported via SpinKube's HPA (
SpinApp.spec.enableAutoscaling) - Deploy = spin-operator rolls the Deployment; old pod terminates once new pod is Ready
Data model at a glance
tenants
└── applications
├── language (go | js | ts | rust)
├── functions[]
│ ├── route
│ └── source (files JSON)
└── builds[]
├── status
├── imageRef
└── errorSee Database schema for the full column list.
What's not in the model (yet)
- Environments (dev/staging/prod) — one control plane per cluster today.
- Secrets — plaintext env vars on the SpinApp CR only; a proper secrets integration is queued.
- Non-HTTP triggers (Redis, cron) — Spin supports them; SpinUP doesn't expose them yet.
- Version pinning — the latest successful build is always deployed. Manual rollback works by deleting the newer build.
- Scale to zero + dense packing — see the scaling roadmap.