Skip to content

Pimcore

Production-oriented deployment of Pimcore, the PHP/Symfony platform for PIM, MDM, DAM, CDP, DXP/CMS, and digital commerce.

This chart targets Pimcore 2026.2.5 and pins the official multi-architecture PHP 8.5 runtime pimcore/pimcore:php8.5.9-max-v5.2-hardened. It models the upstream topology instead of treating the runtime image as a complete application:

  • nginx and PHP-FPM in one web pod with independent health checks;
  • Symfony Messenger workers with the official Pimcore transport set;
  • pimcore:maintenance CronJob;
  • HelmForge MariaDB, RabbitMQ, and optional Redis dependencies;
  • Mercure for Pimcore Studio real-time updates;
  • project and public-asset persistence;
  • explicit installation and product-registration workflow;
  • Ingress, Gateway API, dual-stack Service, NetworkPolicy, PDB, and ESO.

Important application-image boundary

The official pimcore/pimcore image is a PHP runtime. It does not contain a Pimcore project. A production deployment must build project code, locked Composer dependencies, configuration, and generated classes into an immutable image based on that runtime.

For evaluation and first installation, the default bootstrap downloads the exact pimcore/skeleton:2026.2.0 Composer project into a persistent project volume. This mode is intentionally single-replica and requires outbound HTTPS. It is not a replacement for an immutable production image.

Pimcore 2026 requires product registration before installation or use. The chart never invents or reuses registration data. Installation runs only when install.enabled=true and a matching product key, instance identifier, and encryption secret are supplied.

Install

helm install pimcore oci://ghcr.io/helmforgedev/helm/pimcore \
  --namespace pimcore \
  --create-namespace

Wait for the runtime:

kubectl wait -n pimcore \
  --for=condition=available deployment/pimcore \
  --timeout=300s
kubectl port-forward -n pimcore service/pimcore 8080:80
curl -fsS http://127.0.0.1:8080/healthz
curl -fsS http://127.0.0.1:8080/readyz

The two endpoints verify nginx and PHP-FPM. The Pimcore UI is available only after the registered installer completes.

Registered installation

Create a Secret containing one matching registration set:

apiVersion: v1
kind: Secret
metadata:
  name: pimcore-registration
type: Opaque
stringData:
  application-secret: replace-with-a-long-random-value
  admin-password: replace-with-a-strong-password
  product-key: replace-with-the-registered-product-key
  instance-identifier: replace-with-the-registered-uuid
  encryption-secret: replace-with-the-matching-defuse-key
  mercure-jwt-key: replace-with-a-long-random-value

Run the installer:

helm upgrade pimcore oci://ghcr.io/helmforgedev/helm/pimcore \
  --namespace pimcore \
  --reuse-values \
  --set auth.existingSecret=pimcore-registration \
  --set install.enabled=true

Inspect the hook Job and disable the one-time installer after success:

kubectl logs -n pimcore job/pimcore-install
helm upgrade pimcore oci://ghcr.io/helmforgedev/helm/pimcore \
  --namespace pimcore \
  --reuse-values \
  --set install.enabled=false \
  --set worker.enabled=true \
  --set maintenance.enabled=true

Production image

Build a project image outside Kubernetes:

FROM pimcore/pimcore:php8.5.9-max-v5.2-hardened

WORKDIR /var/www/html
COPY --chown=www-data:www-data . .
RUN composer install \
      --no-dev \
      --no-interaction \
      --no-progress \
      --prefer-dist \
      --optimize-autoloader \
  && test -f bin/console

Deploy it with bootstrap and project persistence disabled:

image:
  repository: registry.example.com/platform/pimcore-project
  tag: '2026.2.5-1'

project:
  bootstrap:
    enabled: false
  persistence:
    enabled: false

auth:
  existingSecret: pimcore-registration

worker:
  enabled: true

maintenance:
  enabled: true

