Introduction – Pourquoi industrialiser un wiki auto-hébergé ?
Depuis plusieurs années, j’utilisais Microsoft OneNote pour centraliser ma documentation technique personnelle : procédures d’installation, architecture de mon infra HomeLab, snippets de scripts, etc. C’était pratique… jusqu’à ce que je perde l’accès à certaines notes critiques lors d’une synchronisation ratée entre appareils.
Je travaille à la fois sur un poste HP Windows (infra, scripting, ops) et sur un MacBook Pro (dev front, IA, prototypage). Passer d’un environnement à l’autre rendait l’usage de OneNote de plus en plus instable, notamment en multi-appareil.
En parallèle, j’utilisais GitLab pour le versioning de mes projets. Bien qu’il permette d’ajouter des fichiers Markdown, je constatais vite ses limites : peu ergonomique pour des utilisateurs non développeurs, gestion des images peu fluide, recherche approximative.
C’est alors que le besoin d’un wiki auto-hébergé, accessible, structuré et intégré à mon écosystème K8s s’est imposé. Mon objectif : documenter mon infrastructure et mes outils de manière durable, versionnée, avec authentification centralisée via mon serveur LDAP local, et persistance fiable sur une base MariaDB.
Mise en œuvre – Comment j’ai déployé BookStack dans Kubernetes
Comparatif rapide de solutions
| Nom | Stack | DB supportée | Auth intégrée | Avantages | Limitations |
|---|---|---|---|---|---|
| BookStack | PHP + Laravel | MySQL/MariaDB | LDAP, SAML | Interface claire, très stable | DB unique, pas de Markdown natif |
| Wiki.js | Node.js | PostgreSQL/MySQL | OIDC, LDAP | Moderne, extensible | Complexité accrue à maintenir |
| DokuWiki | PHP (flat files) | Aucune | Basique | Ultra léger, sans DB | Peu adapté aux clusters modernes |
→ Choix : BookStack pour sa maturité, sa clarté d’interface et son intégration simple dans Kubernetes.
Prérequis DevOps (non couverts ici)
Avant de déployer le chart Helm présenté ci-dessous, certaines étapes d’infrastructure et de CI/CD sont nécessaires :
- 📁 Créer un projet Git : personnellement, j’utilise GitLab auto-hébergé pour versionner mes charts Helm et mes manifests Kubernetes.
- 🧑💻 Cloner et éditer le projet localement sur mes postes (Windows HP et MacBook Pro), avec VS Code + Git CLI.
- 🤖 Configurer Jenkins pour déclencher automatiquement les déploiements :
- pipeline Docker pour builder une image si besoin
- publication vers Nexus (registry privé Docker + Helm)
- script Helm (helm upgrade --install) exécuté depuis Jenkins sur mon cluster K8s
- 🔐 Les secrets "bookstack-db-secret" sont injectés manuellement au cluster ou via kubectl.
👉 Ces étapes sont décrites dans un README.md à part, avec des scripts d’automatisation et des hooks de validation Helm.
Stack technique utilisée
- Kubernetes 1.34 (auto-hébergé)
- Helm Chart customisé
- BookStack via linuxserver/bookstack
- Base MariaDB externe (VM + NAS)
- Authentification LDAP (OpenLDAP local)
- Ingress avec Gateway API (NGINX Gateway Controller)
- cert-manager pour HTTPS
Préparation de la base MariaDB externe
Avant toute chose, il est nécessaire de créer la base de données, l'utilisateur et les droits associés dans MariaDB. Sur la VM ou le serveur où MariaDB est installé, exécuter les commandes suivantes :
mysql -u root --execute="CREATE DATABASE bookstack;"
mysql -u root --execute="CREATE USER 'bookstack'@'%' IDENTIFIED WITH mysql_native_password BY 'superSecure123';"
mysql -u root --execute="GRANT ALL ON bookstack.* TO 'bookstack'@'%'; FLUSH PRIVILEGES;"
Adapter superSecure123 à la valeur que vous utiliserez dans le secret Kubernetes correspondant (DBPASS).
Déploiement Helm – values.yaml
replicaCount: 1
image:
repository: linuxserver/bookstack
tag: latest
pullPolicy: Always
service:
type: ClusterIP
port: 80
targetPort: 80
protocol: TCP
envFromSecret:
name: bookstack-db-secret
ingress:
enabled: true
hostname: bookstack
gateway:
name: nginx-gateway
namespace: gateway
serviceAccount:
create: false
name: "default"
imagePullSecrets:
- name: registry-secret
resources:
limits:
cpu: 0.5
memory: 0.5Gi
requests:
cpu: 0.1
memory: 0.25Gi
autoscaling:
enabled: true
minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 80
persistence:
enabled: true
accessModes:
- ReadWriteOnce
size: 5Gi
storageClassName: main-provisioner
Secrets requis (bookstack-db-secret)
Variables d’environnement attendues :
kubectl create secret generic bookstack-db-secret \
--from-literal=DB_HOST=mariadb.svc.cluster.local \
--from-literal=DB_PORT=3306 \
--from-literal=DB_USER=bookstack \
--from-literal=DB_PASS=superSecure123 \
--from-literal=DB_DATABASE=bookstackdb \
--from-literal=APP_URL=https://wiki.egilberny.com \
--from-literal=APP_KEY=base64:6ZQaXoC8g2V9sRMaZ9rYtpKakW3uWVwv6NND+Lgfd0k= \
--from-literal=AUTH_METHOD=ldap \
--from-literal=LDAP_SERVER=ldap://ldap.local \
--from-literal=LDAP_BASE_DN=dc=example,dc=org \
--from-literal=LDAP_DN=cn=admin,dc=example,dc=org \
--from-literal=LDAP_PASS=adminPass123
Fichiers Helm – templates du chart
Voici les principaux fichiers Helm nécessaires pour déployer BookStack dans Kubernetes :
templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "bookstack.fullname" . }}
labels:
{{- include "bookstack.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "bookstack.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "bookstack.selectorLabels" . | nindent 8 }}
spec:
serviceAccountName: {{ include "bookstack.serviceAccountName" . }}
containers:
- name: bookstack
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
envFrom:
- secretRef:
name: {{ .Values.envFromSecret.name }}
ports:
- containerPort: {{ .Values.service.targetPort }}
name: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: bookstack-data
mountPath: /config
volumes:
- name: bookstack-data
persistentVolumeClaim:
claimName: {{ include "bookstack.fullname" . }}-pvc
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ include "bookstack.fullname" . }}
labels:
{{- include "bookstack.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}
protocol: {{ .Values.service.protocol }}
selector:
{{- include "bookstack.selectorLabels" . | nindent 4 }}
templates/httproute.yaml
{{- if .Values.ingress.enabled }}
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: {{ include "bookstack.fullname" . }}-route
namespace: {{ .Release.Namespace }}
labels:
{{- include "bookstack.labels" . | nindent 4 }}
spec:
parentRefs:
- name: {{ .Values.ingress.gateway.name }}
namespace: {{ .Values.ingress.gateway.namespace }}
hostnames:
- "{{ .Values.ingress.hostname }}.svc.{{ .Release.Namespace }}.k8s.lab"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: {{ include "bookstack.fullname" . }}
port: {{ .Values.service.port }}
{{- end }}
templates/pvc.yaml
{{- if .Values.persistence.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "bookstack.fullname" . }}-pvc
namespace: {{ .Release.Namespace }}
labels:
{{- include "bookstack.labels" . | nindent 4 }}
spec:
accessModes:
{{- toYaml .Values.persistence.accessModes | nindent 4 }}
resources:
requests:
storage: {{ .Values.persistence.size }}
storageClassName: {{ .Values.persistence.storageClassName }}
{{- end }}
templates/_helpers.tpl
{{- define "bookstack.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- define "bookstack.selectorLabels" -}}
app.kubernetes.io/name: {{ include "bookstack.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- define "bookstack.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ include "bookstack.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "bookstack.name" -}}
{{- .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "bookstack.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{ include "bookstack.fullname" . }}
{{- else }}
{{- .Values.serviceAccount.name }}
{{- end }}
{{- end }}
Composants créés
Le chart Helm que j’ai construit génère plusieurs ressources Kubernetes essentielles à un déploiement robuste et maintenable. Voici les principaux composants, expliqués avec un regard d’ingénierie.
Deployment
C’est le cœur de la stratégie de déploiement. Le Deployment assure que le bon nombre de pods BookStack sont toujours en ligne. Il gère aussi les mises à jour, les redémarrages et permet de spécifier des ressources minimales/optimales (CPU, mémoire). Ce manifeste référence le container Docker principal, les volumes nécessaires et injecte toutes les variables d’environnement via un secret sécurisé.
Service
Le Service expose BookStack à l’intérieur du cluster via un port HTTP standard. Il agit comme un proxy stable vers les pods BookStack, et constitue la cible logique pour les composants de routage comme les Ingress ou les HTTPRoute. Ce découplage garantit que l'adresse du pod ne change pas entre redémarrages.
PersistentVolumeClaim (PVC)
Le PVC monte un volume durable sur le chemin /config. Ce volume conserve les fichiers de configuration, les médias uploadés, les logs, et tout autre état persistant. En cas de redéploiement ou de crash du pod, les données ne sont pas perdues.
HTTPRoute (Gateway API)
Plutôt que d’utiliser un Ingress traditionnel, j’ai opté pour un HTTPRoute moderne. Ce composant, défini par la Gateway API, permet un routage HTTP/S fin, indépendant du contrôleur sous-jacent. Il gère notamment :
- le nom DNS de l’application (en local et en prod)
- la sécurité TLS via cert-manager
- l’aiguillage des requêtes vers le Service Kubernetes correspondant
En résumé, ces composants assurent l’autonomie, la résilience, la modularité et la conformité réseau d’un déploiement BookStack moderne dans Kubernetes.
- Deployment BookStack
- Service exposé en ClusterIP
- PersistentVolumeClaim pour /config
- HTTPRoute pour Gateway API (ingress moderne)
Schéma de l’architecture
graph TD User[Utilisateur] --> Ingress[NGINX Ingress] Ingress --> BookStack[Pod BookStack] BookStack --> PVC[(Volume Persistant)] BookStack --> DB[(MariaDB sur VM)] BookStack --> LDAP[(Serveur LDAP local)]
Test local
kubectl port-forward svc/bookstack 8080:80
xdg-open http://localhost:8080
Et après ?
Ce qui fonctionne bien
- Auth LDAP immédiate, interface fluide
- Sauvegarde via dump MariaDB + PVC
- Accès performant malgré DB externe
Leçons & améliorations
- Monitoring à prévoir (Prometheus, Velero)
- Amélioration continue avec alerting, backups planifiés, HA possible
Références
- https://www.bookstackapp.com/docs/
- https://hub.docker.com/r/linuxserver/bookstack
- https://kubernetes.io/docs/concepts/configuration/secret/
- https://datatracker.ietf.org/doc/html/rfc4514
Prochaines étapes : intégration Prometheus, sauvegarde Velero, notifications via Alertmanager, intégration dans n8n.