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 user VPN / SSO / allowlist Ingress / Gateway TLS terminates here HTTPRoute or Ingress phpMyAdmin pods Deployment + Service optional HPA / PDB MySQL 3306 /sessions emptyDir or PVC NetworkPolicy ingress + 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 backend Vault / cloud / GitOps ExternalSecret external-secrets.io/v1 optional Secret auth + blowfish phpMyAdmin pod HELMFORGE_BLOWFISH_SECRET generated config.user.inc.php existing Secret alternative path no 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

ParameterTypeDefaultDescription
nameOverridestring""Override chart name.
fullnameOverridestring""Override full release name.
commonLabelsobject{}Labels added to all resources.
replicaCountinteger1Number of phpMyAdmin replicas.
image.repositorystringdocker.io/phpmyadmin/phpmyadminphpMyAdmin image repository.
image.tagstring"5.2.3"Image tag.
image.pullPolicystringIfNotPresentImage pull policy.
imagePullSecretsarray[]Pull secrets for private registries.

phpMyAdmin

ParameterTypeDefaultDescription
phpmyadmin.hoststring""Single MySQL or MariaDB host.
phpmyadmin.hostsstring""Comma-separated multi-server hosts.
phpmyadmin.verbosesstring""Comma-separated display names for PMA_HOSTS.
phpmyadmin.portinteger3306Single-server database port.
phpmyadmin.portsstring""Comma-separated ports for PMA_HOSTS.
phpmyadmin.arbitrarybooleanfalseLet users enter arbitrary database hosts.
phpmyadmin.uploadLimitstring"64M"Maximum SQL import upload size.
phpmyadmin.absoluteUristring""External absolute URL when behind a proxy.
phpmyadmin.hidePhpVersionbooleantrueHide PHP version headers.
phpmyadmin.configBase64string""Base64-encoded config for PMA_CONFIG_BASE64.
phpmyadmin.authTypestringcookiephpMyAdmin auth mode: cookie, config, http, or signon.
phpmyadmin.controlHoststring""Optional control host for configuration storage.
phpmyadmin.controlPortinteger3306Control host port.
phpmyadmin.controlUserstring""Control user for configuration storage.
phpmyadmin.controlPasswordstring""Control password. Prefer Secret-backed values.
phpmyadmin.ssl.enabledbooleanfalseEnable MySQL client TLS variables.
phpmyadmin.ssl.verifybooleantrueVerify database TLS certificates.
phpmyadmin.ssl.caPathstring""CA path mounted in the container.
phpmyadmin.ssl.certPathstring""Client certificate path.
phpmyadmin.ssl.keyPathstring""Client private key path.
phpmyadmin.extraEnvarray[]Additional container environment variables.

Authentication

ParameterTypeDefaultDescription
auth.usernamestring""MySQL username for auto-login.
auth.passwordstring""MySQL password for auto-login.
auth.blowfishSecretstring""Cookie blowfish secret.
auth.existingSecretstring""Existing Secret containing auth material.
auth.existingSecretUsernameKeystringusernameUsername key in the Secret.
auth.existingSecretPasswordKeystringpasswordPassword key in the Secret.
auth.existingSecretBlowfishKeystringblowfish-secretBlowfish secret key in the Secret.
auth.existingSecretControlPasswordKeystringcontrol-passwordControl password key in the Secret.

Config, Sessions, And Themes

ParameterTypeDefaultDescription
config.customConfigstring""Raw content appended to config.user.inc.php.
config.existingConfigMapstring""Existing ConfigMap with config.user.inc.php.
sessions.enabledbooleanfalseMount /sessions.
sessions.typestringemptyDirSession volume type: emptyDir or PVC.
sessions.existingClaimstring""Existing PVC for sessions.
sessions.storageClassstring""StorageClass for generated session PVC.
sessions.accessModesarray["ReadWriteOnce"]Session PVC access modes.
sessions.sizestring1GiSession PVC size.
themes.enabledbooleanfalseMount custom themes into /www/themes.
themes.volumeobject{}Volume source for custom themes.

Service, Ingress, And Gateway API

