Skip to content

Static Web Hosting

Lightweight Static Hosting

Static websites are served with nginx on Alpine Linux inside Incus containers. Each site gets its own container with a persistent storage volume mounted at /www. Deployment is automated via rsync over SSH using a dedicated CI key.

Alpine Linux Reference

This guide uses Alpine Linux. See the Alpine Linux Guide for reference on apk package management and OpenRC service management.

Two containers follow this pattern:

  • www - Main website (this site, built with Zensical)
  • retro - Retro-style version of the website

Install

Infrastructure Configuration

Each container gets a storage volume for the web content, mounted at /www.

OpenTofu Configuration (www.tf)
resource "incus_storage_volume" "www" {
  name = "www"
  pool = incus_storage_pool.default.name
}

resource "incus_instance" "www" {
  name  = "www"
  image = "images:alpine/3.21"

  device {
    name = "www"
    type = "disk"
    properties = {
      path   = "/www"
      source = incus_storage_volume.www.name
      pool   = incus_storage_pool.default.name
    }
  }
}
OpenTofu Configuration (retro.tf)
resource "incus_storage_volume" "retro" {
  name = "retro"
  pool = incus_storage_pool.default.name
}

resource "incus_instance" "retro" {
  name  = "retro"
  image = "images:alpine/3.23"

  device {
    name = "www"
    type = "disk"
    properties = {
      path   = "/www"
      source = incus_storage_volume.retro.name
      pool   = incus_storage_pool.default.name
    }
  }
}
Apply OpenTofu configuration
tofu apply

Container Setup

Run the following steps inside the container:

Enter Incus container
incus shell www

Install packages

apk update && apk add nginx openssh rsync

Create web user and set directory ownership

adduser -D -g 'www' www
chown -R www:www /www

Configure nginx virtual host

The upstream /etc/nginx/nginx.conf is kept as-is. Create a virtual host file pointing to /www:

/etc/nginx/http.d/default.conf
server {
    listen 80;
    listen [::]:80;
    root /www;
    index index.html index.htm;
    server_name localhost;
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /var/lib/nginx/html;
    }
}

Enable and start services

rc-update add nginx default
rc-update add sshd default
rc-service nginx start
rc-service sshd start

Add CI deployment key

passwd -u www
mkdir -p /home/www/.ssh
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILSKbMMF26AiIS4SB26mKtOhrV3kHsSff/V4Dbq/Zi0/ laminar@laminar" > /home/www/.ssh/authorized_keys
chown -R www:www /home/www/.ssh
chmod 700 /home/www/.ssh
chmod 600 /home/www/.ssh/authorized_keys

Deployment

Content is deployed from the Laminar CI server via rsync over SSH as the www user:

Deploy to container (example)
rsync -avz --delete site/ www@www:/www/

The laminar@laminar SSH key is authorized in /home/www/.ssh/authorized_keys on both containers, allowing automated deployment without a password.


Related Documentation: