Files
homelab/kubernetes/apps/wordpress/mariadb.yaml
claude-botandClaude Opus 5 b784ce3d38
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
feat(wordpress): host www.renner.uno in the cluster behind an Authentik gate
Migrates the Hetzner VM 'wordpress-hochzeit' into the cluster. The site is not
the frozen wedding archive it looks like: it carries an unpublished, fully
composed birth announcement from 2026-05-14, so wp-admin has to keep working
for a human author rather than being replaced by a Git workflow.

Split routing on one host: '/' goes straight to WordPress so the blog stays
publicly readable, while /wp-admin and /wp-login.php route through a dedicated
wordpress-gate outpost. The inverse -- gate the host and skip_path_regex the
public paths, as paperless-gate does -- would need a negative lookahead, and
the outpost's RE2 has none.

Gating all of /wp-admin is only safe because the dead wpgiftregistry plugin is
dropped in the migration: it was the sole emitter of ajaxurl on this site, and
of 121 admin-ajax hits in the current log window not one returned 200.

Core lives in the pinned image, not on the volume, so upgrading is a tag bump
in Git. Only wp-content is persisted.

Backups are split the same way CNPG/Barman and Velero are elsewhere: the
operator takes a logical dump to Hetzner Object Storage at 02:45, Velero takes
the wp-content PVC at 08:45 and skips the database volume -- a crash-consistent
snapshot of a running database is not a backup.

Group membership for the gate is owned by Git, as with the Home Assistant,
Shelly and zone-configurator gates. s.renner is the Authentik account for the
site's WordPress Editor, whose login there is still the pre-2019 s.birzer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014giKN9N6e5J2DF8QZmVh98
2026-08-16 21:48:49 +02:00

87 lines
2.7 KiB
YAML

# The database for www.renner.uno. Named `wordpress-mariadb`, not `wp-*`:
# `wp-pg` in the woodpecker namespace is Woodpecker's Postgres, and reusing
# the prefix for an unrelated engine invites exactly the wrong guess.
#
# Mirrors the CNPG convention: the operator lives in `database`, the database
# resource lives with its consumer (ak-pg in authentik, im-pg in immich).
apiVersion: k8s.mariadb.com/v1alpha1
kind: MariaDB
metadata:
name: wordpress-mariadb
namespace: wordpress
annotations:
# Data guard — a Kustomization rebuild must never take the database with it.
kustomize.toolkit.fluxcd.io/prune: disabled
spec:
# generate: true lets the operator create both Secrets on first reconcile.
# Neither password is ever typed by a human or parked in OpenBao, so there
# is nothing to rotate by hand and nothing to leak through Git.
rootPasswordSecretKeyRef:
name: wordpress-mariadb-root
key: password
generate: true
username: wordpress
passwordSecretKeyRef:
name: wordpress-mariadb-app
key: password
generate: true
database: wordpress
port: 3306
replicas: 1
storage:
size: 5Gi
storageClassName: ceph-block
# The source database is 527 KB. 5Gi is not a sizing estimate, it is the
# smallest allocation that leaves room for binlogs and a decade of posts.
myCnf: |
[mariadb]
bind-address=*
default_storage_engine=InnoDB
innodb_buffer_pool_size=256M
# WordPress writes utf8mb4 throughout; the source dump carries
# utf8mb4_unicode_520_ci on every table that survives the migration.
character_set_server=utf8mb4
collation_server=utf8mb4_unicode_520_ci
max_allowed_packet=64M
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
memory: 1Gi
---
# Logical backup to Hetzner Object Storage. Velero only captures PVC data,
# which for a running database is crash-consistent, not transaction-consistent
# — that is a snapshot, not a backup you would want to restore from.
apiVersion: k8s.mariadb.com/v1alpha1
kind: Backup
metadata:
name: wordpress-mariadb-backup
namespace: wordpress
spec:
mariaDbRef:
name: wordpress-mariadb
schedule:
# 02:45, ahead of the Velero window that starts at 03:15.
cron: "45 2 * * *"
suspend: false
maxRetention: 720h # 30d, matching the CNPG ObjectStore retentionPolicy
compression: gzip
storage:
s3:
bucket: renner-uno-k8s-mariadb
prefix: wordpress
endpoint: fsn1.your-objectstorage.com
region: fsn1
accessKeyIdSecretKeyRef:
name: hetzner-s3-creds
key: ACCESS_KEY_ID
secretAccessKeySecretKeyRef:
name: hetzner-s3-creds
key: ACCESS_SECRET_KEY
tls:
enabled: true