Application Servers Azure

Spin on Ubuntu 24.04 on Azure User Guide

| Product: Spin 4.0.2 on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of Spin on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Spin is an open source runtime for building and running WebAssembly serverless applications and microservices. It is a single self contained binary that loads a WebAssembly component and serves it over HTTP, so you run event driven microservices with the fast cold starts, strong sandboxing and tiny footprint of WebAssembly, without managing a language runtime or a container for each service.

The image installs Spin 4.0.2 from the official prebuilt release binary, run under systemd as an unprivileged spin user. The Spin runtime is bound to the loopback interface on port 3000, and nginx terminates HTTPS on port 443 and reverse proxies requests to it, so the WebAssembly application itself is never directly exposed to the network. Plain HTTP on port 80 permanently redirects to HTTPS. A small prebuilt demo WebAssembly HTTP component ships in the application directory so a freshly launched instance immediately demonstrates end to end WebAssembly execution.

Secure by default. The Spin application is bound to loopback only, so the sole network reachable surface is the nginx TLS reverse proxy. The demo component runs inside the Spin sandbox with no ambient access to the host. On the first boot of every VM a one shot service generates a fresh per instance self signed TLS certificate bound to that instance address, so the server answers over HTTPS immediately. Spin has no login, so no shared or default credential of any kind ships in the image.

What is included:

  • Spin 4.0.2 from the official prebuilt release binary, run under systemd as the unprivileged spin user (spin.service), bound to 127.0.0.1:3000

  • nginx terminating HTTPS on port 443 and reverse proxying to the Spin runtime, with an HTTP to HTTPS permanent redirect on port 80 (nginx.service)

  • A demo WebAssembly HTTP component at /opt/spin-app/index.wasm that echoes your request path and reports a value it computes at request time, proving the Spin runtime executes the component rather than serving a static file (replace it with your own component)

  • The whole runtime in a single prebuilt binary with no separate language runtime to install and no container image to build for each microservice

  • The Spin application bound to loopback only, so only the nginx TLS proxy is network reachable

  • A per instance self signed TLS certificate generated on first boot, ready for you to front with your own domain and a trusted certificate

  • The Spin application manifest in one readable file, /opt/spin-app/spin.toml, that you version control

Prerequisites

  • Active Azure subscription, SSH public key, VNet + subnet in target region

  • Subscription to the Spin listing on Azure Marketplace

  • Network Security Group rules allowing TCP 22 (admin) from your management network, and TCP 443 (HTTPS) plus TCP 80 (HTTP redirect) from the clients that will reach your application

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is ample for a demo or a small application. Size up for production microservice workloads and higher request concurrency.

Step 1: Deploy from the Azure Portal

  1. Open the Spin offer on the Azure Marketplace and select Get It Now, then Create.
  2. Choose your subscription, resource group and region.
  3. Set the VM size (Standard_B2s or larger), your SSH public key and the admin username azureuser.
  4. On the Networking tab, attach a Network Security Group that allows inbound TCP 22 from your management network and TCP 443 and TCP 80 from your users.
  5. Review and create. When the deployment completes, note the VM public IP address.

Step 2: Deploy from the Azure CLI

az vm create \
  --resource-group my-rg \
  --name my-spin \
  --image <marketplace-image-urn> \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_rsa.pub \
  --public-ip-sku Standard

# open HTTPS (443), the HTTP redirect (80) and SSH (22)
az vm open-port --resource-group my-rg --name my-spin --port 443 --priority 1001
az vm open-port --resource-group my-rg --name my-spin --port 80  --priority 1002

Step 3: First boot

