Skip to content

phpMyAdmin

Deploy phpMyAdmin on Kubernetes with the official docker.io/phpmyadmin/phpmyadmin:5.2.3 image. The chart supports development installs, internal database administration portals, and production-oriented deployments with optional External Secrets Operator integration, Gateway API, dual-stack Service fields, NetworkPolicy ingress and egress, shared sessions, HPA, PDB, and custom phpMyAdmin configuration.

Protect phpMyAdmin behind strong controls

phpMyAdmin is an administrative interface for database access. Do not expose it directly to the public internet. Put it behind VPN, SSO or reverse-proxy authentication, strict IP allowlists, TLS, and least-privilege database accounts.

Key Features

  • Official image alignment — uses docker.io/phpmyadmin/phpmyadmin:5.2.3.
  • Database target modes — single-server, multi-server dropdown, and arbitrary-server login.
  • Auth modescookie, config, http, and signon, with non-cookie modes applied through generated config.user.inc.php.
  • Secret options — inline Secret for development, existing Kubernetes Secret, or optional External Secrets Operator with external-secrets.io/v1.
  • Routing — Ingress and native Kubernetes Gateway API HTTPRoute.
  • Networking — optional Service dual-stack fields and NetworkPolicy ingress/egress rules.
  • Availability — stateless replicas with optional HPA, PDB, topology spread, and shared sessions.
  • Customization — upload limit, generated/custom phpMyAdmin config, MySQL client TLS, control user, custom themes, and extra volumes/manifests.

Architecture

Production Access

phpMyAdmin should sit behind an authenticated ingress path and connect only to approved MySQL or MariaDB endpoints.

Admin userVPN / SSO / allowlistIngress / GatewayTLS terminates hereHTTPRoute or IngressphpMyAdmin podsDeployment + Serviceoptional HPA / PDBMySQL3306/sessionsemptyDir or PVCNetworkPolicyingress + DB egress

Secrets And Generated Config

Credentials can come from an existing Secret or External Secrets Operator; settings not supported by the image environment contract are written into config.user.inc.php.

Secret backendVault / cloud / GitOpsExternalSecretexternal-secrets.io/v1optionalSecretauth + blowfishphpMyAdmin podHELMFORGE_BLOWFISH_SECRETgenerated config.user.inc.phpexisting Secretalternative pathno ESO required

Installation

HTTPS repository:

helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install phpmyadmin helmforge/phpmyadmin \
  --set phpmyadmin.host=mysql.default.svc.cluster.local

OCI registry:

helm install phpmyadmin oci://ghcr.io/helmforgedev/helm/phpmyadmin \
  --set phpmyadmin.host=mysql.default.svc.cluster.local

For local access without Ingress or Gateway API:

kubectl port-forward svc/phpmyadmin 8080:80

Development vs Production

The default values are intentionally lightweight: one replica, no public route, no NetworkPolicy, no ExternalSecret, and no resource requests. This is appropriate for local validation and temporary administration tasks.

Production deployments should opt in to the controls they need:

  • Use auth.existingSecret or externalSecrets.auth instead of inline passwords.
  • Keep phpmyadmin.authType: cookie unless config, http, or signon is intentionally required.
  • Put access behind VPN, SSO, reverse-proxy auth, or IP allowlists.
  • Terminate TLS at Ingress or Gateway API.
  • Enable NetworkPolicy and restrict database egress.
  • Use least-privilege database accounts, not root/admin accounts.
  • Enable sessions.enabled when running multiple replicas with cookie/session-heavy workflows.
  • Add resource requests, memory limits, PDB, HPA, topology spread, and anti-affinity as needed.

Deployment Examples

# values.yaml - single fixed MySQL/MariaDB endpoint
phpmyadmin:
  host: mysql.database.svc.cluster.local
  port: 3306
  uploadLimit: '128M'
# values.yaml - curated server dropdown
phpmyadmin:
  hosts: 'mysql-primary.database.svc,mysql-replica.database.svc,mariadb.database.svc'
  ports: '3306,3306,3306'
  verboses: 'Primary,Replica,MariaDB'
  ssl:
    enabled: true
    verify: true

When phpmyadmin.hosts and phpmyadmin.ssl.enabled=true are combined, the chart emits the official multi-host variables such as PMA_SSLS and PMA_SSL_VERIFIES so TLS settings follow the PMA_HOSTS order.

# values.yaml - internal production-oriented portal
phpmyadmin:
  host: mysql-primary.production.svc.cluster.local
  port: 3306
  uploadLimit: '256M'
  absoluteUri: 'https://pma.example.com/'
  authType: cookie
  hidePhpVersion: true