With project persistence disabled, nginx, PHP-FPM, workers, and maintenance use the project code directly from the immutable image. Only public assets remain mounted from their dedicated persistent volume.

Database

MariaDB is enabled by default with the upstream-required utf8mb4 character set and utf8mb4_unicode_520_ci collation:

mariadb:
  standalone:
    persistence:
      size: 20Gi

Use a managed MariaDB service:

mariadb:
  enabled: false

database:
  mode: external
  serverVersion: mariadb-11.4.7
  external:
    host: mariadb.database.svc
    name: pimcore
    username: pimcore
    existingSecret: pimcore-database
    existingSecretPasswordKey: password

The chart constructs DATABASE_URL at process start so passwords remain Secret-backed and do not appear in rendered manifests. Set database.serverVersion to the exact Doctrine-compatible external engine version; the default matches the bundled MariaDB 12.3.2 dependency.

RabbitMQ and workers

The default RabbitMQ dependency uses quorum queues and persistent storage. Workers consume the transports recommended by the current Pimcore skeleton:

  • pimcore_generic_execution_engine
  • pimcore_generic_data_index_queue
  • scheduler_generic_data_index
  • pimcore_core
  • pimcore_maintenance
  • pimcore_scheduled_tasks
  • pimcore_image_optimize
  • pimcore_asset_update

Use an external broker:

rabbitmq:
  enabled: false

queue:
  mode: external
  external:
    host: rabbitmq.messaging.svc
    username: pimcore
    vhost: pimcore
    existingSecret: pimcore-rabbitmq

The chart constructs the AMQP DSN in the container. URL-encode a custom vhost.

Redis

The upstream skeleton starts Redis but does not activate a Redis-backed Symfony cache or session handler. The chart therefore keeps Redis optional and exposes connection values only when requested:

cache:
  enabled: true

redis:
  enabled: true

Your project configuration must consume REDIS_HOST, REDIS_PORT, REDIS_PASSWORD, and REDIS_TLS.

Mercure

Mercure is enabled for Pimcore Studio. The internal hub is exposed through the main nginx Service at /hub, avoiding a separate public origin. Anonymous subscriptions match the skeleton development topology and should be disabled after the project configures subscriber authorization:

mercure:
  anonymous: false

Persistence and scaling

Bootstrap mode uses a project PVC. Production immutable-image mode should disable it. Public assets have a separate PVC:

assets:
  persistence:
    enabled: true
    storageClass: nfs-rwx
    accessModes:
      - ReadWriteMany
    size: 100Gi

The chart rejects multiple web replicas with a chart-created RWO project or asset PVC. For HA:

  1. use an immutable project image;
  2. disable project persistence and bootstrap;
  3. provide RWX assets or configure project-specific object storage;
  4. use external HA MariaDB, RabbitMQ, and Redis services;
  5. configure pod topology and a PDB.

OpenSearch

Generic Data Index installations can require OpenSearch. It is deliberately not bundled because supported versions and credentials belong to the application project:

pimcore:
  opensearchDSN: opensearch://user:[email protected]:9200

Prefer pimcore.extraEnv plus a Secret reference when the DSN contains credentials.

External Secrets

The chart implements the canonical externalSecrets.items[] contract:

auth:
  existingSecret: pimcore-registration

externalSecrets:
  enabled: true
  items:
    - name: registration
      spec:
        secretStoreRef:
          name: production
          kind: ClusterSecretStore
        target:
          name: pimcore-registration
          creationPolicy: Owner
        dataFrom:
          - extract:
              key: pimcore/production

The target Secret must contain every key configured under auth.

Routing

Ingress:

ingress:
  enabled: true
  ingressClassName: nginx
  hosts:
    - host: pimcore.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: pimcore-tls
      hosts:
        - pimcore.example.com

Gateway API:

gatewayAPI:
  enabled: true
  httpRoutes:
    - name: public
      parentRefs:
        - name: public-gateway
          namespace: gateways
      hostnames:
        - pimcore.example.com

