A Spin HTTP component (Rust → WebAssembly) that proxies requests to Linode Object Storage. It signs each request with AWS Signature V4 in-memory — no AWS SDK, no network round-trip to obtain credentials — and forwards it to the bucket.
Everything after the /storage/ route prefix is the object key, so /storage/sub/dir/a.txt
maps to the key sub/dir/a.txt.
| Method | Behaviour |
|---|---|
GET |
Returns the object, with its upstream content-type. |
PUT |
Uploads the request body as the object. |
DELETE |
Deletes the object (204 on success). |
| other | 405 with an Allow: GET, PUT, DELETE header. |
Upstream failures are passed through unchanged — status plus Linode's XML <Error> body — so a
SignatureDoesNotMatch is distinguishable from a NoSuchKey with nothing but curl.
- The Spin CLI
- Rust with the
wasm32-wasip2target:rustup target add wasm32-wasip2
Config comes from Spin variables, declared in spin.toml. Locally, supply them as
SPIN_VARIABLE_-prefixed environment variables in a .env file:
SPIN_VARIABLE_S3_ACCESS_KEY="..."
SPIN_VARIABLE_S3_SECRET_KEY="..."
SPIN_VARIABLE_S3_BUCKET="your-bucket"
SPIN_VARIABLE_S3_ENDPOINT="nl-ams-1.linodeobjects.com"
SPIN_VARIABLE_S3_REGION="nl-ams-1".env holds live credentials and is gitignored — don't commit it.
allowed_outbound_hosts in the manifest is templated as https://{{ s3_bucket }}.{{ s3_endpoint }},
so pointing the component at another bucket or cluster only means changing these variables.
spin build # cargo build --target wasm32-wasip2 --release
set -a; . ./.env; set +a # load the variables
spin up # http://127.0.0.1:3000Then:
curl -X PUT http://127.0.0.1:3000/storage/test.txt \
-H 'content-type: text/plain' --data-binary "Hello from Spin Wasm!"
curl http://127.0.0.1:3000/storage/test.txt
curl -X DELETE http://127.0.0.1:3000/storage/test.txttest.sh runs this sequence end to end against a real bucket, starting and stopping
its own spin up.
Needs the aka plugin (spin plugins install aka) and a one-off spin aka login.
The deployed app gets its variables from a vars.json. Note the names are the lowercase
ones from spin.toml — not the SPIN_VARIABLE_-prefixed form .env uses:
{
"s3_access_key": "YOUR-ACCESS-KEY",
"s3_secret_key": "YOUR-SECRET-KEY",
"s3_region": "nl-ams-1",
"s3_endpoint": "nl-ams-1.linodeobjects.com",
"s3_bucket": "YOUR-BUCKET-NAME"
}Then deploy:
spin aka deploy --build --variable @vars.jsonAdd --create-name s3sample on the first deploy, or --app-id <id> to target an
existing app. vars.json holds your secret key, so it is gitignored — keep it that way.
Passing the values individually works too, but they end up visible in ps output:
spin aka deploy --build --variable s3_bucket=my-bucket --variable s3_access_key=...- Request and response bodies are buffered in memory, so this is suited to small objects. Streaming is not implemented yet.
- See CLAUDE.md for the architecture constraints and the SigV4 details that matter for Linode specifically.