auth:
  existingSecret: phpmyadmin-auth
  existingSecretUsernameKey: username
  existingSecretPasswordKey: password
  existingSecretBlowfishKey: blowfish-secret

replicaCount: 2

sessions:
  enabled: true
  type: persistentVolumeClaim
  accessModes:
    - ReadWriteMany
  size: 1Gi

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    memory: 512Mi

serviceAccount:
  create: true
  automountServiceAccountToken: false

podSecurityContext:
  seccompProfile:
    type: RuntimeDefault

securityContext:
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL

pdb:
  enabled: true
  minAvailable: 1

networkPolicy:
  enabled: true
  ingress:
    allowSameNamespace: false
    extraFrom:
      - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: ingress-nginx
  egress:
    enabled: true
    allowDNS: true
    allowSameNamespaceDatabase: true
    databasePort: 3306

ingress:
  enabled: true
  ingressClassName: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/proxy-body-size: '256m'
  hosts:
    - host: pma.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: phpmyadmin-tls
      hosts:
        - pma.example.com
# values.yaml - Gateway API and External Secrets Operator
phpmyadmin:
  host: mysql-primary.production.svc.cluster.local
  absoluteUri: 'https://pma.example.com/'
  authType: cookie

gatewayAPI:
  enabled: true
  parentRefs:
    - name: public
      namespace: gateway-system
      sectionName: https
  hostnames:
    - pma.example.com

externalSecrets:
  enabled: true
  secretStoreRef:
    name: platform-secrets
    kind: ClusterSecretStore
  auth:
    enabled: true
    usernameRemoteRef:
      key: prod/phpmyadmin
      property: username
    passwordRemoteRef:
      key: prod/phpmyadmin
      property: password
    blowfishSecretRemoteRef:
      key: prod/phpmyadmin
      property: blowfish-secret
External Secrets is optional

The chart does not install External Secrets Operator and does not create a SecretStore or ClusterSecretStore. Enable this path only when the operator and store already exist.

# values.yaml - restricted traffic and dual-stack Service fields
phpmyadmin:
  host: mysql.default.svc.cluster.local

service:
  ipFamilyPolicy: PreferDualStack
  ipFamilies:
    - IPv4
    - IPv6

networkPolicy:
  enabled: true
  ingress:
    allowSameNamespace: true
  egress:
    enabled: true
    allowDNS: true
    allowSameNamespaceDatabase: true
    databasePort: 3306

Dual-stack values are omitted by default. Set them only on clusters configured for IPv4/IPv6 Services.

# values.yaml - generated and custom phpMyAdmin config
phpmyadmin:
  host: mysql.database.svc.cluster.local
  authType: http
  uploadLimit: '256M'

auth:
  blowfishSecret: 'use-a-32-byte-random-secret-here'

config:
  customConfig: |
    <?php
    $cfg['ShowPhpInfo'] = false;
    $cfg['MaxRows'] = 100;

authType: http is applied through generated config.user.inc.php. The chart also switches the default probes to TCP checks because the application root returns 401 before HTTP auth succeeds.

Database Connectivity

Single Server

Use phpmyadmin.host and phpmyadmin.port to lock the login form to one MySQL or MariaDB endpoint.

phpmyadmin:
  host: mysql.default.svc.cluster.local
  port: 3306

Multi-Server

Use comma-separated hosts, ports, and verboses to present a server dropdown. Leave phpmyadmin.host empty in this mode.

phpmyadmin:
  hosts: 'mysql-prod.svc,mysql-stage.svc'
  ports: '3306,3307'
  verboses: 'Production,Staging'

Arbitrary Server

phpmyadmin.arbitrary=true lets users enter a database host at login time. Use it only when the user population is trusted and the NetworkPolicy/database network path limits what can be reached.

phpmyadmin:
  arbitrary: true

Authentication And Secrets

Default cookie auth shows the phpMyAdmin login screen and lets users authenticate directly against the configured database target.

phpmyadmin:
  authType: cookie
auth:
  blowfishSecret: 'use-a-32-byte-random-secret-here'

The chart supports auto-login through auth.username and auth.password, but that bypasses the phpMyAdmin login form. Use it only behind another authentication layer.

auth:
  existingSecret: phpmyadmin-auth
  existingSecretUsernameKey: username
  existingSecretPasswordKey: password
  existingSecretBlowfishKey: blowfish-secret

auth.blowfishSecret, auth.existingSecret, and externalSecrets.auth all feed HELMFORGE_BLOWFISH_SECRET. The generated config.user.inc.php writes the value to $cfg['blowfish_secret'], which gives stable cookie encryption across pod restarts and replicas.

Custom Configuration

