Deploy with Helm
Deploy ModPageSpeed 2.0 on Kubernetes with the official Helm chart.
On this page
The official Helm chart deploys ModPageSpeed 2.0 as a sidecar pod: the nginx
interceptor and worker run as two containers in the same pod, sharing
an emptyDir volume for the Cyclone cache. This page covers installation,
configuration, scaling, and upgrades.
Prerequisites
- Kubernetes 1.28+
- Helm 3.12+
- A running origin server accessible from the cluster
The chart pulls the public ghcr.io/we-amp/pagespeed-worker and
ghcr.io/we-amp/pagespeed-nginx images directly — no registry credentials
required.
Architecture
Each pod contains two containers:
+-----------------------------------------+
| Pod |
| +----------------+ +---------------+ |
| | nginx | | worker | |
| | (port 80) | | | |
| +-------+--------+ +-------+-------+ |
| | | |
| +--- /data (emptyDir) ---+ |
| | cache.vol | |
| | pagespeed.sock | |
| | pagespeed.sock.health | |
| | pagespeed.sock.mgmt | |
| +-------------------------+ |
+-----------------------------------------+
- nginx serves HTTP traffic on port 80. On cache miss, it proxies to your origin and sends a notification to the worker via Unix socket.
- worker reads original content from the shared cache, optimizes it, and writes variants back. It creates the cache file and Unix sockets at startup.
- emptyDir is the shared volume mounted at
/datain both containers. Its lifetime is tied to the pod — cache is rebuilt after pod restarts.
Installation
The chart is published to a Helm repository hosted on modpagespeed.com. Add it
once — there is no repository to clone.
helm repo add weamp https://modpagespeed.com/charts
helm repo update
Quick Start
helm install pagespeed weamp/pagespeed \
--set backend.host=my-origin.default.svc.cluster.local \
--set backend.port=8080
Replace my-origin.default.svc.cluster.local with the Kubernetes service name
(or IP) of your origin server.
With a Custom Values File
Create a my-values.yaml:
replicaCount: 2
backend:
host: my-origin.default.svc.cluster.local
port: 8080
worker:
cacheSize: '1073741824' # 1 GB
cache:
sizeLimit: 2Gi
ingress:
enabled: true
className: nginx
hosts:
- host: cdn.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: cdn-tls
hosts:
- cdn.example.com
Install with:
helm install pagespeed weamp/pagespeed -f my-values.yaml
Configuration Reference
Backend (Origin Server)
| Value | Default | Description |
|---|---|---|
backend.host | 127.0.0.1 | Origin server hostname or IP |
backend.port | 8081 | Origin server port |
Worker
| Value | Default | Description |
|---|---|---|
worker.image.repository | ghcr.io/we-amp/pagespeed-worker | Worker container image |
worker.image.tag | 2.0.21 | Image tag |
worker.cacheSize | 1073741824 (1 GB) | Cyclone cache size in bytes |
worker.extraArgs | [] | Additional CLI flags for worker |
Pass additional worker flags via extraArgs:
worker:
extraArgs:
- --jpeg-quality
- '80'
- --log-level
- debug
- --no-proactive-savedata-variants
Nginx
| Value | Default | Description |
|---|---|---|
nginx.image.repository | ghcr.io/we-amp/pagespeed-nginx | Nginx container image |
nginx.image.tag | 2.0.21 | Image tag |
License Key
| Value | Default | Description |
|---|---|---|
licenseKey | "" | License token (stored in a Secret) |
existingSecret | "" | Name of a pre-existing Secret |
existingSecretKey | license-key | Key within the existing Secret |
Set the license key inline:
helm install pagespeed weamp/pagespeed \
--set licenseKey=your-token-here \
--set backend.host=my-origin
Or reference a secret you manage yourself:
kubectl create secret generic my-license --from-literal=license-key=your-token-here
helm install pagespeed weamp/pagespeed \
--set existingSecret=my-license \
--set backend.host=my-origin
The license key is optional for evaluation; production use requires a commercial
license — but the software never locks you out. Without it, both containers
start and the worker keeps optimizing responses,
adding an X-PageSpeed-Warn: unlicensed header and logging a warning at
startup. See the License Activation guide for
details.
Licensing is per site (registrable domain), not per pod or replica. Every pod
serving the same site shares one license, so replicaCount and autoscaling do
not change what you pay. See pricing.
Cache Volume
| Value | Default | Description |
|---|---|---|
cache.sizeLimit | 1Gi | emptyDir size limit |
The sizeLimit caps the disk space the emptyDir volume can consume. Set this
higher than worker.cacheSize to account for Unix socket files and any
temporary data. A good rule: set sizeLimit to at least 1.5x worker.cacheSize.
Service and Ingress
| Value | Default | Description |
|---|---|---|
service.type | ClusterIP | Kubernetes Service type |
service.port | 80 | Service port |
ingress.enabled | false | Create an Ingress object |
ingress.className | "" | Ingress class name |
Autoscaling
| Value | Default | Description |
|---|---|---|
autoscaling.enabled | false | Enable HPA |
autoscaling.minReplicas | 1 | Minimum pod count |
autoscaling.maxReplicas | 10 | Maximum pod count |
autoscaling.targetCPUUtilizationPercentage | 70 | CPU target for scaling |
Resource Recommendations
The worker is CPU-intensive (image encoding) and benefits from memory for the decode/encode pipeline. Nginx is lightweight.
Small Deployment (< 500 images)
worker:
resources:
requests: { cpu: 250m, memory: 256Mi }
limits: { cpu: '1', memory: 512Mi }
cacheSize: '268435456' # 256 MB
nginx:
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { cpu: 500m, memory: 256Mi }
Medium Deployment (500-5000 images)
worker:
resources:
requests: { cpu: 500m, memory: 512Mi }
limits: { cpu: '2', memory: 1Gi }
cacheSize: '1073741824' # 1 GB
nginx:
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { cpu: '1', memory: 512Mi }
Large Deployment (5000+ images)
worker:
resources:
requests: { cpu: '1', memory: 1Gi }
limits: { cpu: '4', memory: 2Gi }
cacheSize: '4294967296' # 4 GB
nginx:
resources:
requests: { cpu: 250m, memory: 256Mi }
limits: { cpu: '1', memory: 512Mi }
cache:
sizeLimit: 6Gi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
Upgrading
Refresh the repository first to pick up newer chart versions, then upgrade:
helm repo update
helm upgrade pagespeed weamp/pagespeed -f my-values.yaml
Or update just the image tags:
helm upgrade pagespeed weamp/pagespeed \
--reuse-values \
--set worker.image.tag=2.0.29 \
--set nginx.image.tag=2.0.29
The emptyDir cache is lost on pod restart. After an upgrade, the cache rebuilds as requests arrive — the first few responses will be cache misses while the worker re-optimizes content. This is expected and resolves within minutes under normal traffic.
Uninstalling
helm uninstall pagespeed
This removes all Kubernetes resources created by the chart (Deployment, Service, Ingress, HPA, ConfigMap, Secret). The emptyDir volumes are deleted with the pods.
Verifying the Deployment
After installation, check that both containers are running:
kubectl get pods -l app.kubernetes.io/name=pagespeed
Both containers in the pod should show READY:
NAME READY STATUS RESTARTS AGE
pagespeed-6d4b8f9c7-x2k9m 2/2 Running 0 45s
Port-forward to test locally:
kubectl port-forward svc/pagespeed 8080:80
curl -I http://localhost:8080/
# Look for: X-PageSpeed: MISS (first request)
Next Steps
- Configuration Reference — All worker flags and nginx directives
- License Activation — Activate your license key
- Deployment Guide — Docker Compose, systemd, and production operations
- Troubleshooting — Diagnose common issues