Network policy

NetworkPolicy is opt-in. When egress enforcement is enabled, the chart permits DNS only to the configured cluster DNS namespace/pod selectors, plus chart-managed MariaDB, RabbitMQ, Redis, and Mercure traffic. Bootstrap also requires outbound HTTPS:

networkPolicy:
  enabled: true
  egress:
    enabled: true
    extraEgress:
      - ports:
          - protocol: TCP
            port: 443

Production deployments should restrict that rule to known Composer or service CIDRs, or remove it when using an immutable project image.

Backup and restore

A complete recovery point includes:

  • MariaDB;
  • public assets or the external object store;
  • the immutable application image and its configuration;
  • registration Secret values;
  • RabbitMQ only when durable in-flight jobs must survive the recovery point.

Do not back up cache directories as authoritative data. Test restore and pimcore:deployment:classes-rebuild procedures against the application project.

Security Scan

Security Scan: pimcore

Framework Score
MITRE + NSA + SOC2 89.43%

Security posture acceptable.

Local details:

  • Tool: Kubescape v4.0.9
  • Command: kubescape scan framework mitre,nsa,soc2 .tmp/pimcore-render.yaml
  • Result: 0 critical and 0 high failed resources, resource summary score 89.43%.

Runtime images are official, exact, multi-architecture tags. The default PHP image is the upstream hardened variant. Containers drop all Linux capabilities, disable privilege escalation, run as non-root users, and do not automount ServiceAccount tokens. nginx and the Helm test use read-only root filesystems. PHP workloads retain a writable root because upstream Pimcore and Composer write project/runtime paths without a complete relocation contract; immutable project images should minimize those paths and keep application data on explicit volumes.

Validation

Run the full HelmForge gate:

make validate-chart CHART=pimcore

This covers dependencies, strict lint, default and CI rendering, unit tests, kubeconform with real CRD schemas, Artifact Hub lint, and behavioral k3d deployment.

Configuration reference

Naming

Value Default Description
nameOverride "" Override the chart name used by resource names.
fullnameOverride "" Override the complete release resource prefix.
namespaceOverride "" Render namespaced resources in another namespace.
clusterDomain cluster.local Kubernetes DNS suffix.
commonLabels {} Labels added to every chart-owned resource.

Application and nginx images

Value Default Description
image.repository docker.io/pimcore/pimcore Runtime or custom project image repository.
image.tag php8.5.9-max-v5.2-hardened Exact official hardened runtime tag.
image.pullPolicy IfNotPresent Application image pull policy.
imagePullSecrets [] Registry pull Secrets.
waitForDependencies.image.repository docker.io/library/busybox Dependency-check image repository.
waitForDependencies.image.tag 1.37.0 Exact dependency-check image tag.
nginx.image.repository docker.io/library/nginx Official nginx repository.
nginx.image.tag 1.30.4-alpine3.24 Exact nginx tag.
nginx.clientMaxBodySize 100m Maximum HTTP request body.
nginx.resources See values nginx requests and limits.

Project lifecycle

Value Default Description
project.runtimeImage.repository docker.io/pimcore/pimcore Immutable project base-image repository.
project.runtimeImage.tag php8.5.9-max-v5.2-hardened Immutable project base-image tag.
project.bootstrap.enabled true Bootstrap the pinned skeleton when the image has no project.
project.bootstrap.skeletonVersion 2026.2.0 Exact skeleton release.
project.persistence.enabled true Persist bootstrapped project code.
project.persistence.storageClass "" Project PVC storage class.
project.persistence.accessModes [ReadWriteOnce] Project PVC access modes.
project.persistence.size 4Gi Project PVC request.
project.persistence.existingClaim "" Reuse an existing project claim.
project.persistence.annotations {} Project PVC annotations.

Web and workers

