Skip to content

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.8-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, and zip
  • Drupal 11.3 supports PHP 8.5, so the explicit PHP 8.5 tag is a better production pin than the older generic apache-bookworm form
  • bookworm keeps the Debian base explicit and conservative for production environments

Official Product

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:

  1. Wait for Drupal and MySQL to become ready.
  2. Open the configured URL or port-forwarded service.
  3. Run the Drupal web installer.
  4. 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 under sites/
  • 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 databasemysqldump
  • 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=true
  • persistence.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

KeyDefaultDescription
replicaCount1Number of Drupal replicas
image.repositorydocker.io/library/drupalDocker Official Drupal image repository
image.tag11.3.8-php8.5-apache-bookwormPinned Drupal image tag
image.pullPolicyIfNotPresentContainer pull policy
imagePullSecrets[]Optional image pull secrets
drupal.siteNameDrupalSuggested site name shown in install guidance
drupal.installProfilestandardSuggested Drupal install profile
drupal.localeenSuggested locale for the installer
drupal.sqlitePathsites/default/files/.ht.sqliteSuggested 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

KeyDefaultDescription
database.modeautoDatabase mode: auto, external, mysql, or sqlite
database.external.host""External database hostname
database.external.port3306External database port
database.external.namedrupalExternal database name
database.external.usernamedrupalExternal database username
mysql.enabledtrueEnable the bundled MySQL subchart
mysql.architecturestandaloneMySQL deployment architecture
mysql.auth.databasedrupalDatabase name created by the MySQL subchart
mysql.auth.usernamedrupalDatabase 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.existingSecretUserPasswordKeymysql-user-passwordSecret key used for the app user password
mysql.primary.persistence.enabledtrueEnable MySQL persistence
mysql.primary.persistence.size8GiMySQL PVC size
persistence.enabledtrueEnable persistence for /var/www/html/sites
persistence.storageClass""Explicit storage class for the sites PVC
persistence.accessModeReadWriteOnceAccess mode for the sites PVC
persistence.size8GiSites PVC size
persistence.existingClaim""Reuse an existing sites PVC
persistence.annotations{}Annotations applied to the sites PVC

Backup, Scaling, And Availability Values

KeyDefaultDescription
backup.enabledfalseEnable scheduled backups
backup.schedule0 3 * * *Cron schedule for backups
backup.s3.endpoint""S3-compatible endpoint
backup.s3.bucket""S3 target bucket
backup.s3.prefixdrupalKey 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.enabledfalseEnable HPA
autoscaling.minReplicas2HPA minimum replicas
autoscaling.maxReplicas5HPA maximum replicas
autoscaling.targetCPUUtilizationPercentage75CPU utilization target
autoscaling.targetMemoryUtilizationPercentage80Memory utilization target
pdb.enabledfalseCreate a PodDisruptionBudget
pdb.minAvailable1Minimum healthy pods during voluntary disruption

Service, Ingress, Probes, And Platform Values

KeyDefaultDescription
service.typeClusterIPDrupal service type
service.port80Drupal service port
service.annotations{}Annotations applied to the service
service.ipFamilyPolicyomittedService IP family policy: SingleStack, PreferDualStack, or RequireDualStack. Omit for cluster default.
service.ipFamiliesomittedOrdered list of IP families (IPv4, IPv6). Omit for cluster default.
ingress.enabledfalseEnable 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.enabledtrueEnable the startup probe
startupProbe.path/Startup probe HTTP path
livenessProbe.enabledtrueEnable the liveness probe
livenessProbe.path/Liveness probe HTTP path
readinessProbe.enabledtrueEnable the readiness probe
readinessProbe.path/Readiness probe HTTP path
resources.requests.cpu250mDefault CPU request for the Drupal container
resources.requests.memory512MiDefault memory request for the Drupal container
resources.limits.cpu1Default CPU limit for the Drupal container
resources.limits.memory1GiDefault memory limit for the Drupal container
podSecurityContext.fsGroup33Group ownership applied to the mounted Drupal files volume
securityContext{}Container-level security context
serviceAccount.createfalseCreate 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
terminationGracePeriodSeconds30Pod 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.

More Information