Skip to content

Strapi on k3s (Production Setup)

This document describes a production‑grade Strapi installation on a k3s cluster using:

  • PostgreSQL (existing, external)
  • Envoy Gateway (Gateway API)
  • cert-manager + Let's Encrypt
  • GitHub Container Registry (GHCR)
  • GitHub Actions CI/CD

Domain: - https://cms.hershkowitz.co.il


1. Overview

Strapi is deployed as a containerized Node.js application, built from source, pushed to GHCR, and deployed into Kubernetes. Ingress is handled by Envoy Gateway with TLS termination.


2. Architecture

Internet
→ Envoy Gateway (HTTPS, SNI)
→ HTTPRoute (cms.hershkowitz.co.il)
→ Strapi Service (cms/strapi:1337)
→ PostgreSQL
→ PersistentVolume (uploads)


3. Namespace

kubectl create namespace cms

4. Database

PostgreSQL is pre‑existing.

Created manually: - Database: strapi - User: strapi - Password stored in Kubernetes Secret


5. Persistent Storage (Uploads)

Critical path for Strapi uploads:

/app/public/uploads

PVC:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: strapi-uploads
  namespace: cms
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

6. Secrets

Secret: strapi-secrets (namespace cms)

Includes: - DATABASE_* (PostgreSQL) - APP_KEYS - API_TOKEN_SALT - ADMIN_JWT_SECRET - JWT_SECRET - HOST=0.0.0.0 - PORT=1337 - PUBLIC_URL=https://cms.hershkowitz.co.il


7. Strapi App Creation

Created using Node container:

docker run --rm -it   -v "$PWD/k8s/strapi/app:/app"   -w /app   node:20-bullseye   bash -lc "npx create-strapi-app@latest ."

Options: - TypeScript: YES - Database: PostgreSQL - Git init: NO


8. Docker Image

Do NOT use strapi/strapi:latest.
Strapi must be built from your app source.

Dockerfile:

FROM node:20-bullseye AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-bullseye
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app ./
EXPOSE 1337
CMD ["npm","run","start"]

9. CI/CD (GitHub Actions → GHCR)

Workflow: .github/workflows/strapi-build-push.yml

Responsibilities: - Build image - Push to GHCR - Create ghcr-credentials secret in cms namespace

Image pushed as:

ghcr.io/rotem-blik/strapi:1

10. Kubernetes Deployment

Key points: - Uses imagePullSecrets: ghcr-credentials - Mounts PVC to /app/public/uploads - Reads env vars from strapi-secrets


11. Ingress (Envoy Gateway)

HTTPRoute:

hostnames:
  - cms.hershkowitz.co.il
parentRefs:
  - name: public-gw
    namespace: default

12. TLS / cert-manager

Certificate must be in default namespace (same as Gateway):

kind: Certificate
metadata:
  name: cms-hershkowitz-co-il
  namespace: default
spec:
  secretName: cms-hershkowitz-co-il-tls
  issuerRef:
    name: letsencrypt
    kind: ClusterIssuer
  dnsNames:
    - cms.hershkowitz.co.il

Gateway listener:

- name: https-cms
  hostname: cms.hershkowitz.co.il
  tls:
    certificateRefs:
      - name: cms-hershkowitz-co-il-tls

13. Verification Checklist

  • Pod running: cms/strapi
  • HTTPS works
  • /admin loads
  • Upload survives pod restart

14. Real Issues Encountered (Important)

  • strapi/strapi:latest not pullable
  • ❌ Missing package-lock.json breaks npm ci
  • ❌ Wrong GHCR owner (rotemh-blik vs rotem-blik)
  • ❌ Wrong upload mount path (/opt/app instead of /app)
  • ❌ TLS secret in wrong namespace

15. Prod to Dev and Back

Strapi – Switching Between Development and Production (Kubernetes)

This document explains how to safely switch a Strapi instance between Development and Production modes when running on Kubernetes.

It is written for a single Strapi deployment that you temporarily flip to Development for schema editing, and then return to Production.


Assumptions

  • Kubernetes namespace: cms
  • Deployment name: strapi
  • Container name: strapi
  • Strapi runs from an image whose Dockerfile uses:
    CMD ["npm", "run", "start"]
    

What the Modes Mean

Production Mode

  • NODE_ENV=production
  • Strapi runs with npm run start
  • Admin schema editing is disabled
  • Intended for real users / traffic

Development Mode

  • NODE_ENV=development
  • Strapi runs with npm run develop --watch-admin
  • Content-Type Builder and schema editing are enabled
  • Intended for short, controlled use only

Switch Strapi to Production Mode

1. Set NODE_ENV=production

kubectl -n cms set env deployment/strapi NODE_ENV=production

2. Run Strapi using npm run start

kubectl -n cms patch deployment strapi --type='json' -p='[
  {
    "op": "replace",
    "path": "/spec/template/spec/containers/0/command",
    "value": ["npm"]
  },
  {
    "op": "replace",
    "path": "/spec/template/spec/containers/0/args",
    "value": ["run", "start"]
  }
]'

3. Restart and wait for rollout

kubectl -n cms rollout restart deployment/strapi
kubectl -n cms rollout status deployment/strapi

4. Verify Production Mode

kubectl -n cms exec deploy/strapi -- sh -lc 'echo NODE_ENV=$NODE_ENV; ps aux | grep strapi | head -n 10'

Expected output: - NODE_ENV=production - Process contains strapi start


Switch Strapi to Development Mode

⚠️ Development mode should be temporary.


1. Set NODE_ENV=development

kubectl -n cms set env deployment/strapi NODE_ENV=development

2. Run Strapi using npm run develop

kubectl -n cms patch deployment strapi --type='json' -p='[
  {
    "op": "replace",
    "path": "/spec/template/spec/containers/0/command",
    "value": ["npm"]
  },
  {
    "op": "replace",
    "path": "/spec/template/spec/containers/0/args",
    "value": ["run", "develop", "--", "--watch-admin"]
  }
]'

3. Restart and wait for rollout

kubectl -n cms rollout restart deployment/strapi
kubectl -n cms rollout status deployment/strapi

4. Verify Development Mode

kubectl -n cms exec deploy/strapi -- sh -lc 'echo NODE_ENV=$NODE_ENV; ps aux | grep strapi | head -n 10'

Expected output: - NODE_ENV=development - Process contains strapi develop --watch-admin


Common Issues & Notes

sh: 1: strapi: not found

Always run Strapi via npm scripts (npm run start / npm run develop).

Vite Error: Host Not Allowed

Occurs only in dev mode. Fix by adding src/admin/vite.config.ts and allowing the hostname, then rebuilding the image.


Summary

Action Production Development
NODE_ENV production development
Command npm run start npm run develop -- --watch-admin
Schema Editing

16. TODO

  • Backup PVC + DB
  • Object storage (S3 / MinIO)
  • Keycloak integration
  • Rate limiting / WAF
  • Horizontal scaling

Status: Production‑ready