Hugo sur KVM : installer une VM Ubuntu pour site statique

⚡ En bref
Migration progressive de Grav CMS vers Hugo — un générateur de site statique. L’objectif est d’isoler Hugo dans une VM KVM dédiée sur le NUC8i3BEH, avec nginx sur le host en proxy inverse. Le site statique généré est servi par nginx dans la VM, sans PHP, sans base de données, sans surface d’attaque applicative.
- 🖥️ Host : NUC8i3BEH Ubuntu 24.04 — nginx proxy + KVM
- 🗄️ Disque VM : NVMe externe Samsung X5 (exFAT → image ext4 loop)
- 🌐 Accès : hugo-test.arleo.eu
- 🎨 Thème : LoveIt
🧠 Pourquoi
Grav CMS est excellent mais repose sur PHP — une surface d’attaque non négligeable. Hugo génère du HTML statique pur : zéro PHP, zéro base de données, zéro vulnérabilité applicative. Les performances sont aussi radicalement meilleures — le HTML est servi directement par nginx sans traitement dynamique.
L’isolation dans une VM KVM offre un avantage supplémentaire : si la VM est compromise, le host reste intact. Le proxy nginx du host ne fait que transmettre les requêtes vers la VM sans exposer d’autres services.
Architecture finale
Internet → Cloudflare → nginx (NUC host) → VM KVM (192.168.122.69)
↓
Hugo + nginx
(HTML statique)🔧 Ce qui a été fait
1. Installation KVM sur le host Ubuntu 24.04
sudo apt install -y \
qemu-kvm libvirt-daemon-system libvirt-clients \
virtinst bridge-utils cloud-image-utils \
cockpit-machines iptables
sudo usermod -aG libvirt jm
sudo usermod -aG kvm jm
sudo systemctl enable --now libvirtd
# Activer le réseau NAT par défaut
sudo virsh net-start default
sudo virsh net-autostart default2. Problème exFAT — image ext4 loop sur le NVMe
Le disque NVMe externe Samsung X5 est formaté en exFAT — ce système de fichiers ne supporte pas les permissions Unix (chown). QEMU ne peut donc pas lire les images disque stockées dessus directement.
Solution : créer une image ext4 dans un fichier sur le volume exFAT :
# Créer une image ext4 de 25 Go sur le NVMe exFAT
sudo dd if=/dev/zero of=/mnt/X5/libvirt-pool.img bs=1G count=25 status=progress
sudo mkfs.ext4 /mnt/X5/libvirt-pool.img
# Monter l'image
sudo mkdir -p /mnt/libvirt-kvm
sudo mount -o loop /mnt/X5/libvirt-pool.img /mnt/libvirt-kvm
# Persistance au reboot
echo "/mnt/X5/libvirt-pool.img /mnt/libvirt-kvm ext4 loop,nofail 0 0" \
| sudo tee -a /etc/fstab
# Permissions pour QEMU
sudo chown libvirt-qemu:kvm /mnt/libvirt-kvm
sudo chmod 755 /mnt/libvirt-kvm
# Pool libvirt dédié
sudo virsh pool-define-as kvm-pool dir --target /mnt/libvirt-kvm
sudo virsh pool-autostart kvm-pool
sudo virsh pool-start kvm-pool3. Création de la VM avec cloud-init
# Télécharger l'image Ubuntu 24.04 cloud
wget -O /mnt/libvirt-kvm/ubuntu-24.04-server.img \
https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
# Créer le disque VM (20 Go, backing file)
qemu-img create \
-F qcow2 -b /mnt/libvirt-kvm/ubuntu-24.04-server.img \
-f qcow2 /mnt/libvirt-kvm/hugo-vm.qcow2 20G
sudo chown libvirt-qemu:kvm /mnt/libvirt-kvm/hugo-vm.qcow2
# Cloud-init avec clé SSH
SSH_KEY=$(cat /home/jm/.ssh/authorized_keys | head -1)
cat > /tmp/user-data << EOF
#cloud-config
hostname: hugo-vm
users:
- name: jm
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
lock_passwd: false
ssh_authorized_keys:
- $SSH_KEY
packages:
- nginx
- git
- curl
runcmd:
- systemctl enable nginx
- systemctl start nginx
EOF
cloud-localds /mnt/libvirt-kvm/hugo-vm-cloud-init.img \
/tmp/user-data /tmp/meta-data4. Démarrage de la VM
sudo virt-install \
--name hugo-vm \
--ram 1024 \
--vcpus 1 \
--disk path=/mnt/libvirt-kvm/hugo-vm.qcow2,format=qcow2,bus=virtio \
--disk path=/mnt/libvirt-kvm/hugo-vm-cloud-init.img,device=cdrom \
--os-variant ubuntu24.04 \
--network network=default,model=virtio \
--graphics none \
--import \
--noautoconsole \
--autostart
# Récupérer l'IP
sudo virsh net-dhcp-leases default
# → 192.168.122.695. Installation de Hugo Extended dans la VM
ssh jm@192.168.122.69
HUGO_VERSION="0.147.0"
wget -q -O /tmp/hugo.deb \
"https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb"
sudo dpkg -i /tmp/hugo.deb
hugo version
# hugo v0.147.0+extended linux/amd646. Initialisation du site Hugo avec LoveIt
mkdir -p ~/hugo-site && cd ~/hugo-site
hugo new site . --force
git init
git submodule add https://github.com/dillonzq/LoveIt.git themes/LoveIt7. Configuration nginx dans la VM
server {
listen 80 default_server;
server_name _;
root /var/www/hugo;
index index.html;
location = / {
return 302 /fr/;
}
location / {
try_files $uri $uri/ =404;
}
}8. Script de déploiement
cat > ~/deploy.sh << 'EOF'
#!/bin/bash
cd ~/hugo-site
hugo --minify
sudo rsync -a --delete public/ /var/www/hugo/
sudo chown -R www-data:www-data /var/www/hugo/
echo "Déployé — $(date)"
EOF
chmod +x ~/deploy.sh9. Proxy nginx sur le host
server {
listen 443 ssl http2;
server_name hugo-test.arleo.eu;
include snippets/ssl.conf;
location / {
proxy_pass http://192.168.122.69:80;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Points de friction rencontrés
| Problème | Cause | Solution |
|---|---|---|
| Réseau libvirt inactif | iptables absent | apt install iptables |
| Permission denied sur qcow2 | disque exFAT → pas de chown | Image ext4 loop dans le fichier exFAT |
| Cloud-init ne crée pas le user | Clé SSH absente du host | virt-customize pour injecter directement |
| SSH refusé (publickey) | Clé privée absente sur le host | Générer hugo_vm + injecter via virt-customize |
| nginx 404 | Permissions home jm (750) | Déplacer public/ vers /var/www/hugo |
🏁 Conclusion
La VM Hugo est opérationnelle sur hugo-test.arleo.eu. Le site statique est servi par nginx dans la VM, proxifié par le host. La prochaine étape est la migration du contenu Grav vers Hugo et le développement d’un MCP Hugo pour permettre la gestion des pages directement depuis Claude.ai.
Pour aller plus loin :
- 💡 Développer un MCP Hugo (FastAPI + OAuth) sur le modèle de grav-mcp-server
- 💡 Automatiser le rebuild Hugo via webhook Git
- 💡 Configurer la sauvegarde de la VM vers le NAS QNAP