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:

  • kubectl configured 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:

Copy
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:

Copy
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:

Copy
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:

Copy
kubectl create namespace <namespace>

Make your namespace the default context so you can drop the -n <namespace> argument from subsequent commands:

Copy
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

Copy
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

Copy
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-file to the contents of your ZendHQ license file, read from the given path.

Validate and manage secrets

You can confirm a secret exists with:

Copy
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:

Copy
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:

Copy
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.

Copy
ERROR: Invalid Code Highlighting Language

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:

Copy
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:

Copy
ERROR: Invalid Code Highlighting Language

Build production assets via a Node image:

Copy
ERROR: Invalid Code Highlighting Language

Build and push the image

To build for local use:

Copy
docker build -f Dockerfile -t <tag for image> .

To build and push a production image to your registry:

Copy
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:

Copy
ERROR: Invalid Code Highlighting Language

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:

Copy
ERROR: Invalid Code Highlighting Language

2. Application configuration - tell the platform which image to use and how to configure it:

Copy
ERROR: Invalid Code Highlighting Language

3. Job Queue worker configuration (optional) - If your application uses Job Queue, tell the platform which image to use for the workers:

Copy
ERROR: Invalid Code Highlighting Language

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:

Copy
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 -f file with the chart defaults (last file wins on conflicts).

If you reference the chart on a remote OCI registry (oci://...), always supply --version to pin the chart version. You may first need to authenticate to the registry with helm 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:

Copy
# 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):

Copy
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: