Getting Started
This section takes you from an empty Kubernetes cluster to your first running PHP application on the Zend Enterprise Web Platform. It covers prerequisites, preparing the cluster, creating the required secrets, building your application container image, assembling your configuration, and performing the first deployment followed by suggested next steps.
If a concept here is unfamiliar, the Architecture section provides background on Kubernetes, Helm, and the umbrella-chart model.
Prerequisites
Before you begin, make sure you have the following available.
A working Kubernetes environment
You need Kubernetes 1.29 or newer, or any environment compatible with the Kubernetes API for example k3s, RKE, EKS, GKE, or AKS. You will also need:
kubectlconfigured for your cluster (or a kubeconfig file you can point it at).- Cluster permissions sufficient to create namespaces, secrets, deployments, services, persistent volumes, and Custom Resource Definitions (CRDs).
A container registry
You need an OCI-compatible registry (for example Zot or docker-registry) for storing and mirroring your container images and Helm charts. From a security standpoint, the recommended practice is to mirror the ZEWP platform images and Helm charts into your own registry, and to host your custom application images there as well. See Configuring a Private Image Registry.
Helm
You need Helm 3.8 or newer. Helm 3.8 is the first version with stable support for OCI registries, which the platform relies on for distributing the chart.
Docker
You need Docker for building your PHP application images. The platform's image-build workflow uses docker build and docker buildx (see Creating PHP application containers).
Recommended but optional tools
These are not strictly required, but they make working with the platform considerably smoother.
Version control
Keep your values override files and your Chart.yaml (if you consume the chart locally) in a version control system such as Git. This gives you a clear history of what changed between chart revisions, which makes it far easier to decide what revision to roll back to if a deployment goes wrong.
Make
A Makefile lets you capture the longer docker buildx and helm upgrade command lines as named targets, so routine tasks become make build-app1 or make deploy. See Using a Makefile to Automate Deployments.
Preparing your Kubernetes environment for the platform
While the platform is designed to work across any Kubernetes environment, several things must be present in the cluster for a successful installation. Prepare these before deploying.
Set KUBECONFIG first. Export a KUBECONFIG variable pointing at your cluster configuration so you can omit the --kubeconfig <path> argument on every helm and kubectl command:
export KUBECONFIG=path/to/k8s/config.yaml
Install the Gateway API
The platform uses the Kubernetes Gateway API to expose services. While the Gateway API is a standard, it is not always pre-installed, because many clusters still use the older Ingress controllers. Install the required Custom Resource Definitions:
kubectl apply \
-f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml
Install the MySQL Operator
The MySQL deployment used by ZendHQ relies on the MySQL Kubernetes Operator. Install it with Helm:
helm repo add mysql-operator https://mysql.github.io/mysql-operator/
helm repo update
helm install mysql-operator \
mysql-operator/mysql-operator \
--namespace mysql-operator \
--create-namespace
If you intend to disable the bundled MySQL subchart and use an external database, you can skip the MySQL Operator. See MySQL in the values reference.
Provide ReadWriteMany (RWX) storage
ReadWriteMany storage is used to share volumes between the PHP web pods, the Job Queue pods, and the web server. When in doubt, use the storage driver native to your environment (you may still need to install it):
| Environment | Recommended driver |
|---|---|
| Azure | azurefile-csi
|
| Amazon | aws-ebs-csi-driver
|
| GCP | filestore-csi-driver
|
| k3s / k3d | Longhorn |
RWX storage is required if you run more than one replica of an application or enable autoscaling. On a single-replica, single-node cluster you can use ReadWriteOnce storage instead. See Shared filesystems.
Create the namespace
Pre-create the namespace(s) you will deploy into. Although the deployment command can create the namespace for you, creating it in advance lets you set up secrets and volumes before deploying:
kubectl create namespace <namespace>
Make your namespace the default context so you can drop the -n <namespace> argument from subsequent commands:
kubectl config set-context --current --namespace=<namespace>
Preparing secrets for the platform and your application
The platform expects a small number of secrets to exist before deployment, and you will likely add your own for your applications. ZEWP uses two secrets by default: one providing the MySQL credentials used by ZendHQ, and one providing the ZendHQ admin credentials and license. You can combine them if you prefer; the important thing is to note the secret name used for each.
Create secrets after creating the namespace but before deploying the chart, so the pods can consume them on first start.
Create the MySQL secret
kubectl -n <namespace> create secret generic mysql-secret \
--from-literal=mysql-username=username_you_will_use \
--from-literal=mysql-password=associated_password \
--from-literal=mysql-root-password=root_password_for_instance
This creates a secret named mysql-secret with three key/value pairs. Make a note of the name — you will reference it in your values.
Create the ZendHQ secret
kubectl -n <namespace> create secret generic zendhq-secret \
--from-literal=zendhq-user=admin \
--from-literal=zendhq-password=somesecretpassword \
--from-literal=zendphp-mysql-secret=mysql-secret \
--from-file=license-file=path/to/zendhq/license
This:
- Sets the default ZendHQ admin user and password.
- Sets
zendphp-mysql-secret, which tells ZEWP which secret to read the MySQL credentials from (the name of the secret you created above). - Sets
license-fileto the contents of your ZendHQ license file, read from the given path.
Validate and manage secrets
You can confirm a secret exists with:
kubectl -n <namespace> get secret <secret-name> -o yaml
Secret values are masked using base64 encoding; decode them with a base64 tool if you need to verify the actual values.
Secrets are immutable once created. Once a secret exists in the cluster it persists between chart revisions. To change it including adding keys you must delete and recreate it:
kubectl -n <namespace> delete secret <secret-name>
Pods that consume a changed secret must be restarted to pick up the new values. The preferred approach (especially with autoscaling) is a rolling restart:
kubectl -n <namespace> rollout restart deployment/<deployment-name>
For the full details of every secret the platform expects and where to reference each one, see Required Platform Secrets.
Creating PHP application containers
Your PHP application runs in a container image you build and push to your registry. ZEWP provides a customizable ZendPHP Dockerfile as your starting point, giving you a hardened, supported PHP runtime with a standardized way to add extensions, system packages, and configuration.
The ZendPHP base images are available from cr.zend.com, and the full customizable Dockerfile is documented at help.zend.com.
The ZEWP Dockerfile
Start from the platform Dockerfile. The build arguments before the FROM line determine which ZendPHP base image is used; the rest are consumed by the ZendPHPCustomizeWithBuildArgs.sh script to install packages and extensions.
Do not change the ZEND_EXTENSIONS_LIST line that appends grpc opentelemetry redis zendhq. These extensions are what integrate your application with the platform (see below). If you build from a platform ZendPHP image, they are pre-installed for you.
Extensions required for ZEWP
If you build from a standard ZendPHP base image, include these extensions in ZEND_EXTENSIONS_LIST to fully integrate with the platform:
| Extension | Purpose |
|---|---|
grpc
|
OpenTelemetry integration (PHP 8.0+) |
opentelemetry
|
OpenTelemetry integration (PHP 8.0+) |
redis
|
Session clustering and data caching |
zendhq
|
ZendHQ monitoring and Job Queue capabilities |
Generally, use the package names returned by zendphpctl ext list-packages. You can change the load order of an extension by prefixing it with a two-digit priority (40-xdebug, 90-zendhq), and you can compile a specific version by appending it as a suffix (xdebug-3.6.5).
Compilation is a last resort
Compiling extensions often requires extra SYSTEM_PACKAGES, may not be detected correctly for INI loading, and most importantly any manually compiled extension cannot be supported by Zend.
OpenTelemetry zero-code instrumentation
To get application- and library-specific trace spans without modifying your code, install the OpenTelemetry SDK packages via Composer:
composer require open-telemetry/sdk open-telemetry/otlp
A full list of OpenTelemetry SDK packages for PHP is available in the OpenTelemetry registry.
Multi-stage builds
Docker multi-stage builds let you perform complex build steps without leaving unwanted artifacts (build-time runtimes, dependency tooling) in the final image. Two common patterns:
Install production dependencies via the Composer image:
Build production assets via a Node image:
Build and push the image
To build for local use:
docker build -f Dockerfile -t <tag for image> .
To build and push a production image to your registry:
docker buildx build \
--push \
--platform linux/amd64 \
-f Dockerfile \
-t cr.example.com/apps/my-cool-app:latest \
-t cr.example.com/apps/my-cool-app:20260318 \
.
Use a unique, immutable tag (such as a date or build number) for every change, in addition to latest. This lets you set pullPolicy: IfNotPresent and have Kubernetes pull only when the tag actually changes. Make a note of the image name and tag, you'll reference them in your Helm values.
For a deeper treatment of the Dockerfile, build arguments, the /entrypoint.d/ initialization hooks, and automating builds, see Creating application images and the PHP Application values reference.
Preparing your first deployment
With your image pushed and your secrets created, you can assemble the configuration for your first deployment.
Basics of platform configuration
You configure the platform through values override files - YAML files that override the chart's defaults. The cardinal rule:
Never edit the chart's own values.yaml. Always create separate override files and pass them with -f.
A recommended convention is to prefix override files with a load order and keep one file per concern:
Helm merges multiple -f files left to right, with the last file winning on conflicts. Keeping one file per application makes it easy to add or remove an application from the overall deployment.
Configuring the platform with your first application
A ZEWP application is described in three places. Only the first two are required for a basic web application; the third is needed only if you use Job Queue workers.
1. Global / ingress configuration - tell the gateway which hostname routes to this application:
2. Application configuration - tell the platform which image to use and how to configure it:
3. Job Queue worker configuration (optional) - If your application uses Job Queue, tell the platform which image to use for the workers:
Application names must match. The identifier under zendphp-web.applications (here, my-cool-app) must match the identifier under global.phpApplications. If they differ, the gateway cannot route requests to the correct application service. The application name is also normalized and used as the ZendHQ table prefix, which is what segregates monitoring data per application.
For the complete set of per-application options shared filesystems, init containers, nginx customization, autoscaling, and more, see the PHP Application and web server values.
Your first deployment
Deploy (and re-deploy) the chart with a single helm upgrade --install command. This installs the chart if it has not been installed before, and creates a new revision if it has:
helm upgrade --install \
-n <namespace> --create-namespace \
--timeout 10m0s \
<release_name> \
oci://cr.zend.com/charts/platform/umbrella \
--version 1.2.3 \
-f 00-infra.values.yaml \
-f 01-my-cool-app.values.yaml # repeat -f as needed
This command:
- Installs the chart as the release
<release_name>if it isn't installed yet; otherwise rolls out a new revision. - Creates
<namespace>if it doesn't already exist. - Merges every
-ffile with the chart defaults (last file wins on conflicts).
If you reference the chart on a remote OCI registry (
oci://...), always supply--versionto pin the chart version. You may first need to authenticate to the registry withhelm registry login -u <username> <server:port>.
The initial install can take a few minutes, since it stands up a large number of services. Later revisions are usually faster, because Helm only rolls out the services that actually changed — typically just the affected PHP application.
Verify the deployment
Check that the release deployed successfully and that pods are healthy:
# Helm's view of the release
helm status <release_name> -n <namespace>
# Kubernetes' view of the pods
kubectl -n <namespace> get pods
The pod STATUS column tells you what's happening. Common values include ContainerCreating, Init: (running an init container), Running (healthy and started), Completed (for jobs), ImagePullBackOff (cannot reach the registry or bad tag), CrashLoopBackOff (container keeps crashing), and Error. The READY column (e.g. 1/1, 2/2) shows how many containers in the pod are ready — remember that a web pod runs both an nginx and a PHP-FPM container.
If a pod isn't healthy, describe it to see lifecycle events (these reveal missing secrets, unreachable registries, bad image tags, and so on):
kubectl -n <namespace> describe pod <pod_name>
kubectl -n <namespace> logs <pod_name> -c <container_name>
Full operational guidance is in Helm and Kubernetes Operation and Troubleshooting.
Next steps
Once your first application is running, consider the following:
- Learn the basics of Helm and Kubernetes operation. Day-to-day tasks — upgrading, rolling back, reading logs, restarting deployments, and port-forwarding to services — are covered in Helm and Kubernetes Operation and Troubleshooting.
- Configure a private image registry. Mirror the platform images into your own OCI registry and authenticate your cluster to it. See Configuring a Private Image Registry.
- Dive into the provided OpenSearch Dashboards. The platform ships with pre-built dashboards for logs, metrics, and traces. See OpenSearch Dashboards Included with the Platform.
- Explore the full chart values reference. When you're ready to tune services, scale, or add advanced features, the complete configuration is documented in the Helm Chart Values Reference.
- Automate your workflow. Capture your build and deploy commands in a
Makefile— see Using a Makefile to Automate Deployments.