Tutorials  /  Container & Cloud

Deploy and Scale Strapi on Kubernetes with SSL and Ingress

Ccentron Redaktion · October 2025 ·12 min read ·Container & Cloud, Tutorial

Strapi is a headless Content Management System (CMS) that simplifies the creation and administration of content for websites, web applications, and mobile platforms. Its user-friendly admin dashboard allows teams to add, update, or remove content without needing deep technical knowledge. In addition, Strapi lets you define custom content types and data structures, giving you maximum flexibility when organizing content. With seamless integration options, Strapi works smoothly with front-end frameworks, databases, and third-party services, making it a powerful solution for modern content management needs.

Strapi Flow Chart

Strapi is capable of handling large amounts of content and serving a growing user base efficiently, which makes it ideal for deployment on a Kubernetes cluster. Kubernetes provides scalability and supports multi-environment deployments. Furthermore, Strapi offers built-in role-based authentication and user permissions to ensure that only authorized users can access or modify content, adding a strong security layer for applications running in clusters.

Because it is designed for high-volume content and efficient scaling, Strapi pairs perfectly with Kubernetes. Kubernetes boosts Strapi’s scalability and supports deployments across different environments. With built-in role-based authentication and user permissions, Strapi ensures secure access to content in cluster-based applications.

This tutorial explains how to containerize, deploy, and scale a Strapi application on a Kubernetes Engine.

K8

Matching infrastructure at centron

From container to cluster: managed Kubernetes with cStack starts at €29.99 per month – control plane, autoscaler and traffic included. Explore managed Kubernetes →

Prerequisites

Before getting started, ensure you have the following:

  • Access to a Kubernetes Engine Cluster.
  • A DNS A record pointing to the Ingress Load Balancer IP (this guide uses strapi.example.com; replace it with your own domain).
  • Access to a PostgreSQL database.
  • An Ubuntu 24.04 instance as a non-root sudo user to serve as the management workstation.
  • Docker installed on the workstation for containerization.
  • Kubectl installed on the management workstation to manage the Kubernetes Cluster.
  • Helm Package Manager installed on the workstation.
  • Your cluster Kube config file configured so kubectl can connect to the cluster.

Build a Strapi Application

Log in to your management workstation and add the Node.js repository:

Console
$ curl -sL https://deb.nodesource.com/setup_22.x | sudo -E bash -

Install Node.js:

Console
$ sudo apt install -y nodejs

Configure Git

Strapi uses Git for version control. If Git is not configured, set up your global username and email:

Set a global Git username:

Console
$ git config --global user.name "Your Full Name"

Set a global Git email address:

Console
$ git config --global user.email "email@example.com"

Create a new Strapi project with npx:

Console
$ npx create-strapi-app@latest strapi-project --quickstart --js

This command generates a new Strapi application named strapi-project using the Quickstart option. It installs the latest create-strapi-app package, prepares the project structure, and installs all required dependencies.

After installation, press Ctrl + C to stop the server and continue with the configuration.

Containerize the Strapi Application

Next, configure Strapi to use a PostgreSQL database and prepare it for deployment on Kubernetes.

Navigate to the project directory:

Console
$ cd strapi-project

Edit the .env file:

Console
$ nano .env

Update the database credentials inside .env:

Code
DATABASE_CLIENT=postgres
DATABASE_HOST=your-postgres-hostname
DATABASE_PORT=5432
DATABASE_NAME=strapi_db
DATABASE_USERNAME=user
DATABASE_PASSWORD=password
DATABASE_SSL=true
DATABASE_SSL_REJECT_UNAUTHORIZED=false
DATABASE_SCHEMA=public
DATABASE_CONNECTION_TIMEOUT=60000

Replace the host, port, username, database name, and password with your actual PostgreSQL credentials.

Back up the original config/server.js file:

Console
$ mv config/server.js config/server.ORIG

Create a new config/server.js file:

Console
$ nano config/server.js

Add the following configuration:

Code
module.exports = ({ env }) => ({
  host: env('HOST', '0.0.0.0'),
  port: env.int('PORT', 1337),
  url: env('STRAPI_URL'),
  app: {
    keys: env.array('APP_KEYS'),
  },
  webhooks: {
    populateRelations: env.bool('WEBHOOKS_POPULATE_RELATIONS', false),
  },
});

Define the STRAPI_URL environment variable:

Console
$ export STRAPI_URL="http://127.0.0.1:1337"

Install the PostgreSQL Node.js driver:

