Strapi
Deploy Strapi on Kubernetes as a production-ready headless CMS for APIs, websites, and custom admin experiences. This chart is designed for prebuilt Strapi project images and includes enterprise-grade features for scalability, security, and observability.
Overview
The HelmForge Strapi chart transforms basic Strapi deployments into production-ready, horizontally-scalable systems with comprehensive configuration options for uploads, email, database pooling, security hardening, and performance tuning.
This chart uses the official HelmForge Strapi base image (docker.io/helmforge/strapi-base), a production-optimized image built specifically for Kubernetes deployments.
Key Features
Core Capabilities
- Multi-replica deployments — horizontal scaling with S3/Cloudinary uploads
- S3-compatible upload providers — AWS S3, MinIO, Cloudflare R2, Backblaze B2
- Cloudinary integration — managed media delivery and transformation
- Email providers — SMTP, SendGrid for notifications and password reset
- Database connection pooling — production-grade PostgreSQL management
- Multiple database backends — PostgreSQL by default, SQLite for development, or external databases
Production Features
- Admin panel security — custom paths, JWT expiration, rate limiting
- Performance tuning — Node.js memory configuration, log levels, structured logs
- HTTP health checks — application-level health validation via
/_health - GraphQL configuration — playground, introspection, depth/complexity limits
- Scheduled S3 backups — automated database dumps and uploads archiving
- Application secrets management — auto-generated keys with upgrade preservation
Installation
HTTPS Repository
helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install strapi helmforge/strapi
OCI Registry
helm install strapi oci://ghcr.io/helmforgedev/helm/strapi
HelmForge Base Image
Why Use helmforge/strapi-base?
The chart uses HelmForge’s production-ready Strapi base image instead of the generic Strapi image. This provides significant advantages for Kubernetes deployments:
Image: docker.io/helmforge/strapi-base:5.50.0
Key Features
- Strapi 5.50.0 — Stable HelmForge base image with security patches
- All official plugins included — Upload providers, email, database plugins pre-installed
- Database support — PostgreSQL for production-ready defaults and SQLite for development use
- Health check endpoint — HTTP
/_healthendpoint returns HTTP 200 with JSON body containing status, uptime, database connectivity, and version - Security hardened — Non-root user (UID 1000), minimal attack surface
- Multi-architecture — Supports linux/amd64 and linux/arm64
- Production optimized — Pre-built dependencies, smaller image size
- Regularly updated — Security patches and Strapi updates applied promptly
Using the Base Image
Default (recommended for testing):
image:
repository: docker.io/helmforge/strapi-base
tag: '5.50.0'
This provides a working Strapi instance with default configuration, perfect for:
- Testing Strapi features
- Proof of concept deployments
- Development environments
- Learning Kubernetes deployments
Custom Strapi Project:
For production deployments with custom content types and configurations:
# Dockerfile
FROM docker.io/helmforge/strapi-base:5.50.0
# Copy your Strapi project
COPY --chown=strapi:strapi . /opt/app
# Build admin panel (if customized)
RUN npm run build
# Your custom startup command (optional)
CMD ["npm", "run", "start"]
Then use your custom image:
image:
repository: ghcr.io/myorg/my-custom-strapi
tag: 'v1.0.0'
Image Specifications
| Specification | Value |
|---|---|
| Registry | docker.io |
| Repository | helmforge/strapi-base |
| Current Tag | 5.50.0 |
| Strapi Version | 5.50.0 |
| Base Image | node:22-alpine |
| User | strapi (UID 1000, GID 1000) |
| Working Dir | /opt/app |
| Exposed Port | 1337 |
| Architectures | linux/amd64, linux/arm64 |
| Size | ~297MB |
Included Features
Pre-installed Plugins:
- Upload providers (local, AWS S3, Cloudinary)
- Email providers (SMTP, SendGrid)
- Database connectors (PostgreSQL, SQLite)
- Admin panel with all features
- GraphQL plugin
- Documentation plugin
- Internationalization (i18n)
Security Features:
- Non-root user execution
- Minimal Alpine-based image
- No unnecessary system packages
- Regular security updates
- Follows Kubernetes security best practices
Health Check Support:
The image includes a custom /_health endpoint that returns HTTP 200 when Strapi is ready:
curl http://localhost:1337/_health
# Returns: {"status":"ok"}
This enables proper Kubernetes health checks:
livenessProbe:
httpGet:
path: /_health
port: 1337
readinessProbe:
httpGet:
path: /_health
port: 1337
Quick Start Examples
Basic PostgreSQL Deployment
Default deployment for development or testing:
image:
repository: docker.io/helmforge/strapi-base
tag: '5.50.0'
postgresql:
enabled: true
persistence:
enabled: true
size: 5Gi
ingress:
enabled: true
ingressClassName: nginx
hosts:
- host: cms-dev.example.com
paths:
- path: /
pathType: Prefix
For single-pod local development with SQLite, set database.mode: sqlite and postgresql.enabled: false.
Production Multi-Replica with S3
Production-ready deployment with horizontal scaling:
replicaCount: 3
image:
repository: docker.io/helmforge/strapi-base
tag: '5.50.0'
# S3 uploads enable multi-replica scaling
strapi:
url: https://cms.example.com
upload:
provider: aws-s3
s3:
enabled: true
bucket: mycompany-strapi-uploads
region: us-east-1
existingSecret: strapi-s3-secret
# Email for password reset and notifications
email:
provider: smtp
defaultFrom: [email protected]
smtp:
host: smtp.sendgrid.net
port: 587
existingSecret: strapi-smtp-secret
# Security hardening
admin:
path: /secure-admin
jwtExpiration: 7d
rateLimit:
enabled: true
max: 5
timeWindow: 900000
# Performance optimization
performance:
nodeOptions: '--max-old-space-size=2048'
logLevel: warn
forceJsonLogs: true
# PostgreSQL with connection pooling
postgresql:
enabled: true
auth:
database: strapi
username: strapi
existingSecret: strapi-postgresql-secret
primary:
persistence:
size: 20Gi
database:
pool:
min: 2
max: 8
# Automated backups
backup:
enabled: true
schedule: '0 2 * * *'
s3:
endpoint: https://s3.amazonaws.com
bucket: mycompany-strapi-backups
existingSecret: strapi-backup-s3-secret
# Production resources
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: '2'
memory: 2.5Gi
ingress:
enabled: true
ingressClassName: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: cms.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: strapi-tls
hosts:
- cms.example.com
Upload Providers
Why Upload Providers Matter
By default, Strapi stores uploads on the local filesystem. This works for single-replica deployments but prevents horizontal scaling. Upload providers solve this by storing media in shared object storage.
S3-Compatible Storage
Supports AWS S3, MinIO, Cloudflare R2, Backblaze B2, and other S3-compatible services:
strapi:
upload:
provider: aws-s3
s3:
enabled: true
# For AWS S3 (leave endpoint empty)
region: us-east-1
bucket: my-strapi-uploads
# For MinIO/R2/Backblaze (specify endpoint)
endpoint: https://minio.example.com
# Credentials (use existingSecret in production)
existingSecret: strapi-s3-secret
# Or inline (not recommended):
# accessKey: AKIAIOSFODNN7EXAMPLE
# secretKey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# Optional configuration
prefix: uploads/ # Folder prefix
acl: private # Access control
baseUrl: https://cdn.example.com # CDN URL
Create the secret:
kubectl create secret generic strapi-s3-secret \
--from-literal=access-key=AKIAIOSFODNN7EXAMPLE \
--from-literal=secret-key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY \
-n strapi
Cloudinary
Managed media delivery with transformation capabilities:
strapi:
upload:
provider: cloudinary
cloudinary:
enabled: true
cloudName: mycompany
existingSecret: strapi-cloudinary-secret
# Or inline:
# apiKey: "123456789012345"
# apiSecret: "abcdefghijklmnopqrstuvwxyz"
Create the secret:
kubectl create secret generic strapi-cloudinary-secret \
--from-literal=api-key=123456789012345 \
--from-literal=api-secret=abcdefghijklmnopqrstuvwxyz \
-n strapi
Multi-Replica Requirements
The chart enforces upload provider requirements:
replicaCount: 1— works with any upload provider (local, S3, Cloudinary)replicaCount > 1— requires S3 or Cloudinary
Attempting multi-replica with local uploads fails at render time with a helpful error message.
Email Configuration
Email providers enable password reset, user invitations, and notifications.
SMTP
Generic SMTP provider (SendGrid, Mailgun, Postmark, etc.):
strapi:
email:
provider: smtp
defaultFrom: [email protected]
defaultReplyTo: [email protected]
smtp:
host: smtp.sendgrid.net
port: 587
username: apikey
existingSecret: strapi-smtp-secret
secure: false # Use STARTTLS
requireTLS: true # Enforce TLS
Create the secret:
kubectl create secret generic strapi-smtp-secret \
--from-literal=smtp-password=SG.abc123... \
-n strapi
SendGrid
Dedicated SendGrid integration:
strapi:
email:
provider: sendgrid
defaultFrom: [email protected]
sendgrid:
existingSecret: strapi-sendgrid-secret
Create the secret:
kubectl create secret generic strapi-sendgrid-secret \
--from-literal=sendgrid-api-key=SG.abc123... \
-n strapi
Database Configuration
SQLite Development Mode
Simple filesystem-based database for development:
database:
mode: sqlite
sqlite:
directory: /opt/app/.tmp
filename: data.db
postgresql:
enabled: false
persistence:
enabled: true
size: 5Gi
Important: SQLite requires persistence.enabled: true and replicaCount: 1.
PostgreSQL Subchart (Default)
Bundled PostgreSQL for complete Helm release. With the default database.mode: auto and postgresql.enabled: true, the chart starts Strapi with PostgreSQL.
postgresql:
enabled: true
architecture: standalone # or replication
auth:
database: strapi
username: strapi
password: '' # auto-generated if empty
existingSecret: strapi-postgresql-secret
primary:
persistence:
enabled: true
size: 20Gi
MySQL Subchart
Bundled MySQL alternative for custom Strapi images that include the MySQL runtime driver:
mysql:
enabled: true
architecture: standalone # or replication
auth:
database: strapi
username: strapi
password: '' # auto-generated if empty
existingSecret: strapi-mysql-secret
primary:
persistence:
enabled: true
size: 20Gi
External Database
Connect to existing database:
database:
mode: external
external:
vendor: postgres # or mysql
host: postgres.database.svc.cluster.local
port: 5432
name: strapi
username: strapi
existingSecret: strapi-db-secret
ssl:
enabled: true
Connection Pooling
Critical for production deployments with multiple replicas:
database:
pool:
min: 2 # Minimum connections per replica
max: 8 # Maximum connections per replica
acquireTimeoutMillis: 30000 # Connection acquisition timeout
idleTimeoutMillis: 30000 # Idle connection timeout
Calculate max connections:
total_connections = replicaCount × pool.max + buffer
Example: 3 replicas × 8 max = 24 connections + 4 buffer = 28 total
Ensure your database max_connections is set accordingly.
Security & Admin Panel
Admin Panel Customization
strapi:
admin:
# Custom admin path (security through obscurity)
path: /secure-admin
# JWT token expiration
jwtExpiration: 7d # 1d, 7d, 30d, etc.
# Rate limiting (brute force protection)
rateLimit:
enabled: true
max: 5 # Max login attempts
timeWindow: 900000 # Time window (15 minutes in ms)
# Forgotten password
forgotPassword:
enabled: true
GraphQL Security
strapi:
graphql:
playgroundEnabled: false # Disable in production
introspection: false # Disable schema introspection
maxDepth: 10 # Query depth limit
maxComplexity: 1000 # Query complexity limit
API Configuration
strapi:
api:
rest:
defaultLimit: 25 # Default pagination limit
maxLimit: 100 # Maximum pagination limit
Performance Tuning
Node.js Configuration
strapi:
performance:
# Node.js memory allocation
nodeOptions: '--max-old-space-size=2048'
# Log level (fatal, error, warn, info, debug, trace)
logLevel: warn
# Structured JSON logs for production
forceJsonLogs: true
# Pretty print logs (development only)
prettyPrint: false
Important: Ensure resources.limits.memory accommodates --max-old-space-size:
resources:
limits:
memory: 2.5Gi # Must be > 2048MB (nodeOptions)
Server Configuration
strapi:
server:
bodyParser:
jsonLimit: 1mb
formLimit: 56kb
textLimit: 1mb
compression:
enabled: true
cron:
enabled: true
logger:
level: info
Backup & Restore
Automated S3 Backups
backup:
enabled: true
schedule: '0 2 * * *' # Daily at 2 AM UTC
s3:
endpoint: https://s3.amazonaws.com
bucket: strapi-backups
prefix: production
createBucketIfNotExists: true
existingSecret: strapi-backup-s3-secret
# Optional: Override database credentials
database:
host: postgres-replica.example.com
existingSecret: strapi-backup-db-secret
What gets backed up:
- SQLite mode: PVC archive (uploads + database)
- PostgreSQL/MySQL mode: SQL dump + uploads archive (if persistence enabled)
Manual Backup
Trigger backup immediately:
kubectl create job --from=cronjob/strapi-backup manual-backup-$(date +%s) -n strapi
Restore Process
Backups are not automated. Manual restore:
- Scale down deployment:
kubectl scale deploy/strapi --replicas=0 - Download backup from S3
- Restore uploads to PVC
- Restore database (import SQL dump or restore SQLite file)
- Scale up deployment:
kubectl scale deploy/strapi --replicas=3
Monitoring & Observability
Health Checks
The chart uses HTTP health checks on the /_health endpoint:
startupProbe:
enabled: true
httpGet:
path: /_health
port: http
failureThreshold: 30
periodSeconds: 5
livenessProbe:
enabled: true
httpGet:
path: /_health
port: http
readinessProbe:
enabled: true
httpGet:
path: /_health
port: http
Logs
View application logs:
# Follow logs
kubectl logs -f deployment/strapi -n strapi
# View recent logs
kubectl logs --tail=100 deployment/strapi -n strapi
# View logs from all replicas
kubectl logs -l app.kubernetes.io/name=strapi -n strapi
Debugging
# Check pod status
kubectl get pods -l app.kubernetes.io/name=strapi -n strapi
# Describe deployment
kubectl describe deployment strapi -n strapi
# Check events
kubectl get events -n strapi --sort-by='.lastTimestamp'
# Test database connectivity (PostgreSQL/MySQL)
kubectl exec deployment/strapi -n strapi -- \
sh -c "nc -zv postgres-host 5432"
# Access admin panel
kubectl port-forward svc/strapi 1337:80 -n strapi
# Then open: http://localhost:1337/admin
Configuration Reference
Essential Values
| Key | Default | Description |
|---|---|---|
replicaCount |
1 |
Number of replicas (requires S3/Cloudinary if > 1) |
image.repository |
helmforge/strapi-base |
Strapi application image (HelmForge production-ready) |
image.tag |
5.50.0 |
HelmForge Strapi base image tag |
strapi.url |
"" |
Public URL (auto-detected from ingress) |
strapi.nodeEnv |
production |
Node environment |
strapi.telemetryDisabled |
true |
Disable Strapi telemetry |
Upload Provider Values
| Key | Default | Description |
|---|---|---|
strapi.upload.provider |
local |
Upload provider (local, aws-s3, cloudinary) |
strapi.upload.s3.enabled |
false |
Enable S3 upload provider |
strapi.upload.s3.bucket |
"" |
S3 bucket name |
strapi.upload.s3.region |
us-east-1 |
S3 region |
strapi.upload.s3.endpoint |
"" |
S3 endpoint (for MinIO/R2) |
strapi.upload.s3.existingSecret |
"" |
Secret for S3 credentials |
Email Provider Values
| Key | Default | Description |
|---|---|---|
strapi.email.provider |
none |
Email provider (none, smtp, sendgrid) |
strapi.email.defaultFrom |
[email protected] |
Default from address |
strapi.email.smtp.host |
"" |
SMTP hostname |
strapi.email.smtp.port |
587 |
SMTP port |
strapi.email.smtp.existingSecret |
"" |
Secret for SMTP password |
Admin & Security Values
| Key | Default | Description |
|---|---|---|
strapi.admin.path |
/admin |
Admin panel path |
strapi.admin.jwtExpiration |
7d |
JWT token expiration |
strapi.admin.rateLimit.enabled |
true |
Enable login rate limiting |
strapi.admin.rateLimit.max |
5 |
Max login attempts |
strapi.admin.rateLimit.timeWindow |
900000 |
Rate limit window (15 min) |
Database Values
| Key | Default | Description |
|---|---|---|
database.mode |
auto |
Database mode. auto resolves to PostgreSQL while the PostgreSQL subchart is enabled. |
database.pool.min |
2 |
Minimum connections per replica |
database.pool.max |
10 |
Maximum connections per replica |
postgresql.enabled |
true |
Deploy PostgreSQL subchart |
mysql.enabled |
false |
Deploy MySQL subchart |
Performance Values
| Key | Default | Description |
|---|---|---|
strapi.performance.nodeOptions |
"" |
Node.js options (e.g., –max-old-space-size) |
strapi.performance.logLevel |
info |
Log level (fatal, error, warn, info, debug, trace) |
strapi.performance.forceJsonLogs |
true |
Structured JSON logs |
Backup Values
| Key | Default | Description |
|---|---|---|
backup.enabled |
false |
Enable scheduled backups |
backup.schedule |
0 3 * * * |
Cron schedule (daily 3 AM) |
backup.s3.bucket |
"" |
S3 bucket for backups |
backup.s3.existingSecret |
"" |
Secret for S3 credentials |
Common Scenarios
Scenario 1: Development Environment
SQLite with local uploads (using HelmForge base image):
replicaCount: 1
image:
repository: docker.io/helmforge/strapi-base
tag: '5.50.0'
pullPolicy: IfNotPresent
database:
mode: sqlite
postgresql:
enabled: false
strapi:
nodeEnv: development
upload:
provider: local
performance:
logLevel: debug
prettyPrint: true
forceJsonLogs: false
persistence:
enabled: true
size: 1Gi
resources:
requests:
cpu: 100m
memory: 256Mi
Scenario 2: Staging with External Database
External PostgreSQL with S3 uploads:
replicaCount: 2
image:
repository: docker.io/helmforge/strapi-base
tag: '5.50.0'
database:
mode: external
external:
vendor: postgres
host: postgres-staging.example.com
name: strapi_staging
username: strapi
existingSecret: strapi-staging-db-secret
pool:
min: 2
max: 5
strapi:
upload:
provider: aws-s3
s3:
enabled: true
bucket: strapi-staging-uploads
existingSecret: strapi-s3-staging-secret
email:
provider: smtp
defaultFrom: [email protected]
smtp:
host: smtp.mailtrap.io
existingSecret: mailtrap-secret
ingress:
enabled: true
ingressClassName: nginx
hosts:
- host: cms-staging.example.com
Scenario 3: Production High Availability
Multi-region with PostgreSQL replication:
replicaCount: 3
image:
repository: docker.io/helmforge/strapi-base
tag: '5.50.0'
postgresql:
enabled: true
architecture: replication
auth:
existingSecret: strapi-postgresql-secret
primary:
persistence:
size: 50Gi
readReplicas:
replicaCount: 2
persistence:
size: 50Gi
database:
pool:
min: 2
max: 8
strapi:
url: https://cms.example.com
upload:
provider: aws-s3
s3:
enabled: true
bucket: strapi-prod-uploads
region: us-east-1
existingSecret: strapi-s3-prod-secret
baseUrl: https://cdn.example.com
email:
provider: smtp
defaultFrom: [email protected]
smtp:
host: smtp.sendgrid.net
existingSecret: sendgrid-prod-secret
admin:
path: /secure-admin
jwtExpiration: 1d
rateLimit:
enabled: true
max: 3
performance:
nodeOptions: '--max-old-space-size=4096'
logLevel: error
forceJsonLogs: true
graphql:
playgroundEnabled: false
introspection: false
backup:
enabled: true
schedule: '0 1 * * *'
s3:
bucket: strapi-prod-backups
existingSecret: strapi-backup-s3-secret
resources:
requests:
cpu: '1'
memory: 2Gi
limits:
cpu: '4'
memory: 5Gi
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app.kubernetes.io/name: strapi
topologyKey: kubernetes.io/hostname
Troubleshooting
Multi-Replica Warning
Error: Multi-replica deployment requires S3 or Cloudinary upload provider
Cause: Attempting replicaCount > 1 with strapi.upload.provider: local
Solution: Configure S3 or Cloudinary upload provider, or set replicaCount: 1
Database Connection Errors
Error: too many connections
Cause: Insufficient database max_connections for replica count and pool size
Solution:
- Calculate required connections:
replicaCount × pool.max + buffer - Increase database
max_connections - Or reduce
database.pool.max
Upload Failures
Error: Uploads fail in multi-replica deployment
Cause: Using local upload provider with multiple replicas
Solution: Configure S3 or Cloudinary upload provider
Health Check Failures
Error: Pods repeatedly restart, health checks failing
Cause: Application not responding on /_health endpoint
Solution:
- Check application logs:
kubectl logs deployment/strapi - Verify database connectivity
- Increase
startupProbe.failureThresholdfor slow starts - Check resource limits (CPU/memory)
Performance Issues
Error: Out of memory errors
Cause: nodeOptions memory allocation exceeds pod limits
Solution: Ensure resources.limits.memory > nodeOptions --max-old-space-size
Email Not Working
Error: Password reset emails not sent
Cause: Email provider not configured
Solution:
- Configure
strapi.email.provider(smtp or sendgrid) - Create secret with SMTP password or SendGrid API key
- Test email configuration in Strapi admin panel
Upgrade Notes
Strapi 5.50.0 adds active device session management, secure defaults for new
projects created with create-strapi-app, EU SendGrid region support, and AWS
credential provider functions. Existing projects are not migrated to the new
scaffold defaults by changing the image tag. The release also defaults legacy
JWT verification to HS256 and includes admin, relation, upload, and dynamic-zone
fixes. Back up the database and uploads PVC before
upgrading production workloads.
Migration Guide
From Strapi Chart < 1.5.0
Breaking Changes in 1.5.0:
-
Health probes changed from TCP to HTTP
- Now uses
/_healthendpoint - Update external monitoring if watching probe endpoints
- Now uses
-
Multi-replica now requires upload provider
replicaCount > 1enforced at render time- Configure S3/Cloudinary or set
replicaCount: 1
New Features (Opt-in):
All new features are backward compatible and opt-in:
- S3/Cloudinary upload providers
- Email providers
- Database connection pooling
- Admin security features
- Performance tuning options
Migration Steps:
- Review your
values.yaml - If using
replicaCount > 1, add S3/Cloudinary configuration - Optionally enable new features (email, admin security, etc.)
- Run
helm upgradewith new values - Verify pods start successfully
- Test admin panel and uploads
Additional Resources
- Chart Source: github.com/helmforgedev/charts/tree/main/charts/strapi
- Strapi Documentation: docs.strapi.io
- Strapi Upload Providers: docs.strapi.io/dev-docs/plugins/upload
- Strapi Email: docs.strapi.io/dev-docs/plugins/email
- Issue Tracker: github.com/helmforgedev/charts/issues
Support
For questions, issues, or feature requests:
- Check this documentation
- Review GitHub Issues
- Open a new issue with:
- Helm chart version
- Kubernetes version
- Complete error messages
- Relevant values.yaml configuration