---
myst:
html_meta:
"description": "Deploy the production-ready Volto example: React frontend, Plone REST API backend, PostgreSQL, Varnish caching, and ingress with TLS."
"property=og:description": "Deploy the production-ready Volto example: React frontend, Plone REST API backend, PostgreSQL, Varnish caching, and ingress with TLS."
"property=og:title": "Deploy the Volto example"
"keywords": "Plone, cdk8s, Kubernetes, Volto, production, PostgreSQL, Varnish, ingress, TLS"
---
# Deploy the Volto example
This guide shows you how to deploy the production-ready Volto example to your Kubernetes cluster.
## What you'll deploy
The [Volto example](https://github.com/bluedynamics/cdk8s-plone/tree/main/examples/volto) includes:
- **Plone 6.1 with Volto** (React frontend + REST API backend)
- **PostgreSQL** with RelStorage (plain PostgreSQL or CloudNativePG)
- **Varnish HTTP caching** with kube-httpcache
- **Ingress** with TLS (Traefik or Kong)
- **Three access domains** (cached, uncached, maintenance)
(volto-prerequisites)=
## Prerequisites
### Required
Ensure you have these installed on your cluster:
1. **Ingress Controller** - Either:
- [Traefik v3](https://doc.traefik.io/traefik/getting-started/install-traefik/) with CRDs
- [Kong Gateway](https://docs.konghq.com/gateway/latest/install/kubernetes/)
2. **cert-manager** - For TLS certificates:
```shell
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.yaml
```
3. **kube-httpcache Operator** - For Varnish caching:
```shell
kubectl apply -f https://github.com/mittwald/kube-httpcache/releases/latest/download/kube-httpcache.yaml
```
4. **PostgreSQL** - The example provisions the database itself; choose the backend with the `DATABASE` variable in Step 4. Prepare the cluster for your choice:
**Option A: plain** (default; no operator needed) - the example deploys a single-instance PostgreSQL StatefulSet using the official `postgres` image, so there is nothing to install.
**Option B: CloudNativePG** (recommended for production HA) - install the operator:
```shell
kubectl apply -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.24/releases/cnpg-1.24.0.yaml
```
### Local tools
- **Node.js 18+** and npm
- **kubectl** configured for your cluster
- **git** to clone the repository
## Step 1: Get the example
Clone the repository and navigate to the example:
```shell
git clone https://github.com/bluedynamics/cdk8s-plone.git
cd cdk8s-plone/examples/volto
```
## Step 2: Install dependencies
```shell
npm install
```
## Step 3: Import CRDs
Generate TypeScript bindings for Kubernetes CRDs:
```shell
npm run import
```
This imports:
- Kubernetes core API
- CloudNativePG Cluster CRDs
- Traefik Middleware CRDs
## Step 4: Configure environment
Create a `.env` file from the example:
```shell
cp .env.example .env
```
Edit `.env` with your settings:
```shell
# Your domains
DOMAIN_CACHED=plone.example.com
DOMAIN_UNCACHED=plone-test.example.com
DOMAIN_MAINTENANCE=plone-admin.example.com
# Your cert-manager ClusterIssuer
CLUSTER_ISSUER=letsencrypt-prod
# Optional: Custom images
#PLONE_BACKEND_IMAGE=plone/plone-backend:6.1.3
#PLONE_FRONTEND_IMAGE=plone/plone-frontend:16.0.0
# Database: 'plain' or 'cloudnativepg'
DATABASE=plain
```
:::{tip}
For production high availability, use `cloudnativepg`. For development and small setups, `plain` is simpler and needs no operator.
:::
## Step 5: Generate manifests
Synthesize Kubernetes YAML:
```shell
npm run synth
```
This creates `dist/plone-example.k8s.yaml` with all resources.
## Step 6: Review generated manifests
Inspect what will be deployed:
```shell
# Count resources
grep "^kind:" dist/plone-example.k8s.yaml | sort | uniq -c
# View specific resources
kubectl apply --dry-run=client -f dist/plone-example.k8s.yaml
```
## Step 7: Deploy to Kubernetes
Deploy to your cluster:
```shell
kubectl apply -f dist/plone-example.k8s.yaml
```
Or deploy to a specific namespace:
```shell
kubectl apply -f dist/plone-example.k8s.yaml -n plone
```
## Step 8: Monitor deployment
Watch pods starting:
```shell
# Watch all pods
kubectl get pods -l app.kubernetes.io/part-of=plone -w
# Check specific components
kubectl get pods -l app.kubernetes.io/name=plone-backend
kubectl get pods -l app.kubernetes.io/name=plone-frontend
kubectl get pods -l app.kubernetes.io/name=plone-httpcache
```
Wait for all pods to be `Running`:
```shell
kubectl wait --for=condition=ready pod -l app.kubernetes.io/part-of=plone --timeout=300s
```
## Step 9: Verify services
Check that services are created:
```shell
kubectl get svc -l app.kubernetes.io/part-of=plone
```
You should see:
- `plone-backend` (backend service)
- `plone-frontend` (frontend service)
- `plone-httpcache` (Varnish cache)
- Database service (plain PostgreSQL or CloudNativePG)
## Step 10: Check ingress
Verify ingress routes:
```shell
kubectl get ingress
```
Check TLS certificates:
```shell
kubectl get certificate
```
## Step 11: Access your site
Once DNS is configured and TLS certificates are issued:
- **Public site (cached)**: https://plone.example.com
- **Testing (uncached)**: https://plone-test.example.com
- **Maintenance**: https://plone-admin.example.com
### Create Plone site
On first access to the maintenance domain:
1. Go to: https://plone-admin.example.com
2. Click "Create a new Plone site"
3. Fill in:
- **Site ID**: `Plone`
- **Title**: Your site title
- **Language**: Choose language
4. Click "Create Plone Site"
## Troubleshooting
### Pods not starting
Check pod logs:
```shell
# Backend logs
kubectl logs -l app.kubernetes.io/name=plone-backend -f
# Frontend logs
kubectl logs -l app.kubernetes.io/name=plone-frontend -f
# Database logs (CloudNativePG)
kubectl logs -l postgresql=plone-postgresql -f
```
### Database connection issues
**CloudNativePG:**
```shell
# Check cluster status
kubectl get cluster
# Check secret
kubectl get secret -l cnpg.io/cluster
```
**Plain PostgreSQL:**
```shell
# Check the StatefulSet and pod
kubectl get statefulset -l app.kubernetes.io/name=plone-postgresql
kubectl get pods -l app.kubernetes.io/name=plone-postgresql
```
### TLS certificate issues
```shell
# Check certificate status
kubectl describe certificate
# Check cert-manager logs
kubectl logs -n cert-manager deployment/cert-manager
```
### Varnish cache not working
```shell
# Check httpcache logs
kubectl logs -l app.kubernetes.io/name=plone-httpcache
# Check if kube-httpcache operator is running
kubectl get pods -n kube-httpcache-system
```
## Updating your deployment
After making changes to the example:
1. Edit configuration files
2. Regenerate manifests: `npm run synth`
3. Apply changes: `kubectl apply -f dist/plone-example.k8s.yaml`
## Scaling
Scale replicas by editing `main.ts`:
```typescript
const plone = new Plone(this, 'plone', {
backend: {
replicas: 3, // Scale backend
},
frontend: {
replicas: 3, // Scale frontend
},
})
```
Then regenerate and reapply.
## Cleanup
Remove all resources:
```shell
kubectl delete -f dist/plone-example.k8s.yaml
```
## Next steps
- Configure monitoring and metrics through {doc}`enable-prometheus-monitoring`.
- Customize [Varnish caching rules](https://github.com/bluedynamics/cdk8s-plone/blob/main/examples/volto/config/varnish.tpl.vcl).
- Harden pods with {doc}`configure-security-context`.
## See also
- {doc}`deploy-blicca` — For the server-side rendered Plone UI.
- {doc}`setup-prerequisites` — Detailed cluster setup.
- {doc}`/reference/configuration-options` — API reference.