Use config.customConfig to append phpMyAdmin PHP settings to generated config.user.inc.php.

config:
  customConfig: |
    <?php
    $cfg['LoginCookieValidity'] = 14400;

Use config.existingConfigMap when another delivery mechanism owns config.user.inc.php. Use phpmyadmin.configBase64 for the official image’s PMA_CONFIG_BASE64 path when you need to replace the main config file instead of appending user config.

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 all resources.
replicaCount integer 1 Number of phpMyAdmin replicas.
image.repository string docker.io/phpmyadmin/phpmyadmin phpMyAdmin image repository.
image.tag string "5.2.3" Image tag.
image.pullPolicy string IfNotPresent Image pull policy.
imagePullSecrets array [] Pull secrets for private registries.

phpMyAdmin

Parameter Type Default Description
phpmyadmin.host string "" Single MySQL or MariaDB host.
phpmyadmin.hosts string "" Comma-separated multi-server hosts.
phpmyadmin.verboses string "" Comma-separated display names for PMA_HOSTS.
phpmyadmin.port integer 3306 Single-server database port.
phpmyadmin.ports string "" Comma-separated ports for PMA_HOSTS.
phpmyadmin.arbitrary boolean false Let users enter arbitrary database hosts.
phpmyadmin.uploadLimit string "64M" Maximum SQL import upload size.
phpmyadmin.absoluteUri string "" External absolute URL when behind a proxy.
phpmyadmin.hidePhpVersion boolean true Hide PHP version headers.
phpmyadmin.configBase64 string "" Base64-encoded config for PMA_CONFIG_BASE64.
phpmyadmin.authType string cookie phpMyAdmin auth mode: cookie, config, http, or signon.
phpmyadmin.controlHost string "" Optional control host for configuration storage.
phpmyadmin.controlPort integer 3306 Control host port.
phpmyadmin.controlUser string "" Control user for configuration storage.
phpmyadmin.controlPassword string "" Control password. Prefer Secret-backed values.
phpmyadmin.ssl.enabled boolean false Enable MySQL client TLS variables.
phpmyadmin.ssl.verify boolean true Verify database TLS certificates.
phpmyadmin.ssl.caPath string "" CA path mounted in the container.
phpmyadmin.ssl.certPath string "" Client certificate path.
phpmyadmin.ssl.keyPath string "" Client private key path.
phpmyadmin.extraEnv array [] Additional container environment variables.

Authentication

Parameter Type Default Description
auth.username string "" MySQL username for auto-login.
auth.password string "" MySQL password for auto-login.
auth.blowfishSecret string "" Cookie blowfish secret.
auth.existingSecret string "" Existing Secret containing auth material.
auth.existingSecretUsernameKey string username Username key in the Secret.
auth.existingSecretPasswordKey string password Password key in the Secret.
auth.existingSecretBlowfishKey string blowfish-secret Blowfish secret key in the Secret.
auth.existingSecretControlPasswordKey string control-password Control password key in the Secret.

Config, Sessions, And Themes

Parameter Type Default Description
config.customConfig string "" Raw content appended to config.user.inc.php.
config.existingConfigMap string "" Existing ConfigMap with config.user.inc.php.
sessions.enabled boolean false Mount /sessions.
sessions.type string emptyDir Session volume type: emptyDir or PVC.
sessions.existingClaim string "" Existing PVC for sessions.
sessions.storageClass string "" StorageClass for generated session PVC.
sessions.accessModes array ["ReadWriteOnce"] Session PVC access modes.
sessions.size string 1Gi Session PVC size.
themes.enabled boolean false Mount custom themes into /www/themes.
themes.volume object {} Volume source for custom themes.

Service, Ingress, And Gateway API

Parameter Type Default Description
service.type string ClusterIP Service type.
service.port integer 80 Service port.
service.annotations object {} Service annotations.
service.ipFamilyPolicy string "" Service IP family policy.
service.ipFamilies array [] Service IP families.
ingress.enabled boolean false Create Ingress.
ingress.ingressClassName string "" Ingress class name.
ingress.annotations object {} Ingress annotations.
ingress.hosts array [] Host/path rules.
ingress.tls array [] TLS entries.
gatewayAPI.enabled boolean false Create Gateway API HTTPRoute.
gatewayAPI.apiVersion string gateway.networking.k8s.io/v1 HTTPRoute API version.
gatewayAPI.annotations object {} HTTPRoute annotations.
gatewayAPI.parentRefs array [] Parent Gateway references. Required when enabled.
gatewayAPI.hostnames array [] HTTPRoute hostnames.
gatewayAPI.matches array path prefix / HTTPRoute match rules.

External Secrets Operator

