Kubernetes
Deploy deco CMS on Kubernetes using Helm
This guide explains how to deploy deco CMS on Kubernetes using the Helm chart.
Learn more: the deco CMS product page
Helm chart source: decocms/helm-chart-deco-mcp-mesh
Overview
This Helm chart encapsulates all Kubernetes resources necessary to run the application:
- Deployment: Main application with security configurations
- Service: Internal application exposure
- ConfigMap: Non-sensitive configurations
- Secret: Sensitive data (authentication)
- PersistentVolumeClaim: Persistent storage for database
- ServiceAccount: Service account for the pod
- HorizontalPodAutoscaler: Automatic autoscaling (optional)
Main Features
- ✅ Parameterizable: All configurations via
values.yaml - ✅ Reusable: Deploy to multiple environments with different values
- ✅ Flexible: Support for additional volumes, tolerations, affinity
- ✅ Observable: Health checks, standardized labels
- ✅ Scalable: Optional HPA for autoscaling
Architecture

Prerequisites
- Kubernetes 1.32+
- Helm 3.0+
kubectlconfigured to access the cluster- StorageClass configured (for PVC)
Quick Start
The simplest way to get the application up and running on k8s:
# 1. Generate a secure secret for authentication
SECRET=$(openssl rand -base64 32)
# 2. Install the chart with the generated secret
helm install deco-mcp-mesh . \
--namespace deco-mcp-mesh \
--create-namespace \
--set secret.BETTER_AUTH_SECRET="$SECRET"
# 3. Wait for pods to be ready
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io/instance=deco-mcp-mesh \
-n deco-mcp-mesh \
--timeout=300s
# 4. Access via port-forward
kubectl port-forward svc/deco-mcp-mesh 8080:80 -n deco-mcp-mesh
The application will be available at http://localhost:8080 .
Important for Production: This configuration uses SQLite and is suitable only for development/testing. For production environments, configure:
- PostgreSQL as the database engine (
database.engine: postgresql) - Autoscaling enabled (
autoscaling.enabled: true) with appropriate values - Distributed persistence (
persistence.distributed: true) or PostgreSQL to allow multiple replicas
See the Configuration section below for more details on production configuration.
Installation
Basic Installation
# Preparing necessary parameters
# Adjust values.yaml with desired configurations to run in your environment
# Install with default values
helm install deco-mcp-mesh . --namespace deco-mcp-mesh --create-namespace
# Install with custom values
helm install deco-mcp-mesh . -f my-values.yaml -n deco-mcp-mesh --create-namespace
Verify Installation
# View release status
helm status deco-mcp-mesh -n deco-mcp-mesh
# View created resources
kubectl get all -l app.kubernetes.io/instance=deco-mcp-mesh -n deco-mcp-mesh
# View logs
kubectl logs -l app.kubernetes.io/instance=deco-mcp-mesh -n deco-mcp-mesh
Using External Secrets
To keep sensitive values out of your values.yaml file (useful for GitOps workflows like ArgoCD), you can create Secrets manually and reference them in your values file.
Step 1: Create Secrets Manually
Edit examples/secrets-example.yaml with your actual values and apply it:
# Edit the file with your real values
# Then apply the Secrets
kubectl apply -f examples/secrets-example.yaml -n deco-mcp-mesh
The Secrets file contains:
- Main Secret (
deco-mcp-mesh-secrets): ContainsBETTER_AUTH_SECRETandDATABASE_URL - Auth Config Secret (
deco-mcp-mesh-auth-secrets): Contains OAuth client IDs/secrets and API keys
Step 2: Configure values.yaml to Use Secrets
secret:
# Reference the existing Secret created manually
secretName: "deco-mcp-mesh-secrets"
# Reference the authConfig Secret
authConfigSecretName: "deco-mcp-mesh-auth-secrets"
database:
engine: postgresql
# Leave url empty when using Secret - value comes from DATABASE_URL in Secret
url: ""
configMap:
authConfig:
socialProviders:
google:
# Leave empty when using Secret - values come from Secret
clientId: ""
clientSecret: ""
github:
clientId: ""
clientSecret: ""
emailProviders:
- id: "resend-primary"
provider: "resend"
config:
apiKey: "" # Leave empty when using Secret
fromEmail: "noreply@decocms.com"
Step 3: Install/Upgrade
# Install with values that reference external Secrets
helm install deco-mcp-mesh . -f values-custom.yaml -n deco-mcp-mesh --create-namespace
# Or upgrade existing release
helm upgrade deco-mcp-mesh . -f values-custom.yaml -n deco-mcp-mesh
Note: When secret.secretName is defined, the chart will use the existing Secret instead of creating a new one. Values defined in values.yaml take precedence over Secret values (for backward compatibility).
Uninstall
helm uninstall deco-mcp-mesh -n deco-mcp-mesh
Configuration
Main Values
The main configurable values are in values.yaml .
| Parameter | Description | Default |
|---|---|---|
replicaCount | Number of replicas | 3 |
image.repository | Image repository | ghcr.io/decocms/mesh/mesh |
image.tag | Image tag | latest |
service.type | Service type | ClusterIP |
persistence.enabled | Enable PVC | true |
persistence.distributed | PVC supports ReadWriteMany | true |
persistence.accessMode | PVC access mode | ReadWriteMany |
persistence.storageClass | PVC StorageClass | efs |
autoscaling.enabled | Enable HPA | false |
database.engine | Database ( sqlite / postgresql ) | sqlite |
database.url | Database URL when PostgreSQL | "" |
database.caCert | CA certificate for SSL validation (managed databases) | "" |
Customizing Values
Create a custom-values.yaml file:
replicaCount: 2
image:
tag: "v1.2.3" # Example
service:
type: LoadBalancer
port: 80
database:
engine: postgresql
url: "postgresql://mesh_user:mesh_password@mesh.example.com:5432/mesh_db"
caCert: |
-----BEGIN CERTIFICATE-----
aaaaaaaabbbbbbcccccccccddddddd
aaaaaaaabbbbbbcccccccccddddddd
-----END CERTIFICATE-----
resources:
requests:
memory: "300Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
persistence:
size: 10Gi
storageClass: "gp3"
Install with custom values:
helm install deco-mcp-mesh . -f custom-values.yaml -n deco-mcp-mesh --create-namespace
Chart Structure
chart-deco-mcp-mesh/
├── Chart.yaml # Chart metadata
├── values.yaml # Default values
├── templates/ # Kubernetes templates
│ ├── _helpers.tpl # Helper functions
│ ├── deployment.yaml # Application deployment
│ ├── service.yaml # Service
│ ├── configmap.yaml # Main ConfigMap
│ ├── configmap-auth.yaml # Authentication ConfigMap
│ ├── secret.yaml # Secret
│ ├── pvc.yaml # PersistentVolumeClaim
│ ├── serviceaccount.yaml # ServiceAccount
│ ├── hpa.yaml # HorizontalPodAutoscaler
│ └── NOTES.txt # Post-installation messages
└── README.md # This file
Templates and Functionality
This section explains how the chart behaves at render/runtime. For the full sources, see the chart repository: decocms/helm-chart-deco-mcp-mesh.
_helpers.tpl (naming + labels)
name: usesnameOverride(or chart name) and truncates to 63 chars.fullname: usesfullnameOverrideif set; otherwise uses only.Release.Name(important when choosing a release name).- Labels/selectorLabels: standard Helm/K8s labels used across templates.
deployment.yaml (pods)
- HPA vs replicas: when
autoscaling.enabled: true, the deployment does not setreplicas(HPA controls it). - Strategy auto-detection (when
strategy.typeis unset):- RollingUpdate when using PostgreSQL or distributed storage
- Recreate for SQLite with single-writer storage (the safe default)
- Persistence:
persistence.enabled: true→ mounts a PVC at/app/datapersistence.enabled: false→ usesemptyDir(data is ephemeral)
service.yaml (exposure)
- Uses
service.type(ClusterIP,NodePort,LoadBalancer) and standard selectors. - Optional
sessionAffinityif configured.
configmap*.yaml and secret.yaml (config + secrets)
- Non-sensitive runtime config goes to ConfigMap.
- Sensitive values go to Secret.
secret.secretNamesupports “bring your own Secret” (External Secrets Operator, etc.).
pvc.yaml (storage)
- Creates a PVC only when
persistence.enabled: trueandpersistence.claimNameis empty. - If
persistence.claimNameis set, the chart references an existing PVC.
hpa.yaml (autoscaling)
- Rendered only when
autoscaling.enabled: true.
Configurable Values
The full list lives in values.yaml ; below are the most important knobs.
Image
image:
repository: ghcr.io/decocms/mesh/mesh
pullPolicy: Always # Always, IfNotPresent, Never
tag: "latest"
Replicas, scaling, and strategy
replicaCount: 3 # Ignored if autoscaling.enabled: true
autoscaling:
enabled: false
minReplicas: 3
maxReplicas: 6
targetMemoryUtilizationPercentage: 80
strategy:
# type: "" # leave empty for auto-detection
Scalability restriction:
- Use
replicaCount > 1orautoscaling.enabled: trueonly when you have PostgreSQL (database.engine: postgresql) or distributed storage (persistence.distributed: true/accessMode: ReadWriteMany).
Persistence
persistence:
enabled: true
storageClass: "efs"
accessMode: ReadWriteMany
size: 10Gi
claimName: "" # if set, uses an existing PVC
distributed: true
Database (SQLite vs PostgreSQL)
database:
engine: sqlite # sqlite | postgresql
url: "" # required when engine=postgresql
caCert: "" # optional CA cert for managed Postgres SSL validation
Service
service:
type: ClusterIP # ClusterIP, NodePort, LoadBalancer
port: 80
targetPort: 3000
Resources
resources:
requests:
memory: "300Mi"
cpu: "250m"
limits:
memory: "600Mi"
cpu: "500m"
Usage Examples
Example 1: Basic deploy
helm install deco-mcp-mesh . -n deco-mcp-mesh --create-namespace
Example 2: Deploy with custom values
helm install deco-mcp-mesh . -f production-values.yaml -n deco-mcp-mesh --create-namespace
Example 3: Deploy with autoscaling
helm install deco-mcp-mesh . -f autoscaling-values.yaml -n deco-mcp-mesh --create-namespace
Example 4: Deploy with existing Secret
helm install deco-mcp-mesh . -f existing-secret-values.yaml -n deco-mcp-mesh --create-namespace
Maintenance and Updates
Update Values
# Edit values.yaml or create new file
vim custom-values.yaml
# Update release
helm upgrade deco-mcp-mesh . -f custom-values.yaml -n deco-mcp-mesh
# View history
helm history deco-mcp-mesh -n deco-mcp-mesh
# Rollback
helm rollback deco-mcp-mesh -n deco-mcp-mesh
Update Image
# Option 1: Update values and upgrade
helm upgrade deco-mcp-mesh . \
--set image.tag=v1.2.3 \
-n deco-mcp-mesh
# Option 2: If pullPolicy=Always, restart
kubectl rollout restart deployment/deco-mcp-mesh -n deco-mcp-mesh
Update ConfigMap/Secret
# Edit values.yaml
vim values.yaml
# Apply changes
helm upgrade deco-mcp-mesh . -n deco-mcp-mesh
# Restart pods to pick up changes
kubectl rollout restart deployment/deco-mcp-mesh -n deco-mcp-mesh
Verify Changes Before Applying
# Render manifests locally
helm template deco-mcp-mesh . -n deco-mcp-mesh
# If you use helm-diff
helm diff upgrade deco-mcp-mesh . -n deco-mcp-mesh
Database Backup (SQLite)
POD=$(kubectl get pod -l app.kubernetes.io/instance=deco-mcp-mesh -n deco-mcp-mesh -o jsonpath='{.items[0].metadata.name}')
kubectl cp deco-mcp-mesh/$POD:/app/data/mesh.db ./backup-$(date +%Y%m%d).db
Security
Do not commit secrets to Git. Prefer External Secrets Operator (or similar) or pass secrets at install time.
Examples:
helm install deco-mcp-mesh . \
--set secret.BETTER_AUTH_SECRET="$(openssl rand -base64 32)" \
-n deco-mcp-mesh --create-namespace
Monitoring
# View all resources for the release
kubectl get all -l app.kubernetes.io/instance=deco-mcp-mesh -n deco-mcp-mesh
# Logs
kubectl logs -l app.kubernetes.io/instance=deco-mcp-mesh -n deco-mcp-mesh
# Metrics (if metrics-server is installed)
kubectl top pods -l app.kubernetes.io/instance=deco-mcp-mesh -n deco-mcp-mesh Found an error or want to improve this page?
Edit this page