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.
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 modes —
cookie,config,http, andsignon, with non-cookie modes applied through generatedconfig.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.
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.
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.existingSecretorexternalSecrets.authinstead of inline passwords. - Keep
phpmyadmin.authType: cookieunlessconfig,http, orsignonis 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.enabledwhen 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: trueWhen 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-secretThe 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: 3306Dual-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.yamldoes not define chart dependencies and the chart does not requireChart.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: httpuses TCP probes by default because HTTP probes receive401.networkPolicy.ingress.allowSameNamespace=falsewith noextraFromdenies 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.