Value Default Description
web.replicaCount 1 nginx/PHP-FPM web pod replicas.
web.resources See values PHP-FPM requests and limits.
worker.enabled false Enable installed-project Messenger consumers.
worker.replicaCount 1 Worker replicas.
worker.transports Eight official queues Messenger transports consumed by each worker.
worker.memoryLimit 250M Symfony Messenger recycle memory threshold.
worker.timeLimit 3600 Symfony Messenger recycle interval.
worker.resources See values Worker requests and limits.

Maintenance and installation

Value Default Description
maintenance.enabled false Enable pimcore:maintenance.
maintenance.schedule */5 * * * * Maintenance Cron schedule.
maintenance.concurrencyPolicy Forbid Prevent overlapping maintenance jobs.
maintenance.successfulJobsHistoryLimit 1 Successful Job retention.
maintenance.failedJobsHistoryLimit 3 Failed Job retention.
install.enabled false Run the one-time registered installer hook.
install.profile App\Installer\SkeletonProfile Installer profile class.
install.hookDeletePolicy before-hook-creation Helm hook cleanup policy.
install.backoffLimit 1 Installer retry limit.
install.activeDeadlineSeconds 1800 Installer hard timeout.

Pimcore environment

Value Default Description
pimcore.environment prod Symfony environment.
pimcore.debug false Symfony debug flag.
pimcore.trustedProxies 127.0.0.1,REMOTE_ADDR Symfony trusted proxies.
pimcore.trustedHosts "" Trusted-host regular expression.
pimcore.mercureURL "" Public Mercure URL; empty derives the chart route.
pimcore.opensearchDSN "" Optional Generic Data Index OpenSearch DSN.
pimcore.extraEnv [] Additional environment variables.
pimcore.extraEnvFrom [] Additional Secret/ConfigMap environment sources.

Registration and application Secret

Value Default Description
auth.existingSecret "" Existing complete application Secret.
auth.applicationSecretKey application-secret Application Secret key name.
auth.adminPasswordKey admin-password Administrator password key name.
auth.productKeyKey product-key Product key key name.
auth.instanceIdentifierKey instance-identifier Instance UUID key name.
auth.encryptionSecretKey encryption-secret Defuse key key name.
auth.mercureJWTKey mercure-jwt-key Mercure JWT key name.
auth.applicationSecret "" Inline application secret; generated when empty.
auth.adminUser admin Initial administrator username.
auth.adminPassword "" Inline password; generated when empty.
auth.productKey "" Registered product key.
auth.instanceIdentifier "" Registered instance UUID.
auth.encryptionSecret "" Matching encryption secret.
auth.mercureJWT "" Inline Mercure key; generated when empty.

Database

Value Default Description
database.mode mariadb mariadb or external.
database.serverVersion mariadb-12.3.2 Doctrine-compatible engine version.
database.external.host "" External MariaDB host.
database.external.port 3306 External MariaDB port.
database.external.name pimcore Database name.
database.external.username pimcore Database user.
database.external.password "" Inline external password.
database.external.existingSecret "" External password Secret.
mariadb.enabled true Deploy HelmForge MariaDB.
mariadb.standalone.persistence.size 8Gi MariaDB volume request.

Queue and cache

Value Default Description
queue.mode rabbitmq rabbitmq or external.
queue.external.host "" External RabbitMQ host.
queue.external.port 5672 External AMQP port.
queue.external.username pimcore External broker user.
queue.external.vhost %2f URL-encoded broker vhost.
queue.external.existingSecret "" Broker password Secret.
rabbitmq.enabled true Deploy HelmForge RabbitMQ.
rabbitmq.singleNode.persistence.size 4Gi Broker storage request.
cache.enabled false Expose Redis connection values.
cache.mode redis redis or external.
cache.external.host "" External Redis host.
cache.external.port 6379 External Redis port.
cache.external.tls false Indicate TLS to project configuration.
redis.enabled false Deploy HelmForge Redis.

Mercure and assets

