New Page
BookStack on Kubernetes (in-cluster MySQL + external HAProxy)
Image used: lscr.io/linuxserver/bookstack:latest (community-maintained, actively updated, includes /status health endpoint that BookStack's own HA docs recommend probing).
1. Create the database in your existing MySQL
BookStack manages its own schema, so it just needs an empty database + a user with full rights on it. Run on your existing MySQL:
CREATE DATABASE bookstack CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'bookstack'@'%' IDENTIFIED BY 'CHANGE_ME_strong_password';
GRANT ALL PRIVILEGES ON bookstack.* TO 'bookstack'@'%';
FLUSH PRIVILEGES;
Use the same password in 01-secret.yaml (DB_PASSWORD).
2. Edit before applying
| File | What to change |
|---|---|
01-secret.yaml |
DB_PASSWORD to match what you set in MySQL |
02-configmap.yaml |
APP_URL (must match the host users/HAProxy hit), DB_HOST (your MySQL Service DNS name) |
03-pvc.yaml |
storageClassName — run kubectl get storageclass to see what's available |
06-ingress.yaml |
host, and ingressClassName if you're not on ingress-nginx |
DB_HOST resolution inside the cluster:
- Same namespace as MySQL's Service → just the Service name, e.g.
mysql - Different namespace →
mysql.<that-namespace>.svc.cluster.local
3. Apply
kubectl apply -f 00-namespace.yaml
kubectl apply -f 01-secret.yaml
kubectl apply -f 02-configmap.yaml
kubectl apply -f 03-pvc.yaml
kubectl apply -f 04-deployment.yaml
kubectl apply -f 05-service.yaml
kubectl apply -f 06-ingress.yaml
Check it's healthy:
kubectl -n bookstack get pods
kubectl -n bookstack logs -f deploy/bookstack
kubectl -n bookstack port-forward svc/bookstack 8080:80
curl -i http://localhost:8080/status # should return 200
Default login is admin@admin.com / password — change it immediately after first login.
4. Point HAProxy at it
This setup gives you a ClusterIPProxy Service fronted by anand Ingress .Should point the DNS to the Proxy service. Because Bookstakc is running in diffrent Namespace, so HAProxy outsidecannot the cluster can't reach a ClusterIP directly — it needscross-connect to hitdiffrent somethingnamespace. exposed on the node, which is your ingress controller's NodePort (or LoadBalancer IP if you have one).
FindCreate theProxy ingressfor controller's NodePort:Bookstack:
kubectlapiVersion: v1
kind: Service
metadata:
name: bookstack-proxy
namespace: default
spec:
type: ExternalName
externalName: bookstack.bookstack.svc.cluster.local
ports:
-n <ingress-namespace>port: get svc <ingress-controller-svc>
80
Example HAProxy backend, assuming ingress-nginx is exposed on NodePort 30080 across your worker nodes:backend:
frontendI bookstack_feHosts:
bind *:80
mode http
default_backend bookstack_be
backend bookstack_be
mode http
balance roundrobin
option httpchk GET /status
http-check expect status 200
server node1 10.0.0.11:30080 check
server node2 10.0.0.12:30080 check
server node3 10.0.0.13:30080 check
http-request set-header Host- bookstack.yourdomain.localcom
The set-header Host line matters — your Ingress rule routes by hostname, so HAProxy must forwardAdd the same Host header that's setbelow in 06-ingress.yaml. If you terminate TLS at HAProxy and it's not the same as APP_URL's scheme, also set X-Forwarded-Proto.
Things to know
ReadWriteOnceReadWriteManykubectl execrules:
-it deploy/bookstackhost: bookstack.yourdomain.com
http:
paths:
-n bookstack -- phppath: /app/www/artisan
bookstack:update-urlpathType: <OLD>Prefix
<NEW>backend:
service:
name: bookstack-proxy
port:
number: 80/config.env