This Helm chart installs WordPress in a Kubernetes cluster with many advanced features. It is based on the official WordPress image and provides automation for installation, user management, plugin installation, and metrics for prometheus (WordPress and Apache).
You can find different sample YAML files (external database, integrated MariaDB and advanced configuration) in the GitHub repo in the subfolder "samples".
Note: No default site is set as "Home", so you will see no landing page. You have to log in to /wp-admin to install a theme.
# ./samples/mariaDB.secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: wordpress-test-secret
stringData:
mariadb-root-password: S3cureDBP@ss
mariadb-password: Sup3rS3cureP@ss
type: Opaque
# ./samples/mariaDB.values.yaml
wordpress:
url: "https://example.com"
mariadb:
auth:
database: "wordpress_db"
username: "wordpress_db_user"
existingSecret: "wordpress-test-secret"Install with Helm:
kubectl apply -f ./samples/mariaDB.secrets.yaml
helm install wordpress oci://ghcr.io/slybase/charts/wordpress --values ./samples/mariaDB.values.yaml- Init Container: Automatic initial installation of WordPress with predefined admin credentials.
- Configuration: Set admin username, password, email, first name, last name, and blog title.
- Debug Mode: Enable debugging for the installation.
- Permalinks: Configure permalink structures (e.g., post name, day and name).
- Automatic User Generation: Create additional users with roles (Administrator, Editor, etc.).
- Email Notification: Automatically send emails with generated passwords.
- Language: Set the WordPress language (e.g., de_DE for German).
- Automatic Installation: Install plugins from WordPress.org, local ZIPs, URLs, or Composer packages.
- Composer Support: Install plugins via Composer (e.g.,
humanmade/s3-uploads) that aren't available in WordPress.org. - Versioning: Specify plugin versions (WordPress.org and Composer syntax supported).
- Activation and Auto-Updates: Activate plugins after installation and enable auto-updates.
- Automatic Installation: Install themes from WordPress.org, local ZIPs, URLs, or Composer packages.
- Composer Support: Install themes via Composer (e.g.,
wpackagist-theme/astra). - Versioning: Specify theme versions (WordPress.org and Composer syntax supported).
- Activation and Auto-Updates: Activate themes after installation and enable auto-updates.
- Custom Themes: Support for custom theme ZIPs via direct URLs.
- Subdirectory and Subdomain modes: Configure multisite with either
example.com/blogorblog.example.comstyle URLs. - Automatic Site Creation: Define sub-sites declaratively in values and they are created/updated on every init.
- Network-wide Plugin/Theme Activation: Use
networkActivatefor plugins ornetworkEnablefor themes to make them available across all sites. - Independent Main Site and Sub-Site Control:
activatecontrols the main site,sites[]controls sub-sites — both can be combined. - Per-Site User Roles: Assign users to specific sub-sites with individual roles.
- Site Pruning: Optionally archive sites not defined in the configuration.
- URL Plugin/Theme Slug Override: Use the
slugproperty for URL-based plugins/themes to enable full feature support (autoupdate, sites, network activation).
- External Database: Use an external MariaDB/MySQL database.
- Embedded MariaDB: Enable the integrated MariaDB chart for local database.
- Memcached: Enable Memcached for caching.
- Redis: Enable Redis as alternative caching.
- Valkey: Enable Valkey (Redis fork) as alternative caching.
- WordPress Metrics: Automatically install a WordPress Plugin for Prometheus metrics.
- See details on GitHub Repo of (SlyMetrics Plugin from slydlake)[https://github.com/slydlake/slymetrics]
- Apache Metrics: Sidecar container for Apache metrics.
- See details on GitHub Repo of (apache exporter from Lusitaniae)[https://github.com/Lusitaniae/apache_exporter]
- Grafana Dashboard: Automatically deploy Grafana dashboard for WordPress metrics visualization.
- Requires Grafana with dashboard sidecar (e.g., kube-prometheus-stack)
- Automatically discovered via
grafana_dashboard: "1"labe
- Custom wp-config.php: Additional constants like WP_MEMORY_LIMIT.
- .htaccess Configuration: Customize Apache URL rewriting and directives via
wordpress.htaccess. - Apache Default Site Config: Modify
/etc/apache2/sites-available/000-default.confusingapache.customDefaultSiteConfig. - Apache Ports Config: Adjust
/etc/apache2/ports.confwithapache.customPortsConfig. - Apache PHP Config: Set PHP settings like upload limits via
apache.customPhpConfig.
- Execute custom shell commands after init.sh via ConfigMap (
wordpress.init.customInitConfigMap.name) - Perfect for custom setup tasks like updating plugins or creating pages
- Configure with
nameand optionalkey(defaults to "commands.sh") - See
samples/customInit.configmap.yamlandsamples/customInit.values.yaml
- Deploy MU-Plugins via ConfigMaps - automatically activated PHP code that cannot be deactivated
- Each ConfigMap data key becomes a PHP file in
wp-content/mu-plugins/ - Reference multiple ConfigMaps in
wordpress.muPluginsConfigMaps - See
samples/muPlugins.configmap.yamlandsamples/muPlugins.values.yaml
- Pod Security Context: Configured by default for secure permissions.
- Container Security Context: RunAsNonRoot and additional security measures enabled.
wordpress.url: The WordPress site URL. Used to automatically setWP_HOMEandWP_SITEURL— both as environment variables in the container and as PHP constants inwp-config.php(viaWORDPRESS_CONFIG_EXTRA). If no protocol is provided, it is auto-prefixed withhttps://(when Ingress TLS is configured) orhttp://(otherwise).storage: Set your storage settings for WordPress- By default
mariadb.enabledis true. You have to setmariadb.authandmariadb.persistence. Alternatively, it is also possible to use an external database.
Note: If you need full manual control over
WP_HOME/WP_SITEURL(e.g. different values for each, or a reverse proxy setup), setwordpress.configExtraInject: falseto disable the automatic injection intowp-config.phpand define the constants yourself viawordpress.configExtra. The environment variablesWP_HOMEandWP_SITEURLin the container are always derived fromwordpress.url.
wordpress.init: Admin credentials and blog setupservice.type: If you want to access WordPress internally set it to NodePort
wordpress.plugins: List of plugins to installwordpress.themes: List of themes to installwordpress.users: Additional userswordpress.language: Language (e.g., de_DE)wordpress.permalinks.structure: Set the wordpress post structuremetrics.wordpress: Enable WordPress metricsmetrics.apache: Enable Apache metricsmemcached.enabled: Enable embedded memcachedredis.enabled: Enable embedded redisvalkey.enabled: Enable embedded valkey
Note: Cache backends are mutually exclusive. Enable at most one of
memcached.enabled,redis.enabled,valkey.enabled(validated byvalues.schema.jsonand templates).
Enable exactly one backend via its enabled flag.
- Memcached (
memcached.enabled)- Subchart on port
11211 - Injects:
WP_CACHE,WP_CACHE_KEY_SALT,$memcached_servers - Additional init logic for PHP extensions
memcache/memcached
- Subchart on port
- Redis (
redis.enabled)- Subchart on port
6379 - Injects:
WP_CACHE,WP_CACHE_KEY_SALT,WP_REDIS_HOST,WP_REDIS_PORT, optionalWP_REDIS_PASSWORD - No extra PHP-extension init required (
redis-cacheuses Predis)
- Subchart on port
- Valkey (
valkey.enabled)- Same behavior as Redis (Valkey is Redis-compatible), also on port
6379 - Same inject pattern as Redis
- Same behavior as Redis (Valkey is Redis-compatible), also on port
Common options for all backends:
createConfig: Enables/disables automatic config injectioncacheKeySalt: Explicit salt valuecacheKeySaltSecret.{name,key}: Salt from an existing Secret- Redis/Valkey auth password from
auth.passwordorauth.existingSecret
Minimal example (Redis):
redis:
enabled: true
createConfig: true
cacheKeySalt: ""
cacheKeySaltSecret:
name: ""
key: "WP_CACHE_KEY_SALT"
auth:
password: ""
existingSecret: ""
existingSecretPasswordKey: "redis-password"Quick check (same idea for Redis/Valkey):
kubectl -n default exec deploy/wp-wordpress -c wordpress -- ls -l /var/www/html/wp-content/object-cache.php
kubectl -n default exec deploy/wp-wordpress -c wordpress -- redis-cli -h wp-redis INFO stats | grep -E 'keyspace_hits|keyspace_misses'Short stats checks:
# Memcached
kubectl -n default exec deploy/wp-wordpress -c wordpress -- php -r '$s=fsockopen("wp-memcached",11211,$e,$es,2); fwrite($s,"stats\r\n"); echo stream_get_contents($s); fclose($s);' | grep -E 'STAT cmd_get|STAT cmd_set|STAT get_hits|STAT get_misses'
# Redis / Valkey
kubectl -n default exec deploy/wp-wordpress -c wordpress -- redis-cli -h wp-redis INFO stats | grep -E 'keyspace_hits|keyspace_misses'See in TL;DR
Find the externalDB.secrets.yaml and externalDB.values.yaml in the GitHub repo in the subfolder "samples".
kubectl apply -f ./samples/externalDB.secrets.yaml
helm install wordpress oci://ghcr.io/slybase/charts/wordpress --values ./samples/externalDB.values.yamlFind the advanced.secrets.yaml, advanced.configmap.yaml and advanced.values.yaml in the GitHub repo in the subfolder "samples".
This includes everything from basic installation plus:
- Initial setup of WordPress
- Plugin Installation
- Theme Installation
- Additional WordPress user
- Additional configuration files
- .htaccess
- wp-config.php settings
- apache custom.ini
- Permanent Nodeport
- Prometheus metrics
- For WordPress
- For Apache
- Memcached pod
kubectl apply -f ./samples/advanced.secrets.yaml
kubectl apply -f ./samples/advanced.configmap.yaml
helm install wordpress oci://ghcr.io/slybase/charts/wordpress --values ./samples/advanced.values.yamlFind the advanced2.values.yaml in the GitHub repo in the subfolder "samples".
This setup includes everything from the advanced installation plus:
- 2 WordPress pod replicas for high availability and load balancing
- ReadWriteMany (RWX) storage to allow multiple pods to share the same WordPress files
- Distributed locking ensures only one pod runs init operations at a time (prevents race conditions)
Requirements:
- Your cluster must support ReadWriteMany (RWX) storage class (e.g., NFS, Ceph, cloud provider shared storage)
- LoadBalancer or Ingress for distributing traffic across replicas
kubectl apply -f ./samples/advanced.secrets.yaml
kubectl apply -f ./samples/advanced.configmap.yaml
helm install wordpress oci://ghcr.io/slybase/charts/wordpress --values ./samples/advanced2.values.yamlUse WordPress with Memcached object cache. See samples/memcached.values.yaml.
helm install wordpress oci://ghcr.io/slybase/charts/wordpress --values ./samples/memcached.values.yamlUse WordPress with Redis object cache. See samples/redis.values.yaml.
helm install wordpress oci://ghcr.io/slybase/charts/wordpress --values ./samples/redis.values.yamlUse WordPress with Valkey object cache. See samples/valkey.values.yaml.
helm install wordpress oci://ghcr.io/slybase/charts/wordpress --values ./samples/valkey.values.yamlExecute custom shell commands after WordPress installation. See samples/customInit.configmap.yaml and samples/customInit.values.yaml.
kubectl apply -f ./samples/customInit.configmap.yaml
helm install wordpress oci://ghcr.io/slybase/charts/wordpress --values ./samples/customInit.values.yamlDeploy Must-Use Plugins that are automatically activated. See samples/muPlugins.configmap.yaml and samples/muPlugins.values.yaml.
kubectl apply -f ./samples/muPlugins.configmap.yaml
helm install wordpress oci://ghcr.io/slybase/charts/wordpress --values ./samples/muPlugins.values.yamlInstall plugins and themes via Composer that aren't available in WordPress.org. See samples/composer.values.yaml.
helm install wordpress oci://ghcr.io/slybase/charts/wordpress --values ./samples/composer.values.yamlExamples:
- Plugins:
humanmade/s3-uploads,wpackagist-plugin/wordpress-seo - Themes:
wpackagist-theme/astra - Library Packages: Composer packages listed under
wordpress.pluginsorwordpress.themesare explicitly mapped intowp-content/pluginsorwp-content/themes, even when upstream declares them astype: library - Auto-Update: Works for packages without fixed version (always installs latest)
- Pruning: Compatible with
pluginsPruneandthemesPrune
By default, only wpackagist.org is configured, which mirrors all WordPress.org plugins and themes.
Add custom repositories for private/premium packages:
wordpress:
composer:
repositories:
- type: "vcs"
url: "https://github.com/mycompany/private-plugin"
- type: "composer"
url: "https://my-satis-server.com"
- type: "package"
package:
name: "vendor/premium-plugin"
version: "1.0.0"
dist:
url: "https://example.com/premium-plugin.zip"
type: "zip"
plugins:
- name: "mycompany/private-plugin"
activate: trueFull multisite configuration with automatic site creation, network-wide plugin/theme management, and per-site user roles. See samples/multisite.values.yaml and samples/multisite.secrets.yaml.
kubectl apply -f ./samples/multisite.secrets.yaml
helm install wordpress oci://ghcr.io/slybase/charts/wordpress --values ./samples/multisite.values.yamlImportant: Do not add multisite constants (
WP_ALLOW_MULTISITE,MULTISITE,SUBDOMAIN_INSTALL,DOMAIN_CURRENT_SITE,PATH_CURRENT_SITE,SITE_ID_CURRENT_SITE,BLOG_ID_CURRENT_SITE) toconfigExtra,configExtraConfigMap, orconfigExtraSecret. These constants are automatically written towp-config.phpby the init container during multisite setup and persist on the PVC. Defining them again would cause PHP "Constant already defined" warnings.
In multisite mode, activate and sites work independently:
| Property | Effect |
|---|---|
activate: true |
Activate on the main site |
sites: [blog, shop] |
Activate on specific sub-sites |
activate: true + sites: [blog] |
Activate on main + blog |
networkActivate: true |
Activate across entire network (overrides activate and sites) |
Example:
wordpress:
plugins:
# Network-wide: available on ALL sites
- name: "wordpress-seo"
activate: true
networkActivate: true
# Sub-sites only: NOT on main site
- name: "contact-form-7"
activate: false
sites:
- blog
- shop
# Main site + specific sub-sites
- name: "woocommerce"
activate: true
sites:
- shopThe same pattern applies to themes with networkEnable (make theme available to all sites) and activate/sites (control which site uses it as active theme).
When installing plugins or themes from a URL, the slug (directory name after installation) cannot always be determined from the URL. Use the slug property to explicitly specify it:
wordpress:
plugins:
- name: "https://example.com/downloads/my-premium-plugin-v2.3.zip"
slug: "my-premium-plugin" # Required: actual plugin directory name
activate: true
autoupdate: true
sites:
- blog
themes:
- name: "https://creativethemes.com/downloads/blocksy-child.zip"
slug: "blocksy-child" # Required: actual theme directory name
activate: true
autoupdate: trueWithout slug, URL plugins/themes use basename of the URL as a best-effort guess, but this fails for URLs like download?id=123 or versioned filenames like plugin-v2.3.1.zip. Setting slug explicitly enables:
- Skip-if-installed detection (no unnecessary reinstalls)
- Auto-updates
- Site-specific activation (
sites[]) - Network activation (
networkActivate/networkEnable)
- Complete Memcached object-cache support for WordPress, including runtime bootstrap of
memcache/memcachedPHP extensions (no custom image required). WP_CACHEis now injected automatically (guarded) when Memcached config is enabled.- New
WP_CACHE_KEY_SALThandling for stable cache isolation: explicit value, existing Secret reference, or auto-generated persisted value. - New
memcached.serverGroupsallows cache-group-specific Memcached backends, with fallback to the embedded Memcached service. - Improved init safety in drop-in mode (
object-cache.php) to avoid plugin activation/redeclare conflicts and stale drop-in bootstrap failures. - Fixed blog title update in init (
blogname) so title changes are now applied reliably. - Added Redis as alternative caching option using cloudpirates-redis chart and redis-cache plugin.
- Added Valkey (Redis fork) as alternative caching option using cloudpirates-valkey chart and redis-cache plugin.
- Automatically injects
WP_REDIS_HOST,WP_REDIS_PORT, and optionalWP_REDIS_PASSWORDinto wp-config.php. - Uses Predis library (pure PHP) - no PHP extensions or init containers required.
- Redis, Valkey, and Memcached can be used as mutually exclusive caching backends.
- Introducing multisite for WordPress, including users, plugins, and themes
- Split init pipeline into three containers:
fix-permissions(runs as root),base, andinit— enables correct ownership handling on RWX storage (e.g., Longhorn).⚠️ fix-permissionsrequires root permissions. - Moved the init script library into a separate ConfigMap and added ConfigMap checksum annotations to trigger automatic rollouts on script/config changes. If the ConfigMap is managed outside the Helm release (e.g. applied via Kustomize), you may need to manually restart the deployment to run the init containers again:
kubectl rollout restart deployment/wordpress-wordpress - Improved multi-pod init safety with heartbeat-based distributed locking, a bootstrap
helm_lockstable, and automatic stale-lock detection (60s without heartbeat). - Replaced
wp-cliwith direct database queries for much faster initialization (up to ~200% speed improvement in many scenarios).
- WordPress version from 6.8.3 to 6.9.0
- WordPress image tag from PHP version 8.1 to 8.3 (default)
- mariadb from 12.0.2 to 12.1.2
This major release introduces new possibilities to use composer plugins and themes and muPlugins. Now it is possible to activate a prune mode for plugins and themes. This will uninstall all plugins/themes that are not listed in the values.
Also adds more flexible user customization with init scripts. The init script is now a huge set to pre-configure and set up WordPress. For more, see the changelog.