Parameter Type Default Description
externalSecrets.enabled boolean false Render ExternalSecret resources.
externalSecrets.apiVersion string external-secrets.io/v1 ExternalSecret API version.
externalSecrets.refreshInterval string 1h Refresh interval.
externalSecrets.secretStoreRef.name string "" Existing SecretStore or ClusterSecretStore name.
externalSecrets.secretStoreRef.kind string SecretStore Store kind.
externalSecrets.target.creationPolicy string Owner Target Secret creation policy.
externalSecrets.auth.enabled boolean false Manage auth Secret through ESO.
externalSecrets.auth.targetName string "" Target Secret name.
externalSecrets.auth.usernameRemoteRef object {} Remote reference for username.
externalSecrets.auth.passwordRemoteRef object {} Remote reference for password.
externalSecrets.auth.blowfishSecretRemoteRef object {} Remote reference for blowfish secret.
externalSecrets.auth.controlPasswordRemoteRef object {} Remote reference for control password.

NetworkPolicy

Parameter Type Default Description
networkPolicy.enabled boolean false Create a NetworkPolicy.
networkPolicy.ingress.allowSameNamespace boolean true Allow ingress from the same namespace.
networkPolicy.ingress.extraFrom array [] Additional ingress peers.
networkPolicy.egress.enabled boolean false Add egress rules.
networkPolicy.egress.allowDNS boolean true Allow DNS egress.
networkPolicy.egress.allowSameNamespaceDatabase boolean true Allow DB egress in the same namespace.
networkPolicy.egress.databasePort integer 3306 Database egress port.
networkPolicy.egress.allowHTTPS boolean false Allow HTTPS egress for external dependencies.
networkPolicy.egress.extraTo array [] Additional egress destinations.

Availability, Probes, And Scheduling

Parameter Type Default Description
autoscaling.enabled boolean false Create HorizontalPodAutoscaler.
autoscaling.minReplicas integer 1 Minimum HPA replicas.
autoscaling.maxReplicas integer 3 Maximum HPA replicas.
autoscaling.targetCPUUtilizationPercentage integer 80 CPU utilization target.
autoscaling.targetMemoryUtilizationPercentage string "" Optional memory utilization target.
pdb.enabled boolean false Create PodDisruptionBudget.
pdb.minAvailable integer 1 Minimum available pods.
pdb.maxUnavailable string "" Maximum unavailable pods.
startupProbe.enabled boolean true Enable startup probe.
livenessProbe.enabled boolean true Enable liveness probe.
readinessProbe.enabled boolean true Enable readiness probe.
serviceAccount.create boolean false Create a ServiceAccount.
serviceAccount.name string "" ServiceAccount name override.
serviceAccount.annotations object {} ServiceAccount annotations.
serviceAccount.automountServiceAccountToken boolean false Mount the ServiceAccount token.
resources object {} Container requests and limits.
podSecurityContext object {} Pod-level security context.
securityContext object {} Container-level security context.
nodeSelector object {} Node selector.
tolerations array [] Tolerations.
affinity object {} Affinity.
topologySpreadConstraints array [] Topology spread constraints.
priorityClassName string "" PriorityClass name.
terminationGracePeriodSeconds integer 30 Pod termination grace period.
podLabels object {} Additional pod labels.
podAnnotations object {} Additional pod annotations.

Extension Points

Parameter Type Default Description
extraVolumeMounts array [] Extra volume mounts.
extraVolumes array [] Extra pod volumes.
extraManifests array [] Extra Kubernetes manifests to render.

Operational Notes

  • Chart.yaml does not define chart dependencies and the chart does not require Chart.lock.
  • Defaults are development-friendly, not production-ready by themselves.
  • Ingress and Gateway API resources expose only the phpMyAdmin HTTP service.
  • Gateway API requires Gateway API CRDs and an existing Gateway controller.
  • External Secrets requires an existing External Secrets Operator and SecretStore or ClusterSecretStore.
  • Dual-stack Service values require a dual-stack Kubernetes cluster.
  • authType: http uses TCP probes by default because HTTP probes receive 401.
  • networkPolicy.ingress.allowSameNamespace=false with no extraFrom denies ingress instead of opening it.

Validation

Recommended local checks before promotion:

helm lint charts/phpmyadmin
helm unittest charts/phpmyadmin
helm template phpmyadmin charts/phpmyadmin -f charts/phpmyadmin/examples/production.yaml

For cluster validation, install into K3D or K3S with a test MySQL/MariaDB target and check:

  • pod readiness and restarts;
  • container logs;
  • namespace events;
  • Service endpoints;
  • Ingress or HTTPRoute status when routing is enabled;
  • ExternalSecret reconciliation when ESO is enabled.

Official References