Overview#
This deployment brings together WordPress, MariaDB, and phpMyAdmin as a fully declarative stack running on Kubernetes. Each component is version-controlled in Git and continuously synchronized to the cluster using Argo CD, following a GitOps workflow. WordPress connects to a persistent MariaDB backend, while phpMyAdmin provides an optional web interface for database management.
The setup is designed for clarity and reproducibility — every configuration, secret reference, and ingress rule is defined as code. Persistent volumes ensure data durability, and Traefik handles secure HTTPS access through automatic TLS certificates issued by cert-manager.

Folder Structure#
Each component of the stack is organized under its own directory within the apps/ folder, following a clear GitOps convention. The MariaDB and phpMyAdmin applications include their manifests and Helm values files, while WordPress uses a Kustomize structure with separate base and overlays directories for environment-specific configuration. This layout keeps application definitions modular, easy to maintain, and fully traceable through Git.
apps/
├── mariadb/
│ ├── resources/
│ ├── app.yaml
│ └── values.yaml
│
├── phpmyadmin/
│ ├── resources/
│ ├── app.yaml
│ └── values.yaml
│
└── wordpress/
├── base/
└── overlays/
├── dev/
└── prod/
MariaDB Configuration#
The MariaDB component is deployed through a dedicated Argo CD Application that combines the
Bitnami Helm chart with custom Kubernetes manifests for Helm values, storage, and Vault-based
secret synchronization. It serves as the persistent backend for both wordpress_dev
and wordpress_prod databases.
Helm Values#
The values.yaml file defines the main configuration for the Bitnami chart, including authentication, persistence, and security parameters.
Credentials are sourced from a Vault-managed Kubernetes Secret (vault-mariadb-secrets), which is synchronized by the CSI driver but not directly mounted inside the pod. MariaDB reads the credentials from the Secret at startup, ensuring that sensitive data remains managed securely outside Git.
# Excerpt from values.yaml
auth:
existingSecret: vault-mariadb-secrets
database: wordpress_dev
username: wordpress
primary:
persistence:
enabled: true
existingClaim: mariadb-pvc
storageClass: longhorn-retain
size: 4Gi
This configuration links the Helm release to a pre-provisioned PersistentVolumeClaim and references Vault-generated credentials for authentication.
The deployment automatically created the wordpress_dev database. Once phpMyAdmin was added for database management, a second database — wordpress_prod — was manually created to host the production WordPress instance.
Vault Integration#
A SecretProviderClass defines how credentials are synchronized from Vault to Kubernetes using
the Secrets Store CSI driver. The driver fetches values from Vault and mirrors them into a
Kubernetes Secret (vault-mariadb-secrets), which MariaDB reads at startup — credentials are
not mounted directly into the pod filesystem.
# Excerpt from mariadb-spc.yaml
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: vault-mariadb-secrets
namespace: mariadb
spec:
provider: vault
parameters:
vaultAddress: "https://vault.vault.svc:8200"
roleName: mariadb
objects: |
- objectName: "mariadb-root-password"
secretPath: "kv/data/mariadb/credentials"
secretKey: "rootPassword"
This approach keeps credentials securely managed in Vault, synchronized through Kubernetes, and fully compatible with standard Helm chart configurations.
Persistence#
MariaDB uses a dedicated PersistentVolume and PersistentVolumeClaim provisioned via Longhorn, ensuring data durability across pod restarts and upgrades.
# Excerpt from persistence.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mariadb-pvc
namespace: mariadb
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn-retain
resources:
requests:
storage: 4Gi
By using the longhorn-retain StorageClass, data remains intact even if the application is redeployed or removed, maintaining stateful reliability across the stack.
phpMyAdmin Configuration#
The phpMyAdmin component is deployed through a dedicated Argo CD Application that combines the Bitnami Helm chart with custom Kubernetes manifests for Helm values, TLS, and ingress routing. It provides a secure, web-based interface for managing both the wordpress_dev and wordpress_prod databases hosted on the MariaDB service.
Helm Values#
The values.yaml file defines the phpMyAdmin deployment settings — security contexts, resource presets, and the connection to MariaDB within the cluster.
Ingress is disabled at the chart level since routing is handled separately through a custom Traefik IngressRoute.
# Excerpt from values.yaml
db:
host: mariadb.mariadb.svc.cluster.local
port: 3306
allowArbitraryServer: false
bundleTestDB: false
enableSsl: false
This setup ensures encrypted HTTPS access to phpMyAdmin via phpmyadmin.kub.techcats.org, with certificates automatically renewed by cert-manager.
TLS and Ingress#
TLS certificates are automatically provisioned by cert-manager through a custom Certificate manifest using the DNS-01 challenge with the Vault-integrated issuer.
Traffic is routed securely through Traefik using an IngressRoute definition that references the issued certificate.
# Excerpt from ir-phpmyadmin.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: phpmyadmin
namespace: phpmyadmin
spec:
entryPoints:
- websecure
routes:
- match: Host(`phpmyadmin.kub.techcats.org`)
kind: Rule
services:
- name: phpmyadmin
port: 80
tls:
secretName: phpmyadmin-tls-secret
This setup ensures encrypted HTTPS access to phpMyAdmin via phpmyadmin.kub.techcats.org, with certificates automatically renewed by cert-manager.
All domains under *.kub.techcats.org are resolved locally through Pi-hole and are not publicly accessible outside the homelab network.