ParameterTypeDefaultDescription
service.typestringClusterIPService type.
service.portinteger80Service port.
service.annotationsobject{}Service annotations.
service.ipFamilyPolicystring""Service IP family policy.
service.ipFamiliesarray[]Service IP families.
ingress.enabledbooleanfalseCreate Ingress.
ingress.ingressClassNamestring""Ingress class name.
ingress.annotationsobject{}Ingress annotations.
ingress.hostsarray[]Host/path rules.
ingress.tlsarray[]TLS entries.
gatewayAPI.enabledbooleanfalseCreate Gateway API HTTPRoute.
gatewayAPI.apiVersionstringgateway.networking.k8s.io/v1HTTPRoute API version.
gatewayAPI.annotationsobject{}HTTPRoute annotations.
gatewayAPI.parentRefsarray[]Parent Gateway references. Required when enabled.
gatewayAPI.hostnamesarray[]HTTPRoute hostnames.
gatewayAPI.matchesarraypath prefix /HTTPRoute match rules.

External Secrets Operator

ParameterTypeDefaultDescription
externalSecrets.enabledbooleanfalseRender ExternalSecret resources.
externalSecrets.apiVersionstringexternal-secrets.io/v1ExternalSecret API version.
externalSecrets.refreshIntervalstring1hRefresh interval.
externalSecrets.secretStoreRef.namestring""Existing SecretStore or ClusterSecretStore name.
externalSecrets.secretStoreRef.kindstringSecretStoreStore kind.
externalSecrets.target.creationPolicystringOwnerTarget Secret creation policy.
externalSecrets.auth.enabledbooleanfalseManage auth Secret through ESO.
externalSecrets.auth.targetNamestring""Target Secret name.
externalSecrets.auth.usernameRemoteRefobject{}Remote reference for username.
externalSecrets.auth.passwordRemoteRefobject{}Remote reference for password.
externalSecrets.auth.blowfishSecretRemoteRefobject{}Remote reference for blowfish secret.
externalSecrets.auth.controlPasswordRemoteRefobject{}Remote reference for control password.

NetworkPolicy

ParameterTypeDefaultDescription
networkPolicy.enabledbooleanfalseCreate a NetworkPolicy.
networkPolicy.ingress.allowSameNamespacebooleantrueAllow ingress from the same namespace.
networkPolicy.ingress.extraFromarray[]Additional ingress peers.
networkPolicy.egress.enabledbooleanfalseAdd egress rules.
networkPolicy.egress.allowDNSbooleantrueAllow DNS egress.
networkPolicy.egress.allowSameNamespaceDatabasebooleantrueAllow DB egress in the same namespace.
networkPolicy.egress.databasePortinteger3306Database egress port.
networkPolicy.egress.allowHTTPSbooleanfalseAllow HTTPS egress for external dependencies.
networkPolicy.egress.extraToarray[]Additional egress destinations.

Availability, Probes, And Scheduling

ParameterTypeDefaultDescription
autoscaling.enabledbooleanfalseCreate HorizontalPodAutoscaler.
autoscaling.minReplicasinteger1Minimum HPA replicas.
autoscaling.maxReplicasinteger3Maximum HPA replicas.
autoscaling.targetCPUUtilizationPercentageinteger80CPU utilization target.
autoscaling.targetMemoryUtilizationPercentagestring""Optional memory utilization target.
pdb.enabledbooleanfalseCreate PodDisruptionBudget.
pdb.minAvailableinteger1Minimum available pods.
pdb.maxUnavailablestring""Maximum unavailable pods.
startupProbe.enabledbooleantrueEnable startup probe.
livenessProbe.enabledbooleantrueEnable liveness probe.
readinessProbe.enabledbooleantrueEnable readiness probe.
serviceAccount.createbooleanfalseCreate a ServiceAccount.
serviceAccount.namestring""ServiceAccount name override.
serviceAccount.annotationsobject{}ServiceAccount annotations.
serviceAccount.automountServiceAccountTokenbooleanfalseMount the ServiceAccount token.
resourcesobject{}Container requests and limits.
podSecurityContextobject{}Pod-level security context.
securityContextobject{}Container-level security context.
nodeSelectorobject{}Node selector.
tolerationsarray[]Tolerations.
affinityobject{}Affinity.
topologySpreadConstraintsarray[]Topology spread constraints.
priorityClassNamestring""PriorityClass name.
terminationGracePeriodSecondsinteger30Pod termination grace period.
podLabelsobject{}Additional pod labels.
podAnnotationsobject{}Additional pod annotations.

Extension Points

ParameterTypeDefaultDescription
extraVolumeMountsarray[]Extra volume mounts.
extraVolumesarray[]Extra pod volumes.
extraManifestsarray[]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