Skip to content

Commit 09abd4c

Browse files
committed
add SIP - packaging components
Signed-off-by: Michelle Dhanani <mdhanani@akamai.com>
1 parent 1a419a8 commit 09abd4c

2 files changed

Lines changed: 235 additions & 1 deletion

File tree

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
title = "SIP 026 - Packaging Components"
2+
template = "main"
3+
date = "2026-08-25T00:00:00Z"
4+
---
5+
6+
Summary: This SIP introduces a standalone component manifest (`component.toml`) and the CLI support to build and distribute an individual Spin component on its own, independent of any application. Reusable components — HTTP middleware being the prime example — can then be built, versioned, published to a registry, and pulled back down.
7+
8+
Owner(s): Michelle Dhanani <mdhanani@akamai.com>
9+
10+
Created: August 25, 2026
11+
12+
## Background
13+
14+
A Spin application is, today, always a *runnable* unit: the manifest is required
15+
to declare one or more triggers, and the tooling (`spin up`, `spin build`,
16+
`spin registry push`) is oriented around that assumption. This works well for
17+
services, but it does not describe a component that is meant to be *consumed by
18+
other applications* rather than run on its own (i.e. dependencies)
19+
20+
The motivating case is HTTP middleware. A component such as a GitHub OAuth gate
21+
is not an application — it has no trigger and does nothing on its own — yet it is
22+
highly reusable across applications. [SIP 020 (Component
23+
Dependencies)](../sips/020-component-dependencies.md) already lets an application
24+
consume such a component from a registry, and [SIP 024 (`spin deps` CLI
25+
DX)](../sips/024-spin-deps-cli-dx.md) makes wiring one up interactive. The gap is
26+
on the *author* side: there is no way to describe a single component, build it,
27+
and publish it so that others can depend on it.
28+
29+
Tools such as wkg can already push and pull a bare component wasm to a registry. But a component alone advertises only its WIT world — the imports and exports it declares — not the operational configuration a consuming application must grant it: variables and secrets, allowed outbound hosts, key-value stores, SQLite databases, AI models, or mounted files. Spin needs a manifest that travels with the component so tooling can determine programmatically what a consumer must provide.
30+
31+
An earlier draft proposed a manifest dedicated to packaging *middleware*. But
32+
nothing about building and distributing a component is specific to middleware: a
33+
plain library component, a trigger-less utility, and a piece of HTTP middleware
34+
all share the same needs. This SIP therefore supersedes that middleware-only
35+
proposal with a general **component manifest** that applies to any component.
36+
37+
## Proposal
38+
39+
Introduce a standalone component manifest, `component.toml`, that describes a
40+
single component: its identity, how to build it, and the host capabilities and
41+
configuration it requires. Extend `spin build` to build from a component manifest,
42+
and extend `spin registry` to publish and fetch components as registry packages.
43+
44+
### The component manifest (`component.toml`)
45+
46+
```toml
47+
component_manifest_version = 1
48+
49+
[component]
50+
name = "github-oauth"
51+
source = "target/wasm32-wasip2/release/github_oauth.wasm"
52+
version = "0.1.0"
53+
description = "HTTP middleware that gates requests behind GitHub OAuth"
54+
authors = ["Michelle Dhanani <mdhanani@akamai.com>"]
55+
repository = "https://github.com/michellen/github-oauth-middleware"
56+
license = "Apache-2.0"
57+
58+
[build]
59+
command = "cargo build --target wasm32-wasip2 --release"
60+
61+
[requires]
62+
variables = [
63+
"github_client_id",
64+
{ name = "github_client_secret", secret = true },
65+
]
66+
allowed_outbound_hosts = ["https://github.com", "https://api.github.com"]
67+
key_value_stores = ["default"]
68+
sql_variables = ["default"]
69+
ai_models = ["llama2-chat"]
70+
environment_variables = ["staging", { name = "region", default = "us" }]
71+
files = ["assets/**/*", { source = "local/path", destination = "/mounted/path" }]
72+
```
73+
74+
The manifest is intentionally close to a single `[component.<id>]` entry in
75+
`spin.toml`, but reorganised so a component can stand on its own.
76+
77+
#### `component_manifest_version`
78+
79+
`component_manifest_version = 1` identifies the file as a component manifest and
80+
distinguishes it from an application manifest (`spin_manifest_version`). The
81+
value is a fixed `1`; future revisions will bump it.
82+
83+
#### `[component]` — identity and artifact
84+
85+
| Field | Required | Description |
86+
| --- | --- | --- |
87+
| `name` | yes | The component's identifier. |
88+
| `source` | yes | Path to the built Wasm artifact. Required because it is the file that is published and packaged. |
89+
| `version` | for publishing | Semver version. Used as the version when publishing. |
90+
| `description`, `authors`, `repository`, `license` | no | Human-readable metadata. |
91+
92+
`version` is only required when the component is published; a component that is only built locally may omit them.
93+
94+
`source` lives under `[component]` (not `[build]`) because it is the component's
95+
artifact — the thing that is packaged — and exists whether or not the component
96+
is built locally (for example, a pre-built component that is only being
97+
republished).
98+
99+
#### `[build]` — how to build (optional)
100+
101+
Mirrors the `[component.<id>.build]` table in `spin.toml`:
102+
103+
| Field | Required | Description |
104+
| --- | --- | --- |
105+
| `command` | yes (if `[build]` present) | The build command, or an array of commands run in sequence. |
106+
| `workdir` | no | Working directory for the build, relative to the manifest. |
107+
108+
`[build]` is optional: a pre-built component may be described and published with
109+
only a `source`. A `watch` field (globs for `spin watch`) is intentionally *not*
110+
included yet — `spin watch` does not operate on component manifests, so the field
111+
would have no effect. It will be added together with `spin watch` support for
112+
component manifests (see [Future work](#future-work)).
113+
114+
#### `[requires]` — host capabilities (optional)
115+
116+
Declares the capabilities the component expects the host application to grant it.
117+
These mirror the capability fields of a `spin.toml` component:
118+
119+
| Field | Description |
120+
| --- | --- |
121+
| `variables` | Configuration variables the component consumes. Each entry is a bare name, or `{ name, default, secret }`. |
122+
| `key_value_stores` | Key-value store labels the component accesses. |
123+
| `sql_variables` | SQLite database labels the component accesses. |
124+
| `ai_models` | AI models the component accesses. |
125+
| `allowed_outbound_hosts` | Outbound network destinations the component is allowed to reach. |
126+
| `environments_variables` | Environment variables the component needs. Each entry is a bare name, or `{ name, default }`. |
127+
| `files` | Files the component may read: a glob, or `{ source, destination }` mount. |
128+
129+
`[requires]` is descriptive: it documents what an application must provide when it
130+
adopts the component. It is consumed at application-assembly time (see [Future
131+
work](#future-work)) rather than at build time.
132+
133+
### Building a component
134+
135+
`spin build` recognizes a component manifest, runs its `[build].command`, and embeds
136+
the component manifest (omitted the `[build]` section) as JSON in a custom section of the built binary:
137+
138+
```console
139+
$ spin build -f component.toml
140+
Building component github-oauth with `cargo build --target wasm32-wasip2 --release`
141+
Finished building all Spin components
142+
```
143+
144+
When invoked without `-f`, `spin build` searches for a manifest, preferring an
145+
application manifest (`spin.toml`) and falling back to a component manifest
146+
(`component.toml`). If the component manifest has no `[build]` section, `spin
147+
build` reports that there is nothing to build (the component is treated as
148+
pre-built).
149+
150+
Only `spin build` operates on component manifests. `spin up`, `spin deploy`, and
151+
similar commands continue to require an application manifest, because a lone
152+
component has no trigger and cannot be run on its own.
153+
154+
### Publishing and fetching components
155+
156+
Reusable components are distributed as
157+
[wasm-pkg](https://github.com/bytecodealliance/wasm-pkg-tools) component packages
158+
— the same package format Spin already resolves when a component declares a
159+
registry dependency (SIP 020). This makes a published component immediately
160+
consumable as a dependency by other applications.
161+
162+
#### `spin registry push`
163+
164+
```console
165+
$ spin registry push -f component.toml --registry ghcr.io/michellen/spin-components
166+
Pushed component spin-components:github-oauth@0.1.0
167+
```
168+
169+
- The published **package reference** is `namespace:name` where namespace is derived from
170+
registry or is overriden by the `--package-namespace` flag, name is `[component].name` and
171+
the version is `[component].version`.
172+
- The component's built `source` must exist; otherwise Spin reports an error and
173+
suggests building first (`spin registry push --build`).
174+
- `--build` performs a default `spin build` (component-aware) before publishing.
175+
- `spin registry push` detects a component manifest and takes the component path;
176+
an application manifest continues to be pushed as a Spin application OCI
177+
artifact (with its registry reference argument), unchanged.
178+
179+
#### `spin registry pull`
180+
181+
```console
182+
$ spin registry pull ghcr.io/michellen/spin-components/github-oauth:0.1.0 --output github-oauth.wasm
183+
Pulled component to github-oauth.wasm
184+
```
185+
186+
- The version portion is a semver requirement; when omitted, the latest
187+
non-yanked release is pulled.
188+
- `--output` selects where the component Wasm is written; it defaults to
189+
`<name>.wasm` in the current directory.
190+
191+
## Relationship to other SIPs
192+
193+
- **[SIP 020 — Component Dependencies](../sips/020-component-dependencies.md):**
194+
this SIP is the producer side of that consumer feature. A component published
195+
here can be referenced in another application's `[component.dependencies]` and `[trigger.dependencies]`.
196+
- **[SIP 024 — `spin deps` CLI DX](../sips/024-spin-deps-cli-dx.md):** components
197+
published here are exactly what `spin deps add` resolves and wires up,
198+
including HTTP middleware.
199+
- **[SIP 008 — OCI registries](../sips/008-using-oci-registries.md):**
200+
application distribution continues to use OCI application artifacts.
201+
Component distribution uses wasm-pkg component packages so components are
202+
resolvable as dependencies.
203+
204+
## Future work
205+
206+
- **Assembling an application from `[requires]`.** Tooling like `spin deps` could
207+
read `[requires]` and scaffold or validate the host application's grants when a
208+
component is adopted.
209+
- **`component.toml` discovery for more commands.** Only `spin build` and `spin
210+
registry` recognise component manifests today; `spin watch` and others could
211+
follow if there is demand.
212+
213+
## Alternatives considered
214+
215+
- **Publish a component as a single-component Spin application.** A component
216+
could be wrapped in a synthetic `spin.toml` and pushed as an application OCI
217+
artifact. This reuses the application pipeline but produces an artifact that is
218+
semantically an *application* (with no trigger) and is **not** resolvable as a
219+
component dependency, defeating the primary purpose. Publishing a wasm-pkg
220+
component package instead makes the result directly consumable by spin, wkg and
221+
potentially other tools.
222+
- **A middleware-specific manifest.** The original draft targeted middleware
223+
only. Since building and distributing a component is not middleware-specific,
224+
a general component manifest serves middleware and all other reusable
225+
components with one mechanism.
226+
- **Using OCI annotations** to relay the `[requires]` information was considered rather
227+
than embedding component manifest in the component binary but that would lock component packages to OCI registries and we may want the option to distribute via github releases or other distribution platforms.

docs/content/sips/index.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,11 @@ Here is a list of accepted SIPs:
2929
- [Support for Inbound WebSockets](../sips/016-inbound-websockets.md)
3030
- [Application-Internal Service Chaining](../sips/017-service-chaining.md)
3131
- [Adding OTel tracing to Spin](../sips/018-adding-otel-tracing-to-spin.md)
32-
- [Spin Governance](../sips/019-governance.md)
32+
- [Spin Governance](../sips/019-governance.md)
33+
- [Component Dependencies](../sips/020-component-dependencies.md)
34+
- [Spin Factors](../sips/021-spin-factors.md)
35+
- [Build Profiles](../sips/022-build-profiles.md)
36+
- [Fine-grained Capability Inheritance for Component Dependencies](../sips/023-fine-grained-capability-inheritance.md)
37+
- [spin deps CLI DX](../sips/024-spin-deps-cli-dx.md)
38+
- [Target Environment Validation](../sips/025-validate-target-environment.md)
39+
- [Packaging Components](../sips/026-packaging-components.md)

0 commit comments

Comments
 (0)