phpmyadmin.kub.techcats.org. The interface connects to the internal MariaDB service and manages both wordpress_dev and wordpress_prod databases.WordPress Configuration#
The WordPress component is deployed through two dedicated Argo CD Applications — one for development and one for production — each in its own namespace (wordpress-dev and wordpress-prod).
Both integrate the Bitnami Helm chart with custom Helm values, persistence, TLS, ingress routing, and Vault-based secret synchronization, maintaining a consistent, declarative configuration aligned with the homelab’s GitOps principles.
Helm Values#
The values.yaml file defines the main configuration for the Bitnami WordPress Helm chart, covering authentication, persistence, and external database connectivity.
Both development and production environments share a common base configuration, while overlays apply small patches for environment-specific settings such as database names, Vault roles, and ingress definitions.
Credentials and database passwords are fetched from Vault through the Secrets Store CSI driver, which mirrors them into Kubernetes Secrets (vault-wp-admin-secret and vault-wp-db-secret) without exposing plaintext values in Git.
Each overlay references its own Vault role (wordpress-dev or wordpress-prod), ensuring that secrets remain isolated per environment while maintaining consistent configuration logic.
# Excerpt from values.yaml
externalDatabase:
host: mariadb.mariadb.svc.cluster.local
port: 3306
user: wordpress
database: wordpress
existingSecret: vault-wp-db-secret
persistence:
enabled: true
existingClaim: wordpress-pvc
storageClass: longhorn-retain
accessModes:
- ReadWriteOnce
size: 4Gi
Vault Integration#
A dedicated SecretProviderClass defines how WordPress credentials and database passwords are synchronized from Vault into Kubernetes using the Secrets Store CSI driver.
Each environment (wordpress-dev and wordpress-prod) uses a separate Vault role and secret path, ensuring isolated authentication and environment-specific secret scoping.
The CSI driver retrieves values such as the WordPress admin credentials, site metadata, and external MariaDB password directly from Vault, mirroring them into two Kubernetes Secrets — vault-wp-admin-secret and vault-wp-db-secret — which are referenced by the Helm chart at runtime.
Sensitive data never appears in Git or manifests, maintaining end-to-end secret confidentiality.
# Excerpt from wordpress-spc.yaml
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: vault-wp-secrets
namespace: wordpress
spec:
provider: vault
parameters:
vaultAddress: "https://vault.vault.svc:8200"
roleName: wordpress
objects: |
- objectName: "wordpress-username"
secretPath: "kv/data/wordpress/site"
secretKey: "username"
- objectName: "mariadb-password"
secretPath: "kv/data/wordpress/db"
secretKey: "password"
Separate patches modify the roleName and secret paths for each environment, referencing kv/data/wordpress-dev/ or kv/data/wordpress-prod/.
This approach aligns with the same Vault-based secret management workflow used across MariaDB and phpMyAdmin, keeping credentials dynamic, isolated, and centrally m
Persistence#
WordPress uses dedicated PersistentVolume and PersistentVolumeClaim resources provisioned through Longhorn, ensuring data durability for uploads, plugins, and themes across pod restarts and redeployments.
Both environments share the same storage specification, while JSON 6902 patches adjust the volume name and handle to create distinct resources for development and production.
# Excerpt from persistence.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wordpress-pvc
namespace: wordpress
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn-retain
resources:
requests:
storage: 4Gi
Each overlay applies a small patch that renames the PersistentVolume and updates its Longhorn volume handle to either wordpress-dev or wordpress-prod, preventing cross-environment overlap.
Using the longhorn-retain StorageClass ensures data persists across redeployments, providing long-term stateful reliability within the homelab.
TLS and Ingress#
TLS certificates are automatically issued by cert-manager using the Let’s Encrypt ClusterIssuer (letsencrypt-vault-issuer), which retrieves its Cloudflare API credentials securely from Vault.
Traffic is securely routed through Traefik via an IngressRoute definition that references the generated certificate.
# Excerpt from ir-wordpress.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: wordpress
namespace: wordpress
spec:
entryPoints:
- websecure
routes:
- match: Host(`techcats.org`)
kind: Rule
services:
- name: wordpress-prod
port: 80
tls:
secretName: wordpress-tls-secret
Each overlay applies patches to adjust hostnames and certificates per environment —development.techcats.org for the dev instance and techcats.org for production.
The development site is accessible only within the local network, while the production instance is exposed externally through Cloudflare and can be viewed at https://techcats.org.

techcats.org, with TLS issued by cert-manager via the Vault-integrated Let’s Encrypt ClusterIssuer.An additional Traefik Middleware restricts access to /wp-admin and /wp-login.php, allowing only trusted local IP ranges within the homelab.
This configuration is described in detail in this post.
Backup and Recovery#
Cluster-level backups are automated through Velero, ensuring data resilience for all namespaces and persistent volumes.
Velero runs a daily scheduled backup with the following configuration:
- Schedule:
0 3 * * * - Retention: 7 days (
ttl: 168h)
The entire backup process and configuration are documented in detail here.
Key Takeaways#
- Demonstrates a complete GitOps workflow managed through Argo CD.
- Integrates Vault for centralized, environment-specific secret management.
- Uses Longhorn for persistent volumes and Traefik for ingress/TLS.
- Includes automated daily backups with Velero for data resilience and recovery.
- Shows clear separation between development and production environments.
Repository & Implementation#
All manifests, overlays, and Helm configurations are version-controlled in Git and deployed via Argo CD.
Full implementation available at whitehatcats/k3s-homelab-gitops.
References#
- Bitnami MariaDB Helm Chart – Artifact Hub
- Bitnami phpMyAdmin Helm Chart – Artifact Hub
- Bitnami WordPress Helm Chart – Artifact Hub