Console
$ npm install pg --save

Rebuild the application:

Console
$ npm run build

Start the application in the background:

Console
$ npm run start &

Open a browser and visit http://<SERVER-IP>:1337 to confirm the Strapi dashboard appears and create the first administrator account.

Create the Docker Image

Create a Dockerfile in the project directory:

Console
$ nano Dockerfile

Add the following instructions:

Code
FROM node:22-alpine

RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev nasm bash vips-dev
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

WORKDIR /opt/
COPY package.json package-lock.json ./
RUN npm install
ENV PATH=/opt/node_modules/.bin:$PATH

WORKDIR /opt/app
COPY . .
RUN chown -R node:node /opt/app
USER node
RUN ["npm", "run", "build"]

EXPOSE 1337

CMD ["npm", "run", "start"]

Create a .dockerignore file:

Console
$ nano .dockerignore

Add the following content:

Code
node_modules/

Build the Docker image:

Console
$ docker build -t strapi:latest .

Log in to DockerHub:

Console
$ docker login -u <username> -p <password>

Tag the image with your DockerHub username:

Console
$ docker tag strapi:latest <username>/strapi:latest

Push the image to DockerHub:

Console
$ docker push <username>/strapi:latest

Prepare the Kubernetes Cluster

Before deploying the Strapi application, you must configure the Kubernetes cluster by installing the necessary plugins and creating essential resources. This section demonstrates the steps to install the Nginx Ingress Controller, the CertManager plugin, and to create a ClusterIssuer resource to issue Let’s Encrypt SSL certificates.

Follow the steps below to set up your cluster.

Install the Nginx Ingress Controller

Add the Nginx Ingress repository:

Console
$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx

Update Helm chart repositories:

Console
$ helm repo update

Create a new ingress-nginx namespace for Nginx Ingress Controller:

Console
$ kubectl create namespace ingress-nginx

Install the Nginx Ingress Controller:

Console
$ helm install ingress-nginx ingress-nginx/ingress-nginx --namespace ingress-nginx

After installation, a Load Balancer is provisioned automatically for your cluster. Verify that the assigned External-IP Address matches the Load Balancer IP in the Kubernetes Engine dashboard:

Console
$ kubectl get services ingress-nginx-controller --namespace ingress-nginx

The Load Balancer may take several minutes to become ready. If the EXTERNAL-IP field shows as <pending>, wait a few minutes and try again. You can also verify the Load Balancer deployment by opening the cluster page in the Kubernetes portal and checking the linked resources.

Install the CertManager for SSL Certificates

Install CertManager:

Console
$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.2/cert-manager.yaml

Visit the official CertManager releases page to get the latest version.

Get the Load Balancer’s External-IP Address:

Console
$ kubectl get services ingress-nginx-controller --namespace ingress-nginx

Add an A record for your domain strapi.example.com and point it to the External-IP Address of the Load Balancer.

Create a new manifest named clusterissuer.yaml:

Console
$ nano clusterissuer.yaml

Add the following configurations to the file:

YAML
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: "hello@example.com"
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
      - http01:
          ingress:
            class: nginx

Replace "hello@example.com" with your actual email address.

Apply the ClusterIssuer resource:

Console
$ kubectl apply -f clusterissuer.yaml

Verify the ClusterIssuer deployment:

Console
$ kubectl get clusterissuer letsencrypt-prod

A READY status of True confirms the successful deployment of the ClusterIssuer resource.

Deploy the Strapi Application

Create a new deployment manifest file strapi-deployment.yaml:

Console
$ nano strapi-deployment.yaml

Add the following configurations to the file:

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: strapi-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      name: strapi-app
  template:
    metadata:
      labels:
        name: strapi-app
    spec:
      imagePullSecrets:
        - name: regcred
      containers:
        - name: strapi
          image: /strapi:latest
          imagePullPolicy: Always
          command: ["sh", "-c", "npm run build && npm run start"]
          ports:
            - containerPort: 1337
          env:
            - name: STRAPI_URL
              value: "strapi.example.com"

This configuration creates a Deployment resource with two pods running the container image you built, labeled strapi-app. It uses the regcred secret resource to authenticate to DockerHub to pull the container image from a private repository. Replace the image <username>/strapi:latest with your actual repository and change strapi.example.com to your domain pointing to the cluster load balancer.

Apply the strapi-deployment.yaml deployment file to deploy the application:

Console
$ kubectl apply -f strapi-deployment.yaml

Verify that the application is deployed:

Console
$ kubectl get deployment strapi-deployment

Create the Service and Ingress

Create a new service manifest to expose the application:

Console
$ nano strapi-service.yaml

Add the following configurations to the file:

YAML
apiVersion: v1
kind: Service
metadata:
  name: strapi-service
spec:
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: 1337
  selector:
    name: strapi-app

Apply the Service:

Console
$ kubectl apply -f strapi-service.yaml

Create an Ingress manifest:

Console
$ nano strapi-ingress.yaml

Add the following configurations to the file:

YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: strapi-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - secretName: strapi-tls
      hosts:
        - strapi.example.com
  rules:
    - host: strapi.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: strapi-service
                port:
                  number: 80

The above Ingress resource allows external access to the strapi-service resource. It uses the cluster issuer resource letsencrypt-prod to issue a new SSL certificate for domain strapi.example.com and store it as a secret resource strapi-tls.

Apply the Ingress resource:

Console
$ kubectl apply -f strapi-ingress.yaml

Verify the Ingress resource:

Console
$ kubectl get ingress

In a web browser, visit the following URL and verify that the login page displays over the HTTPS protocol:

https://strapi.example.com

Scale the Strapi Deployment

Scaling the Strapi application ensures it can efficiently handle high traffic loads without downtime or interruptions. In this section, you'll scale the Strapi application by adjusting the number of running replicas.

Increase the number of running replicas:

Console
$ kubectl scale deployment/strapi-deployment --replicas=4

The above command scales the deployment to 4 replicas.

Verify the deployment status:

Console
$ kubectl get deployment strapi-deployment

Your output should be similar to the one below:

NAME          READY   UP-TO-DATE   AVAILABLE   AGE

strapi-deployment   4/4   4   4   67m

View the running pods:

Console
$ kubectl get pods

Your output should be similar to the one below:

NAME                     READY   STATUS   RESTARTS   AGE

strapi-deployment-5d5ccdb8b5-gj96w  1/1   Running   0   10m

strapi-deployment-5d5ccdb8b5-l8wv7  1/1   Running   0   9m34s

strapi-deployment-5d5ccdb8b5-wcg2x  1/1   Running   0   100s

strapi-deployment-5d5ccdb8b5-xx8t6  1/1   Running   0   100s

Decrease the number of replicas:

Console
$ kubectl scale deployment/strapi-deployment --replicas=2

Kubernetes automatically terminates the extra pods and adjusts traffic distribution.

You can test the fault tolerance by manually deleting a pod:

Console
$ kubectl delete pod 

Replace <pod-name> with an existing pod name.

Kubernetes detects the change and automatically creates a new pod. The Ingress resource detects the new pod and starts routing requests without downtime.

Conclusion

You have created an example Strapi application using the Strapi API with a PostgreSQL configuration, then containerized, deployed, and scaled the application on a Kubernetes Engine. You also configured CertManager to issue Let’s Encrypt certificates and set up the Nginx Ingress controller to enable secure external access to the cluster services.

Strapi simplifies content management through an intuitive web interface, eliminating the need for technical expertise. This makes it a good choice for building low-code or no-code backends. For more information, visit the following resources:

Jetzt 200 € Guthaben sichern

Testen Sie Ihr Setup auf ccloud³

Registrieren Sie sich in der ccloud³ und erhalten Sie 200 € Startguthaben für Ihr Projekt – z. B. für eine PostgreSQL-VM mit automatischen Backups.

centron Redaktion Technische Redaktion

Das Redaktionsteam von centron schreibt Anleitungen aus dem Betriebsalltag: getestet auf unserer eigenen Plattform, betrieben im Rechenzentrum in Hallstadt bei Bamberg.

Kategorie Container & Cloud
Teilen
Noch offene Fragen?

Our team will help you with your specific setup - in German or English, by people who run the platform themselves.

War dieses Tutorial hilfreich?

Your answer is stored anonymously and helps us improve our tutorials.

Kommentare

No comments yet - be the first to ask a question about this tutorial.

Sign in to comment

Comments are open to centron customers. Sign in to your account to ask a question about this tutorial.

Weiterlesen

Das könnte Sie auch interessieren

Jetzt kostenlos anfangen

Melden Sie sich an und erhalten Sie in den ersten 60 Tagen ein Guthaben von 200 € bei centron.

Dieses Werbeangebot gilt nur für neue Konten. Angebot ausschließlich für Gewerbetreibende.

Jetzt loslegen Sales kontaktieren