Drupal
Deploy Drupal on Kubernetes using the Docker Official drupal image with a persistence model tailored for the installer-managed sites/ directory, plus native backup automation and safe scaling guardrails.
Why This Chart Matters
- Reference CMS chart — built to be HelmForge’s production baseline for installer-driven PHP CMS workloads
- Seeded
sites/persistence — preserves uploads and installer-generated state without masking Drupal core files from the image - Built-in backup automation — archives Drupal
sites/plus either MySQL or SQLite to S3-compatible storage - Safe horizontal scaling — HPA is supported only when the storage and database combination is actually safe
- Installer-first workflow — the chart prepares runtime, storage, ingress, and database connectivity, then guides setup through
NOTES.txt
Image Decision
This chart uses docker.io/library/drupal because Drupal does not currently publish its own upstream-maintained runtime container image.
HelmForge pins:
docker.io/library/drupal:11.3.11-php8.5-apache-bookworm
That choice is intentional:
- the Apache variant already includes PHP
- the image includes the Drupal-relevant extensions such as
pdo_mysql,pdo_sqlite,sqlite3,gd,mbstring,curl,dom,openssl, andzip - Drupal 11.3 supports PHP 8.5, so the explicit PHP 8.5 tag is a better production pin than the older generic
apache-bookwormform bookwormkeeps the Debian base explicit and conservative for production environments
Official Product
Release Notes
This chart tracks Drupal 11.3.11, an upstream Drupal 11 patch and bugfix
release. The bundled HelmForge MySQL dependency is aligned to the current
2.0.0 chart while preserving the same Drupal installer values for the default
bundled database path.
Installation
HTTPS Repository
helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install drupal helmforge/drupal
OCI Registry
helm install drupal oci://ghcr.io/helmforgedev/helm/drupal
Install Example
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: '1'
memory: 1Gi
mysql:
enabled: true
auth:
database: drupal
username: drupal
persistence:
enabled: true
size: 8Gi
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: drupal.example.com
paths:
- path: /
pathType: Prefix
After installation:
- Wait for Drupal and MySQL to become ready.
- Open the configured URL or port-forwarded service.
- Run the Drupal web installer.
- Use the database details printed by
helm get notes <release>.
Database Modes
Bundled MySQL
database:
mode: auto
mysql:
enabled: true
External Database
database:
mode: external
external:
host: db.example.com
port: 3306
name: drupal
username: drupal
mysql:
enabled: false
SQLite
database:
mode: sqlite
mysql:
enabled: false
For SQLite installs, use sites/default/files/.ht.sqlite in the Drupal installer unless you intentionally choose another writable path.
SQLite is supported and backed up by the chart, but it remains a single-replica path and is not the preferred default for most production Drupal sites.
Persistence
This chart persists only /var/www/html/sites.
That layout is intentional:
settings.php, uploaded files, and installer output live undersites/- Drupal core remains in the container image instead of being shadowed by a PVC
- an init container seeds the initial
sites/directory into the mounted volume before Drupal starts
Single-replica deployments are the default recommendation. If you scale above one replica, use shared writable storage for sites/.
Backup Strategy
When backup.enabled=true, the chart creates a CronJob that always captures two things:
- the Drupal
sites/tree - the active database
Database backup behavior depends on the selected mode:
- MySQL or external MySQL-compatible database —
mysqldump - SQLite — a consistent SQLite snapshot created through Python’s SQLite backup API before compression
Backup Example
backup:
enabled: true
schedule: '0 2 * * *'
s3:
endpoint: https://minio.example.com
bucket: drupal-backups
existingSecret: drupal-backup-s3
If database.mode=external, also provide backup credentials for the dump job:
backup:
database:
existingSecret: drupal-backup-db
Autoscaling And HPA
HPA is supported, but only when the chart can guarantee safe shared state.
Required for multi-replica or HPA Drupal:
persistence.enabled=truepersistence.accessMode=ReadWriteMany- a MySQL-compatible database mode
If those requirements are not met, the chart fails during render instead of letting an unsafe deployment reach the cluster.
HPA Example
persistence:
accessMode: ReadWriteMany
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 5
pdb:
enabled: true
minAvailable: 1
Values
The chart still has an installer-oriented surface, but it now covers production runtime, storage, backups, scaling, networking, probes, and escape hatches.
Runtime And Installer Values
| Key | Default | Description |
|---|---|---|
replicaCount | 1 | Number of Drupal replicas |
image.repository | docker.io/library/drupal | Docker Official Drupal image repository |
image.tag | 11.3.11-php8.5-apache-bookworm | Pinned Drupal image tag |
image.pullPolicy | IfNotPresent | Container pull policy |
imagePullSecrets | [] | Optional image pull secrets |
drupal.siteName | Drupal | Suggested site name shown in install guidance |
drupal.installProfile | standard | Suggested Drupal install profile |
drupal.locale | en | Suggested locale for the installer |
drupal.sqlitePath | sites/default/files/.ht.sqlite | Suggested SQLite path for the web installer |
drupal.extraEnv | [] | Extra container environment variables |
php.ini | "" | Extra PHP configuration mounted as custom.ini |
Database And Persistence Values
| Key | Default | Description |
|---|---|---|
database.mode | auto | Database mode: auto, external, mysql, or sqlite |
database.external.host | "" | External database hostname |
database.external.port | 3306 | External database port |
database.external.name | drupal | External database name |
database.external.username | drupal | External database username |
mysql.enabled | true | Enable the bundled MySQL subchart |
mysql.architecture | standalone | MySQL deployment architecture |
mysql.auth.database | drupal | Database name created by the MySQL subchart |
mysql.auth.username | drupal | Database username created by the MySQL subchart |
mysql.auth.password | "" | Optional application password override |
mysql.auth.rootPassword | "" | Optional root password override |
mysql.auth.existingSecret | "" | Existing secret for MySQL passwords |
mysql.auth.existingSecretUserPasswordKey | mysql-user-password | Secret key used for the app user password |
mysql.standalone.persistence.enabled | true | Enable bundled standalone MySQL persistence |
mysql.standalone.persistence.size | 8Gi | Bundled standalone MySQL PVC size |
persistence.enabled | true | Enable persistence for /var/www/html/sites |
persistence.storageClass | "" | Explicit storage class for the sites PVC |
persistence.accessMode | ReadWriteOnce | Access mode for the sites PVC |
persistence.size | 8Gi | Sites PVC size |
persistence.existingClaim | "" | Reuse an existing sites PVC |
persistence.annotations | {} | Annotations applied to the sites PVC |
Backup, Scaling, And Availability Values
| Key | Default | Description |
|---|---|---|
backup.enabled | false | Enable scheduled backups |
backup.schedule | 0 3 * * * | Cron schedule for backups |
backup.s3.endpoint | "" | S3-compatible endpoint |
backup.s3.bucket | "" | S3 target bucket |
backup.s3.prefix | drupal | Key prefix in the bucket |
backup.s3.existingSecret | "" | Existing secret for S3 credentials |
backup.database.host | "" | Optional database host override for backup |
backup.database.password | "" | Inline database password override for backup |
backup.database.existingSecret | "" | Existing secret for backup database password |
autoscaling.enabled | false | Enable HPA |
autoscaling.minReplicas | 2 | HPA minimum replicas |
autoscaling.maxReplicas | 5 | HPA maximum replicas |
autoscaling.targetCPUUtilizationPercentage | 75 | CPU utilization target |
autoscaling.targetMemoryUtilizationPercentage | 80 | Memory utilization target |
pdb.enabled | false | Create a PodDisruptionBudget |
pdb.minAvailable | 1 | Minimum healthy pods during voluntary disruption |
Service, Ingress, Probes, And Platform Values
| Key | Default | Description |
|---|---|---|
service.type | ClusterIP | Drupal service type |
service.port | 80 | Drupal service port |
service.annotations | {} | Annotations applied to the service |
service.ipFamilyPolicy | omitted | Service IP family policy: SingleStack, PreferDualStack, or RequireDualStack. Omit for cluster default. |
service.ipFamilies | omitted | Ordered list of IP families (IPv4, IPv6). Omit for cluster default. |
ingress.enabled | false | Enable ingress resources |
ingress.ingressClassName | "" | Ingress class name |
ingress.annotations | {} | Ingress annotations |
ingress.hosts | [] | Ingress host and path definitions |
ingress.tls | [] | TLS secret mappings for ingress |
startupProbe.enabled | true | Enable the startup probe |
startupProbe.path | / | Startup probe HTTP path |
livenessProbe.enabled | true | Enable the liveness probe |
livenessProbe.path | / | Liveness probe HTTP path |
readinessProbe.enabled | true | Enable the readiness probe |
readinessProbe.path | / | Readiness probe HTTP path |
resources.requests.cpu | 250m | Default CPU request for the Drupal container |
resources.requests.memory | 512Mi | Default memory request for the Drupal container |
resources.limits.cpu | 1 | Default CPU limit for the Drupal container |
resources.limits.memory | 1Gi | Default memory limit for the Drupal container |
podSecurityContext.fsGroup | 33 | Group ownership applied to the mounted Drupal files volume |
securityContext | {} | Container-level security context |
serviceAccount.create | false | Create a dedicated service account |
serviceAccount.name | "" | Existing or custom service account name |
serviceAccount.annotations | {} | Service account annotations |
nodeSelector | {} | Node selector rules |
tolerations | [] | Pod tolerations |
affinity | {} | Pod affinity and anti-affinity |
topologySpreadConstraints | [] | Topology spread constraints |
priorityClassName | "" | Priority class for the pod |
terminationGracePeriodSeconds | 30 | Pod termination grace period |
podLabels | {} | Extra pod labels |
podAnnotations | {} | Extra pod annotations |
extraVolumes | [] | Additional volumes injected into the pod |
extraVolumeMounts | [] | Additional volume mounts injected into the Drupal container |
extraManifests | [] | Extra Kubernetes manifests rendered by the chart |
Dual-stack Networking
The Drupal Service accepts Kubernetes dual-stack configuration. By default, both service.ipFamilyPolicy and service.ipFamilies are unset and the chart inherits whatever the cluster advertises (matching prior behavior).
service:
ipFamilyPolicy: PreferDualStack
ipFamilies:
- IPv4
- IPv6
PreferDualStack is the safer choice for clusters that may be single- or dual-stack: omit ipFamilies and the cluster auto-populates whatever it supports. Set ipFamilies explicitly only when the cluster is configured for both families — the Kubernetes API rejects an explicit family the cluster does not advertise. RequireDualStack enforces both.