On the first boot the spin-firstboot.service one shot unit generates a fresh per instance self signed TLS certificate (bound to this VM's IP address), writes an informational note to /root/spin-info.txt, then starts spin.service and nginx.service. This takes a few seconds. SSH in as azureuser:

ssh azureuser@<vm-public-ip>

Step 4: Confirm the services are running

Confirm spin.service and nginx.service are active, check the Spin version, and confirm the runtime is bound to loopback on port 3000 while nginx is listening on port 443 (HTTPS) and port 80 (the redirect):

sudo systemctl is-active spin nginx
/usr/local/bin/spin --version
sudo ss -tln | grep -E '127.0.0.1:3000|:443 |:80 '

You should see active for both services, a version line reporting spin 4.0.2, the Spin runtime listening on 127.0.0.1:3000, and nginx listening on *:443 and *:80.

spin.service and nginx.service both report active, the spin binary reports version spin 4.0.2, and ss shows the Spin runtime listening on 127.0.0.1 port 3000 for loopback only while nginx listens on port 443 for HTTPS and port 80 for the HTTP to HTTPS redirect on all interfaces

Step 5: Verify WebAssembly execution over HTTPS

The demo component at /opt/spin-app/index.wasm is executed by the Spin runtime for every request: it echoes the request path back to you and reports a value it computes at request time (6 x 7), so you can prove the Spin runtime is executing the WebAssembly component rather than serving a static file. Fetch it over HTTPS (the -k flag accepts the per instance self signed certificate). Note that the path you request is echoed back in the response, which a static file server cannot do:

curl -ks https://127.0.0.1/hello-from-cloudimg | sed -e 's|</tr>|\n|g' -e 's/<[^>]*>//g' | grep -iE 'produced by a WebAssembly|Request path|wasm-exec-check'
curl -ksI https://127.0.0.1/ | grep -i x-cloudimg-spin

The response reports that it was produced by a WebAssembly component executed by the Spin runtime, echoes the request path /hello-from-cloudimg, and shows the WASM execution check (6 x 7) evaluating to 42 on the server, plus an x-cloudimg-spin: executed-42 response header.

the curl command fetches the demo page over HTTPS and the output shows the response was produced by a WebAssembly component executed by the Spin runtime, with the runtime reported as Fermyon Spin, the request path echoed live as slash hello-from-cloudimg, the WASM execution check of 6 times 7 evaluating to 42, and an x-cloudimg-spin executed-42 response header, proving the Spin runtime executed the WebAssembly component

Open https://<vm-public-ip>/ in a browser (your browser will warn about the self signed certificate until you front the instance with a trusted certificate in Step 8). The demo landing page confirms the WebAssembly runtime and echoes your request path:

a web browser showing the demo Spin landing page served over HTTPS, headed Spin on cloudimg, confirming the response was produced by a WebAssembly component executed by the Spin runtime, with a table showing the runtime Fermyon Spin, the request path echoed live as slash hello-from-cloudimg, the full request URL, and the WASM execution check of 6 times 7 equal to 42, plus quickstart instructions for deploying your own WebAssembly component

Step 6: HTTP to HTTPS redirect and the loopback bound runtime

Plain HTTP on port 80 permanently redirects (301) to HTTPS. Confirm the redirect, then confirm the Spin application port 3000 is bound to loopback only, so the runtime itself is not reachable from the network (only the nginx TLS proxy is):

curl -ksI http://127.0.0.1/ | grep -iE 'HTTP/|location'
sudo ss -tln | grep ':3000 '

You should see HTTP/1.1 301 Moved Permanently with a Location: header pointing at the HTTPS URL, and the Spin runtime listening only on 127.0.0.1:3000.

the curl command against port 80 returns HTTP 301 Moved Permanently with a Location header pointing to the HTTPS URL, and the ss command confirms the Spin application is listening only on 127.0.0.1 port 3000, so the runtime is bound to loopback and is not reachable from the network, making the image secure by default

Step 7: Deploy your own WebAssembly component

The application directory is /opt/spin-app. Build a WebAssembly component (Spin supports Rust, JavaScript, TypeScript, Python, Go and more), copy the compiled .wasm into the application directory, point the Spin manifest at it, then restart the service:

# copy your compiled component into the application directory
sudo cp ./my-component.wasm /opt/spin-app/index.wasm

# (optional) edit /opt/spin-app/spin.toml to change the route or add components

# apply the change
sudo systemctl restart spin

The manifest maps an HTTP route to a component with the [[trigger.http]] and [component.*] sections, so you can host multiple components on one runtime and route between them by path.

Step 8: Front your instance with a trusted certificate

The image ships with a per instance self signed certificate so it answers over HTTPS immediately. For production, point a domain's DNS A record at this instance and replace the self signed certificate that nginx serves with a trusted one (for example from your own certificate authority or an ACME client such as Certbot). The nginx site is at /etc/nginx/sites-available/cloudimg-spin; update the ssl_certificate and ssl_certificate_key paths to your trusted certificate and reload nginx:

sudo nano /etc/nginx/sites-available/cloudimg-spin
sudo nginx -t && sudo systemctl reload nginx

Step 9: Review the manifest and info note

The Spin application manifest is one readable file, and the first boot note records this instance's endpoint details:

sudo cat /opt/spin-app/spin.toml
sudo cat /root/spin-info.txt

The manifest shows the [[trigger.http]] route mapped to the demo component with source = "index.wasm". The info note records the HTTPS and HTTP URLs for this instance, confirms that the Spin runtime is bound to loopback only behind nginx, and confirms that Spin has no login and no shared credential.

Step 10: Security recommendations

  • Restrict the management port. Allow inbound TCP 22 only from your management network, never 0.0.0.0/0.
  • Front your users with HTTPS only. Expose TCP 443 to your users and TCP 80 only so the redirect works. Switch to a trusted certificate for your own domain (Step 8) so browsers do not warn.
  • Keep the runtime on loopback. This image binds the Spin application to 127.0.0.1:3000 so only the nginx TLS proxy is network reachable. Leave it that way; terminate TLS at nginx rather than exposing the runtime port directly.
  • Keep the OS patched. The image ships fully patched with unattended-upgrades enabled, so security updates continue automatically. Reboot after kernel updates.
  • Run your components as least privilege. spin.service already runs as the unprivileged spin user with systemd sandboxing, and WebAssembly components run inside the Spin sandbox with no ambient host access.

Step 11: Support and Licensing

Spin is open source software distributed under the Apache License 2.0. This cloudimg image bundles Spin 4.0.2 on Ubuntu 24.04 LTS with nginx TLS termination, fully patched and hardened. cloudimg provides the packaging, configuration, security hardening and support; it does not modify the upstream Spin licence.

Deploy on Azure

Launch Spin on Ubuntu 24.04 from the cloudimg Azure Marketplace listing and follow this guide to deploy your WebAssembly component in minutes.

Need Help?

cloudimg provides 24/7 technical support for this Spin image. Contact support@cloudimg.co.uk for help with building WebAssembly components in Rust, JavaScript, Python and Go, the Spin manifest and HTTP routing, TLS and certificate options, and Spin version upgrades.

All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.