Hosting a static site in a container

created: mercredi, déc. 10, 2025

La plupart de nos sites hébergés sont rendus statiquement, construits avec des outils comme Hugo, Zola ou Jekyll. En général, tous ces générateurs de sites prennent une entrée simplifiée (généralement du Markdown) et génèrent du HTML bien défini en sortie. Cela laisse la question : comment puis-je héberger un tel site ?

Il existe des fournisseurs spécialisés offrant un hébergement pour les sites statiques, mais comme vous le savez, nous avons pris une autre voie en construisant notre infrastructure principale. Pour notre cloud, le dénominateur commun pour le déploiement est un conteneur. Et avec cela vient la question : comment passer d’une build de site statique à un conteneur qui héberge le site web ?

Commençons donc par générer une page très simple « hello world » avec Hugo.

hugo new site hello
cd hello
git init
git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke
echo "theme = 'ananke'" >> hugo.toml

Pour vérifier la build, nous pouvons démarrer le serveur localement :

hugo serve

Puis notre propre page « Hello World » apparaît sur http://localhost:1313

Jusqu’ici tout va bien. Maintenant, envisageons de mettre cela dans un conteneur.

Two Approaches to Containerization

Techniquement, nous pouvons suivre deux voies différentes ici :

  1. Mettre tout le source dans un conteneur et exécuter le processus de build à l’intérieur
  2. Builder localement et ne copier que les sorties dans le conteneur

Pour rendre notre exemple plus reproductible et moins dépendant des environnements locaux, nous choisissons l’option 1 pour ce scénario. Cela rend aussi les pipelines CI/CD plus propres puisque l’environnement de build est entièrement défini dans le Dockerfile.

Building the Container Image

Une image de conteneur commence toujours par une image de base. Pour cela, nous utilisons Alpine Linux car elle est petite (environ 5 Mo) et fournit assez d’outils pour notre projet.

Nous allons utiliser un build multi-étapes, une technique supportée par les outils modernes de build de conteneurs qui permet d’utiliser une image pour la construction et une autre pour l’exécution. Cela garde notre image finale petite en excluant les outils de build non nécessaires à l’exécution.

Étape 1 : Build

FROM alpine AS build
RUN apk add --no-cache hugo
WORKDIR /src
ADD . .
RUN hugo --minify

Dans cette étape, nous :

Le site construit se retrouve dans /src/public/.

Étape 2 : Exécution

FROM alpine AS runner
RUN apk add --no-cache lighttpd
COPY --from=build /src/public /var/www/localhost/htdocs
EXPOSE 80
CMD ["lighttpd", "-D", "-f", "/etc/lighttpd/lighttpd.conf"]

Dans cette étape, nous :

The Complete Dockerfile

Voici le Dockerfile complet combinant les deux étapes :

# Stage 1: Build the static site
FROM alpine AS build
RUN apk add --no-cache hugo
WORKDIR /src
ADD . .
RUN hugo --minify

# Stage 2: Serve with lighttpd
FROM alpine AS runner
RUN apk add --no-cache lighttpd
COPY --from=build /src/public /var/www/localhost/htdocs
EXPOSE 80
CMD ["lighttpd", "-D", "-f", "/etc/lighttpd/lighttpd.conf"]

Enregistrez ceci sous le nom Dockerfile à la racine de votre projet Hugo.

Building and Running Locally

Vous pouvez utiliser n’importe quel outil compatible OCI comme Podman ou Docker. Les exemples ci-dessous utilisent docker, mais podman fonctionne comme un remplacement direct.

Pour construire l’image :

docker build -t my-website .

Pour la lancer localement :

docker run -p 8080:80 --rm my-website

Votre site est maintenant accessible sur http://localhost:8080

L’option -p 8080:80 fait correspondre le port 8080 de votre machine au port 80 dans le conteneur. L’option --rm supprime automatiquement le conteneur à l’arrêt.

Why This Approach Works Well

Cette configuration présente plusieurs avantages :

  1. Taille d’image réduite : L’image finale contient uniquement Alpine (~5 Mo) + lighttpd (~1 Mo) + vos fichiers HTML. Pas de Node.js, pas de Ruby, pas d’outils de build qui alourdissent l’image de production.

  2. Builds reproductibles : La même version exacte de Hugo tourne en CI comme localement, éliminant les problèmes de type « ça marche chez moi ».

  3. Démarrage rapide : lighttpd démarre en millisecondes, parfait pour les déploiements scale-to-zero sur DTZ.

  4. Sécurité : Le conteneur de production a une surface d’attaque minimale – juste un serveur de fichiers statiques sans runtime dynamique.

Architecture Considerations

Si vous construisez votre image sur un Mac Apple Silicon (ARM64) ou une autre architecture non standard, rappelez-vous que les serveurs tournent typiquement sur AMD64 (x86_64). Pour garantir que votre conteneur fonctionne correctement sur DTZ (et la plupart des autres fournisseurs cloud), vous devriez spécifier explicitement la plateforme cible lors de la build.

Changez votre commande de build pour :

docker build --platform linux/amd64 -t my-website .

Cela indique à Docker de cross-compiler l’image pour les serveurs Linux standard, assurant la compatibilité quelle que soit la machine de build.

Deploying to DownToZero

Une fois votre image construite, vous pouvez la pousser vers un registre de conteneurs et la déployer sur DTZ. Si vous utilisez notre registre de conteneurs :

# Tag pour le registre DTZ
docker tag my-website YOUR_CONTEXT_ID.cr.dtz.dev/my-website:latest

# Connexion et push
docker login YOUR_CONTEXT_ID.cr.dtz.dev -u apikey
docker push YOUR_CONTEXT_ID.cr.dtz.dev/my-website:latest

Ensuite, créez un service conteneur dans le tableau de bord DTZ pointant vers votre image. Le service gérera automatiquement les certificats TLS, la scalabilité et le routage.

Pour des déploiements automatisés à chaque commit, consultez notre GitHub Action pour des déploiements transparents.

Adapting for Other Static Site Generators

Le même schéma fonctionne pour d’autres générateurs. Voici les changements clés :

Pour Zola :

FROM alpine AS build
RUN apk add --no-cache zola
WORKDIR /src
ADD . .
RUN zola build

Pour Jekyll :

FROM ruby:alpine AS build
RUN apk add --no-cache build-base
RUN gem install bundler jekyll
WORKDIR /src
ADD . .
RUN bundle install
RUN bundle exec jekyll build

L’étape runtime reste identique – il suffit de copier depuis /src/public (Zola) ou /src/_site (Jekyll) vers la racine des documents de lighttpd.

Wrapping Up

Conteneuriser des sites statiques est simple une fois que vous comprenez le modèle : construire dans une étape, servir dans une autre. Le résultat est un conteneur minuscule, rapide et sécurisé, parfait pour les déploiements cloud modernes.

Cette approche correspond bien à notre philosophie chez DTZ – usage minimal des ressources, démarrages à froid ultra rapides, et une infrastructure qui scale jusqu’à zéro quand elle n’est pas utilisée. Un site statique dans un conteneur d’environ 10 Mo qui démarre instantanément est sans doute la forme la plus efficace d’hébergement web.