WebAssembly
    Sub-ms Cold Start

    Deploy Fermyon Spin on a VPS

    Serverless WebAssembly runtime with single-digit-ms cold starts, ~30 MB idle footprint, and OCI artifact deploys.

    At a Glance

    ProjectFermyon Spin 3.x
    LicenseApache 2.0
    Recommended PlanRamNode Cloud VPS 2 vCPU / 2-4 GB
    OSUbuntu 22.04+ / Debian
    LanguagesRust, TypeScript, Python, Go and more
    1

    Install the Spin Runtime

    Update + install
    sudo apt update && sudo apt upgrade -y
    sudo apt install -y curl ca-certificates git build-essential
    
    cd /tmp
    curl -fsSL https://developer.fermyon.com/downloads/install.sh | bash
    sudo install -m 0755 ./spin /usr/local/bin/spin
    spin --version
    2

    Dedicated Service User

    Create system user + dirs
    sudo useradd --system --create-home --home-dir /var/lib/spin --shell /usr/sbin/nologin spin
    sudo mkdir -p /var/lib/spin/apps /var/log/spin
    sudo chown -R spin:spin /var/lib/spin /var/log/spin
    3

    Build a Sample Component

    Rust toolchain + scaffold
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
    source "$HOME/.cargo/env"
    rustup target add wasm32-wasip1
    
    cd ~
    spin new -t http-rust hello-spin --accept-defaults
    cd hello-spin
    spin build
    spin up --listen 127.0.0.1:3000 &
    curl http://127.0.0.1:3000
    kill %1
    4

    Package as an OCI Artifact

    Push + pull
    spin registry login ghcr.io -u YOUR_USERNAME
    # paste a PAT with write:packages
    spin registry push ghcr.io/your-username/hello-spin:0.1.0
    
    # On the production host, as the spin user:
    sudo -u spin spin registry pull ghcr.io/your-username/hello-spin:0.1.0
    5

    systemd Template Unit

    /etc/systemd/system/spin@.service
    [Unit]
    Description=Spin application: %i
    After=network-online.target
    Wants=network-online.target
    
    [Service]
    Type=simple
    User=spin
    Group=spin
    WorkingDirectory=/var/lib/spin/apps/%i
    ExecStart=/usr/local/bin/spin up --listen 127.0.0.1:%I --from /var/lib/spin/apps/%i/spin.toml
    Restart=on-failure
    RestartSec=5
    StandardOutput=append:/var/log/spin/%i.log
    StandardError=append:/var/log/spin/%i.err
    
    NoNewPrivileges=true
    ProtectSystem=strict
    ProtectHome=true
    PrivateTmp=true
    PrivateDevices=true
    ProtectKernelTunables=true
    ProtectKernelModules=true
    ProtectControlGroups=true
    ReadWritePaths=/var/lib/spin /var/log/spin
    RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
    RestrictNamespaces=true
    LockPersonality=true
    MemoryDenyWriteExecute=false
    
    [Install]
    WantedBy=multi-user.target

    MemoryDenyWriteExecute=false is required: the WebAssembly runtime needs JIT pages.

    Enable
    sudo systemctl daemon-reload
    sudo systemctl enable --now spin@hello-spin.service
    curl http://127.0.0.1:3000
    6

    Caddy + TLS

    Install Caddy
    sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
    sudo apt update && sudo apt install -y caddy
    /etc/caddy/Caddyfile
    spin.example.com {
        encode zstd gzip
        reverse_proxy 127.0.0.1:3000 {
            header_up X-Real-IP {remote_host}
            header_up X-Forwarded-For {remote_host}
            header_up X-Forwarded-Proto {scheme}
        }
        header {
            Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
            X-Content-Type-Options "nosniff"
            X-Frame-Options "DENY"
            -Server
        }
    }
    7

    Storage + Outbound Allowlist

    spin.toml — declare allowed hosts
    [component.my-app]
    source = "target/wasm32-wasip1/release/my_app.wasm"
    allowed_outbound_hosts = [
        "https://api.example.com",
        "redis://localhost:6379",
        "postgres://db.internal:5432"
    ]
    runtime-config.toml — local KV + SQLite
    [key_value_store.default]
    type = "spin"
    path = "/var/lib/spin/apps/hello-spin/data/kv.db"
    
    [sqlite_database.default]
    type = "spin"
    path = "/var/lib/spin/apps/hello-spin/data/db.sqlite"
    8

    Metrics + Backup

    Prometheus scrape
    scrape_configs:
      - job_name: spin
        static_configs:
          - targets: ['127.0.0.1:3000']
            labels:
              app: hello-spin
    Daily backup
    sudo tee /etc/cron.daily/spin-backup > /dev/null <<'EOF'
    #!/usr/bin/env bash
    set -euo pipefail
    DATE=$(date +%Y%m%d)
    rsync -a --delete /var/lib/spin/ backup@backup.example.com:/srv/backups/spin-${DATE}/
    EOF
    sudo chmod +x /etc/cron.daily/spin-backup