WordPress
Deploy WordPress on Kubernetes with the official
docker.io/library/wordpress:6.9.4-apache image. The chart keeps the default install useful for
development while exposing explicit production controls for database selection, secrets, routing,
storage, backup, network policy, metrics, WordPress cron, plugin installation, and Redis Object Cache.
A default install creates one WordPress pod, a HelmForge MySQL subchart, generated credentials, and a ReadWriteOnce PVC. Production deployments should opt in to explicit secrets, TLS routing, resources, backup, NetworkPolicy, and a storage/database strategy that matches the required availability target.
Key Features
- Official image alignment - uses the upstream WordPress Apache image, with PHP configuration exposed through values.
- Database choices - HelmForge MySQL subchart by default, or external MySQL/MariaDB through structured values.
- Secrets paths - generated Kubernetes Secrets, existing Secrets, or optional External Secrets Operator resources.
- Routing - classic Ingress and native Gateway API
HTTPRoute. - Networking - dual-stack Service fields and optional NetworkPolicy ingress/egress rules.
- Production controls - ServiceAccount token mount control, resources, HPA, PDB, deterministic WordPress cron, probes, and scheduling.
- Backups - S3-compatible CronJob that exports both the database and
wp-content. - Plugins - idempotent post-install/post-upgrade installer for official WordPress.org plugin slugs.
- Redis Object Cache - optional
redis-cacheplugin and drop-in using HelmForge Redis or external Redis. - Extensibility - extra env, envFrom, init containers, sidecars, volumes, mounts, and raw extra manifests.
Architecture
Production
Traffic reaches WordPress through Ingress or Gateway API. Credentials can come from Kubernetes Secrets or External Secrets Operator. Backups export both database and content artifacts.
Redis Object Cache
Redis Object Cache is enabled by installing the official plugin, creating the object-cache.php drop-in, and pointing WordPress at a subchart or external Redis endpoint.
Installation
HTTPS repository:
helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install wordpress helmforge/wordpress -f values.yaml
OCI registry:
helm install wordpress oci://ghcr.io/helmforgedev/helm/wordpress -f values.yaml
For local access without Ingress or Gateway API:
kubectl port-forward svc/wordpress 8080:80
Development vs Production
Use the default install for local validation, demos, and development. It gives you a working WordPress instance quickly with a bundled MySQL database and persistent WordPress files.
For production, make the choices explicit:
- Use existing Secrets or External Secrets Operator for admin, database, backup, and Redis credentials.
- Set
wordpress.siteUrl,wordpress.forceSSLAdmin,wordpress.disallowFileEdit, andwordpress.disableWpCron. - Use Gateway API or Ingress with TLS and route-aware NetworkPolicy ingress rules.
- Set CPU and memory requests, limits, PDB, and HPA only after storage is safe for multiple pods.
- Use
ReadWriteManystorage or an immutable media/plugin strategy before running multiple replicas. - Enable the Kubernetes
wpCronCronJob whenDISABLE_WP_CRONis set. - Test backup and restore, not only backup creation.
WordPress writes uploads, plugins, themes, and drop-ins under /var/www/html. Keep one replica with the default
ReadWriteOnce PVC. Use ReadWriteMany storage or a custom immutable image/object-storage media strategy before enabling
HPA or setting replicaCount above 1.
Deployment Examples
# values.yaml - development install with bundled MySQL
wordpress:
siteTitle: My Blog
adminUser: admin
adminPassword: change-me
adminEmail: admin@example.com
mysql:
enabled: true
auth:
password: db-password
persistence:
enabled: true
size: 10Gi
php:
ini: |
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M# values.yaml - production-oriented baseline
wordpress:
siteUrl: https://blog.example.com
siteTitle: My Blog
adminEmail: admin@example.com
forceSSLAdmin: true
disallowFileEdit: true
disableWpCron: true
memoryLimit: 256M
maxMemoryLimit: 512M
admin:
existingSecret: wordpress-admin
mysql:
enabled: false
database:
external:
host: mysql.example.com
port: 3306
name: wordpress
username: wordpress
existingSecret: wordpress-db
existingSecretPasswordKey: password
persistence:
enabled: true
accessMode: ReadWriteMany
storageClass: nfs-rwx
size: 20Gi
service:
ipFamilyPolicy: PreferDualStack
ipFamilies:
- IPv4
- IPv6
gatewayAPI:
enabled: true
parentRefs:
- name: public
namespace: gateway-system
sectionName: https
hostnames:
- blog.example.com
networkPolicy:
enabled: true
ingress:
extraFrom:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: gateway-system
egress:
enabled: true
allowDNS: true
allowSameNamespaceDatabase: false
allowHTTPS: true
wpCron:
cronJob:
enabled: true
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 5
pdb:
enabled: true
minAvailable: 1
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: '1'
memory: 1Gi# values.yaml - external MySQL or MariaDB
mysql:
enabled: false
database:
mode: external
external:
host: mysql.database.svc.cluster.local
port: 3306
name: wordpress
username: wordpress
existingSecret: wordpress-db
existingSecretPasswordKey: database-password
admin:
existingSecret: wordpress-admin
existingSecretPasswordKey: admin-password
backup:
enabled: true
s3:
endpoint: https://s3.amazonaws.com
bucket: wordpress-backups
existingSecret: wordpress-s3# values.yaml - Gateway API with External Secrets Operator
mysql:
enabled: false
database:
external:
host: mysql.example.com
name: wordpress
username: wordpress
existingSecretPasswordKey: database-password
externalSecrets:
enabled: true
secretStoreRef:
name: vault
kind: ClusterSecretStore
admin:
enabled: true
passwordRemoteRef:
key: prod/wordpress
property: admin-password
database:
enabled: true
passwordRemoteRef:
key: prod/wordpress
property: database-password
gatewayAPI:
enabled: true
parentRefs:
- name: public
namespace: gateway-system
hostnames:
- blog.example.com# values.yaml - Redis Object Cache using HelmForge Redis subchart
plugins:
enabled: true
installer:
enabled: true
objectCache:
enabled: true
redis:
mode: subchart
subchart:
enabled: true
redis:
architecture: standalone
auth:
existingSecret: wordpress-redis-auth
existingSecretPasswordKey: redis-password# values.yaml - Redis Object Cache using external Redis
plugins:
enabled: true
installer:
enabled: true
objectCache:
enabled: true
redis:
mode: external
external:
host: redis.example.com
auth:
existingSecret: wordpress-redis
existingSecretPasswordKey: redis-password# values.yaml - install official WordPress.org plugins
persistence:
enabled: true
plugins:
enabled: true
installer:
enabled: true
activeDeadlineSeconds: 60
items:
- slug: classic-editor
activate: true
skipIfInstalled: true
- slug: contact-form-7
activate: true
skipIfInstalled: trueThe installer Job writes files into the WordPress volume. It fails validation when plugins.installer.enabled=true
and persistence.enabled=false, because an isolated Job emptyDir would not be visible to the WordPress pod.
# values.yaml - S3-compatible backup of database and wp-content
backup:
enabled: true
schedule: '0 3 * * *'
archivePrefix: wordpress
s3:
endpoint: https://s3.amazonaws.com
bucket: wordpress-backups
existingSecret: wordpress-s3
existingSecretAccessKeyKey: access-key
existingSecretSecretKeyKey: secret-key
database:
mysqldumpArgs: '--single-transaction --quick --skip-lock-tables --no-tablespaces'With External Secrets Operator-managed backup credentials:
backup:
enabled: true
s3:
endpoint: https://s3.amazonaws.com
bucket: wordpress-backups
externalSecrets:
enabled: true
secretStoreRef:
name: vault
kind: ClusterSecretStore
backup:
enabled: true
accessKeyRemoteRef:
key: prod/wordpress-backup
property: access-key
secretKeyRemoteRef:
key: prod/wordpress-backup
property: secret-keyOperational Notes
Database Mode
When database.mode: auto, the chart resolves the database path in this order:
database.external.hostordatabase.external.existingSecretselects an external database.mysql.enabled: trueselects the HelmForge MySQL subchart.- Rendering fails because WordPress requires MySQL or MariaDB.
WordPress Cron
Set wordpress.disableWpCron: true and enable wpCron.cronJob.enabled when you want deterministic
cron execution from Kubernetes instead of request-driven wp-cron.php.
Redis Object Cache
The chart installs the official redis-cache plugin, writes the object-cache.php drop-in, and sets
WP_CACHE plus WP_REDIS_* constants. Redis is only functionally validated when WordPress can create
cache keys in Redis; a running Redis pod alone is not enough.
If the Redis subchart is used and redis.auth.enabled=false, WordPress does not reference a Redis
password Secret. If the subchart uses redis.auth.existingSecret, WordPress and the plugin installer
read the same Secret and key.
NetworkPolicy
networkPolicy.enabled isolates application ingress. Add networkPolicy.ingress.extraFrom for an
Ingress controller or Gateway controller that runs outside the WordPress namespace. Egress rules are
opt-in; list DNS, database, HTTPS, SMTP, or custom destinations before enabling restrictive egress.
Backups and Restore
The backup CronJob writes two artifacts:
- A compressed database dump from
mysqldump. - A compressed archive of
/var/www/html/wp-content.
Production users should test restore procedures, object storage retention, object lock, and database consistency. A successful backup upload is not a complete disaster recovery validation.
Configuration Reference
Core and Image
| Parameter | Type | Default | Description |
|---|---|---|---|
nameOverride | string | "" | Override chart name. |
fullnameOverride | string | "" | Override full release name. |
commonLabels | object | {} | Labels added to every rendered resource. |
replicaCount | integer | 1 | WordPress replicas when HPA is disabled. |
image.repository | string | docker.io/library/wordpress | WordPress image repository. |
image.tag | string | 6.9.4-apache | Image tag. |
image.pullPolicy | string | IfNotPresent | Image pull policy. |
imagePullSecrets | array | [] | Pull secrets for private registries. |
WordPress
| Parameter | Type | Default | Description |
|---|---|---|---|
wordpress.siteUrl | string | "" | Full external site URL. |
wordpress.siteTitle | string | WordPress | Site title. |
wordpress.adminUser | string | admin | Initial admin user. |
wordpress.adminPassword | string | "" | Initial admin password, generated when empty. |
wordpress.adminEmail | string | admin@example.com | Initial admin email. |
wordpress.tablePrefix | string | wp_ | Database table prefix. |
wordpress.debug | boolean | false | Enables WP_DEBUG. |
wordpress.forceSSLAdmin | boolean | false | Sets FORCE_SSL_ADMIN. |
wordpress.disallowFileEdit | boolean | false | Sets DISALLOW_FILE_EDIT. |
wordpress.disableWpCron | boolean | false | Sets DISABLE_WP_CRON. |
wordpress.memoryLimit | string | "" | Sets WP_MEMORY_LIMIT. |
wordpress.maxMemoryLimit | string | "" | Sets WP_MAX_MEMORY_LIMIT. |
wordpress.configExtra | string | "" | Extra PHP appended to wp-config.php. |
wordpress.extraEnv | array | [] | Extra container environment variables. |
wordpress.extraEnvFrom | array | [] | Extra envFrom sources. |
php.ini | string | "" | Custom PHP ini settings mounted into Apache PHP. |
Credentials and External Secrets
| Parameter | Type | Default | Description |
|---|---|---|---|
admin.existingSecret | string | "" | Existing admin Secret. |
admin.existingSecretPasswordKey | string | admin-password | Admin password key. |
externalSecrets.enabled | boolean | false | Render ExternalSecret resources. |
externalSecrets.apiVersion | string | external-secrets.io/v1 | External Secrets Operator API version. |
externalSecrets.secretStoreRef.name | string | "" | SecretStore or ClusterSecretStore name. |
externalSecrets.secretStoreRef.kind | string | SecretStore | Store kind. |
externalSecrets.admin.enabled | boolean | false | Manage admin password with ESO. |
externalSecrets.database.enabled | boolean | false | Manage external database password with ESO. |
externalSecrets.backup.enabled | boolean | false | Manage backup S3 credentials with ESO. |
Database and MySQL Subchart
| Parameter | Type | Default | Description |
|---|---|---|---|
database.mode | string | auto | auto, external, or mysql. |
database.external.host | string | "" | External database hostname. |
database.external.port | integer | 3306 | External database port. |
database.external.name | string | wordpress | Database name. |
database.external.username | string | wordpress | Database username. |
database.external.password | string | "" | Inline external database password. |
database.external.existingSecret | string | "" | Existing database password Secret. |
database.external.existingSecretPasswordKey | string | database-password | Password key in Secret. |
mysql.enabled | boolean | true | Deploy HelmForge MySQL subchart. |
mysql.architecture | string | standalone | MySQL architecture. |
mysql.auth.database | string | wordpress | Subchart database name. |
mysql.auth.username | string | wordpress | Subchart database user. |
mysql.auth.password | string | "" | Subchart user password. |
mysql.auth.rootPassword | string | "" | Subchart root password. |
mysql.primary.persistence.enabled | boolean | true | Enable MySQL PVC. |
mysql.primary.persistence.size | string | 8Gi | MySQL PVC size. |
Storage, Service, and Routing
| Parameter | Type | Default | Description |
|---|---|---|---|
persistence.enabled | boolean | true | Create PVC for /var/www/html. |
persistence.storageClass | string | "" | StorageClass name. |
persistence.accessMode | string | ReadWriteOnce | PVC access mode. |
persistence.size | string | 5Gi | WordPress PVC size. |
persistence.existingClaim | string | "" | Use an existing PVC. |
service.type | string | ClusterIP | Service type. |
service.port | integer | 80 | HTTP Service port. |
service.ipFamilyPolicy | string | "" | SingleStack, PreferDualStack, or RequireDualStack. |
service.ipFamilies | array | [] | Ordered IP families, for example ["IPv4", "IPv6"]. |
ingress.enabled | boolean | false | Create Ingress. |
ingress.ingressClassName | string | "" | Ingress class. |
ingress.hosts | array | [] | Ingress host rules. |
ingress.tls | array | [] | Ingress TLS entries. |
gatewayAPI.enabled | boolean | false | Create Gateway API HTTPRoute. |
gatewayAPI.apiVersion | string | gateway.networking.k8s.io/v1 | HTTPRoute API version. |
gatewayAPI.parentRefs | array | [] | Parent Gateway references. |
gatewayAPI.hostnames | array | [] | HTTPRoute hostnames. |
gatewayAPI.matches | array | Path prefix / | HTTPRoute matches. |
Production Controls
| Parameter | Type | Default | Description |
|---|---|---|---|
serviceAccount.create | boolean | false | Create a dedicated ServiceAccount. |
serviceAccount.automountServiceAccountToken | boolean | false | Mount Kubernetes API token into pods. |
autoscaling.enabled | boolean | false | Create HPA. |
autoscaling.minReplicas | integer | 2 | HPA minimum replicas. |
autoscaling.maxReplicas | integer | 5 | HPA maximum replicas. |
pdb.enabled | boolean | false | Create PodDisruptionBudget. |
pdb.minAvailable | integer | 1 | Minimum available pods. |
wpCron.cronJob.enabled | boolean | false | Create Kubernetes CronJob for wp-cron.php. |
wpCron.cronJob.schedule | string | */5 * * * * | WordPress cron schedule. |
resources | object | {} | WordPress container requests and limits. |
podSecurityContext | object | {} | Pod security context. |
securityContext | object | {} | Container security context. |
Plugins and Redis Object Cache
| Parameter | Type | Default | Description |
|---|---|---|---|
plugins.enabled | boolean | false | Enable plugin installer support. |
plugins.installer.enabled | boolean | false | Create post-install/post-upgrade installer Job. |
plugins.installer.activeDeadlineSeconds | integer | 60 | Installer timeout. |
plugins.installer.backoffLimit | integer | 1 | Installer retry limit. |
plugins.installer.preferWordPressPodNode | boolean | true | Prefer node with WordPress pod for RWO PVCs. |
plugins.items | array | [] | Official WordPress.org plugin slugs. |
objectCache.enabled | boolean | false | Enable object cache integration. |
objectCache.provider | string | redis-cache | Supported provider. |
objectCache.installPlugin | boolean | true | Install and activate redis-cache. |
objectCache.enableDropIn | boolean | true | Create wp-content/object-cache.php. |
objectCache.redis.mode | string | subchart | subchart or external. |
objectCache.redis.subchart.enabled | boolean | false | Deploy HelmForge Redis dependency. |
objectCache.redis.external.host | string | "" | External Redis hostname. |
objectCache.redis.port | integer | 6379 | Redis port. |
objectCache.redis.database | integer | 0 | Redis database number. |
objectCache.redis.auth.enabled | boolean | true | Use Redis password when effective Redis auth is enabled. |
objectCache.redis.auth.existingSecret | string | "" | Existing Redis password Secret. |
NetworkPolicy, Metrics, and Backup
| Parameter | Type | Default | Description |
|---|---|---|---|
networkPolicy.enabled | boolean | false | Create NetworkPolicy. |
networkPolicy.ingress.allowSameNamespace | boolean | true | Allow same namespace ingress. |
networkPolicy.ingress.extraFrom | array | [] | Extra application ingress sources. |
networkPolicy.metrics.enabled | boolean | false | Add metrics ingress rule. |
networkPolicy.egress.enabled | boolean | false | Manage egress allow list. |
networkPolicy.egress.allowDNS | boolean | true | Allow DNS egress. |
networkPolicy.egress.allowSameNamespaceDatabase | boolean | true | Allow same-namespace DB egress. |
networkPolicy.egress.allowHTTPS | boolean | false | Allow HTTPS egress. |
networkPolicy.egress.allowSMTP | boolean | false | Allow SMTP egress. |
metrics.enabled | boolean | false | Enable Apache exporter sidecar. |
metrics.serviceMonitor.enabled | boolean | false | Create ServiceMonitor. |
backup.enabled | boolean | false | Create S3-compatible backup CronJob. |
backup.schedule | string | 0 3 * * * | Backup schedule. |
backup.s3.endpoint | string | "" | S3 endpoint URL. |
backup.s3.bucket | string | "" | Backup bucket. |
backup.s3.existingSecret | string | "" | Existing S3 credentials Secret. |
backup.database.mysqldumpArgs | string | --single-transaction --quick --skip-lock-tables --no-tablespaces | Extra mysqldump arguments. |
Scheduling and Extensions
| Parameter | Type | Default | Description |
|---|---|---|---|
nodeSelector | object | {} | Node selector. |
tolerations | array | [] | Pod tolerations. |
affinity | object | {} | Pod affinity. |
topologySpreadConstraints | array | [] | Topology spread constraints. |
priorityClassName | string | "" | PriorityClass name. |
terminationGracePeriodSeconds | integer | 30 | Pod termination grace period. |
podLabels | object | {} | Extra pod labels. |
podAnnotations | object | {} | Extra pod annotations. |
extraVolumeMounts | array | [] | Extra WordPress volume mounts. |
extraVolumes | array | [] | Extra pod volumes. |
extraInitContainers | array | [] | Additional init containers. |
extraContainers | array | [] | Additional sidecars. |
extraManifests | array | [] | Extra Kubernetes manifests. |