Value Default Description
mercure.enabled true Deploy the internal Mercure hub.
mercure.image.tag v0.24.2 Exact official Mercure tag.
mercure.replicaCount 1 Fixed at one for the bundled local transport.
mercure.anonymous true Allow anonymous subscribers.
assets.persistence.enabled true Persist public assets.
assets.persistence.accessModes [ReadWriteOnce] Asset PVC access modes.
assets.persistence.size 20Gi Asset PVC request.
assets.persistence.existingClaim "" Reuse an existing asset claim.

Routing, policy, and scheduling

Value Default Description
service.type ClusterIP Kubernetes Service type.
service.port 80 Public Service port.
service.ipFamilyPolicy "" Optional dual-stack policy.
service.ipFamilies [] Optional IP families.
ingress.enabled false Create an Ingress.
ingress.ingressClassName "" Ingress class.
gatewayAPI.enabled false Create HTTPRoutes.
externalSecrets.enabled false Create ExternalSecret resources.
externalSecrets.items [] Canonical ExternalSecret definitions.
networkPolicy.enabled false Create workload policy.
networkPolicy.egress.enabled false Enforce egress rules.
networkPolicy.egress.dns.namespaceSelector kube-system label selector Cluster DNS namespace selector.
networkPolicy.egress.dns.podSelector k8s-app: kube-dns Cluster DNS pod selector.
networkPolicy.egress.extraEgress [] Complete extra egress rules.
podDisruptionBudget.enabled true Create PDBs for scaled workloads.
serviceAccount.automountServiceAccountToken false Disable API credentials.
nodeSelector {} Node selection constraints.
tolerations [] Pod tolerations.
affinity {} Pod affinity rules.
topologySpreadConstraints [] Pod distribution rules.

Troubleshooting guide

The web pod remains in Init:0/2

Inspect prepare-project. Bootstrap downloads more than 200 locked Composer packages and needs outbound HTTPS, writable project storage, and sufficient time. Production images avoid this dependency.

The installer rejects the product key

The product key is cryptographically tied to the instance identifier and encryption secret. Supply the exact three values generated by one registration flow. Do not generate or rotate only one member of the set.

/healthz works but the UI returns 404

This is expected before database installation. /healthz proves nginx and /readyz proves PHP-FPM; neither claims that Pimcore is registered or installed.

PHP-FPM starts but reports database errors

Verify the MariaDB Service, Secret key, database name, username, and collation. Inspect the in-container environment without printing secret values, then test TCP connectivity to port 3306.

Workers crash immediately

Workers require a fully installed database and an application image containing the same project code as web. Keep worker.enabled=false during initial registration and installation.

Jobs accumulate in RabbitMQ

Confirm the eight configured transports match the project, inspect worker logs, and verify that workers can read the broker Secret. Scale workers based on queue depth and task cost.

Pimcore Studio does not receive real-time updates

Check the Mercure pod, /hub nginx proxy, and JWT key consistency. Disable anonymous subscriptions only after project authorization is configured.

Multiple web replicas are rejected

Disable bootstrap/project persistence and use an immutable application image. Provide RWX assets or external object storage. The guard prevents unsafe RWO mount assumptions.

Assets disappear after a rollout

Verify assets.persistence.enabled, the bound PVC, mount events, and storage access mode. Do not store authoritative assets only in pod-local storage.

Redis is healthy but unused

That is expected unless the project configures Symfony cache/session providers. The chart supplies connection variables but does not rewrite application configuration.

Generic Data Index fails

Configure a compatible OpenSearch endpoint and credentials for the installed bundle version. OpenSearch is deliberately external to this chart.

NetworkPolicy blocks bootstrap

Add explicit HTTPS egress for Composer or deploy an immutable image and remove runtime package downloads. Keep the rule as narrow as the environment allows.

Upgrades time out

Inspect the installer/migration Job before rolling web and workers. Back up MariaDB and assets, run schema transitions once, and never allow all replicas to race migrations.

Additional resources