Author: Mirdul Swarup
Last Updated: Wed, Jul 12, 2023Strapi is a headless Content Management System (CMS) that assists users to create and manage content for various platforms such as websites, web, and mobile applications. It offers a user-friendly UI admin dashboard that allows users to edit, delete or add content without prior technical knowledge. Users can also define content types and data structures providing more flexibility in content customization. Its seamless integration with third-party tools allows users to connect with different front-end frameworks, databases, and third-party services.
Strapi can handle large amounts of content and serve it to a growing number of users, which makes it suitable to deploy on a Kubernetes cluster for scalability and deployment in multiple environments. It also provides role-based authentication and user permissions to ensure that only authorized individuals can access or modify your content making it a secure application to run in a cluster.
This article explains how to deploy a Strapi application, containerize, deploy, and scale it on a Vultr Kubernetes Engine (VKE) cluster.
Before you begin, you should:
Deploy a Vultr Kubernetes Engine cluster.
Set up a domain record. This article uses strapi.example.com, replace all occurrences with your actual domain.
Deploy a Vultr Managed Database for PostgreSQL.
Deploy a OneClick Docker instance from the Vultr Marketplaceto use as the management machine.
Use SSH to access the management machine as a non-root sudo user.
Install Kubectl on the server.
Download your VKE cluster configuration file and configure Kubectl to connect to the cluster.
Add the Node.js repository to the server.
$ curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
Install Node.js
$ sudo apt install nodejs
Using the npx
utility, create a new project.
$ npx create-strapi-app@latest strapi-project
The above command creates a new Strapi application called strapi-project
. Then installs the latest version of the create-strapi-app
package. The package creates the necessary structure and installs all necessary dependencies.
When prompted, press enter to Choose Quickstart as your installation type.
Once installation is complete, press CTRL
+ C
to stop the server and configure other application values.
Change to the project directory.
$ cd strapi-project
In this section, configure your Strapi application to use a Vultr Managed Database for PostgreSQL, then set up an environment variable in the app's server file. Additionally, build the app's docker container image to deploy it on the Kubernetes cluster as described below.
Back up the original config/database.js
file.
$ mv config/database.js config/database.ORIG
Using a text editor such as nano
, re-create the file.
$ nano config/database.js
Add the following configurations to the file.
module.exports = ({ env }) => ({
connection: {
client: 'postgres',
connection: {
host: env('DATABASE_HOST', 'postgres.vultrdb.com'),
port: env.int('DATABASE_PORT', 5432),
database: env('DATABASE_NAME', 'default'),
user: env('DATABASE_USERNAME', 'user'),
password: env('DATABASE_PASSWORD', 'password'),
schema: env('DATABASE_SCHEMA', 'public'), // Not required
ssl: {
rejectUnauthorized: env.bool('DATABASE_SSL_SELF', false),
},
},
debug: false,
},
});
Save and close the file.
The above configuration defines the database connection details for the PostgreSQL database. Replace postgres.vultrdb.com
, 5432
, user
, and password
with your actual database details.
Back up the original config/server.js
file.
$ mv config/server.js config/server.ORIG
Re-create the file.
$ nano config/server.js
Add the following configurations to the file.
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),
},
});
Save and close the file.
The above code exports a configuration object that sets the host
, port
, url
, and webhooks
settings for the Strapi application. It also retrieves values from the environment variable.
Define the STRAPI_URL
environment variable.
$ export STRAPI_URL="http://127.0.0.1:1337"
The above command sets the environment variable STRAPI_URL
to the localhost Strapi server and port.
Install the PostgreSQL library for Node.js to enable the database connection.
$ npm install pg --save
Rebuild the package.
$ npm run build
The above command runs the app's build script to incorporate all changes to the files.
Start the application in the background.
$ npm run start &
The above command starts the application in the production environment state. To verify that the Strapi Application runs correctly. Load port 1337
using your Server IP in a web browser.
http://192.0.2.100:1337
The Strapi dashboard should appear with a login screen appears to create the first administrator.
Within the project directory, create a new Dockerfile.
$ nano Dockerfile
Add the following contents to the file.
FROM node:16-alpine
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev nasm bash vips-dev
ARG NODE_ENV=development
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"]
Save and close the file.
The above instructions inherit the base as node:16-alpine. It installs various dependencies necessary for the application. It uses /opt/app
as a working directory and copies the package.json
and the package-lock.json
and installs the dependencies using npm install.
The configuration exposes the Strapi port 1337
which is the default port used by Strapi, then starts the server using the npm run start
command.
Create a file named .dockerignore
.
$ nano .dockerignore
The .dockerignore
file declares a list of files and directories to ignore while building the image.
Add the following content to the file
node_modules/
The above directive prevents duplication of libraries installed on the host machine and lets Docker install all libraries under the dependencies section.
Log in to your DockerHub account.
$ docker login
Enter your DockerHub username and password when prompted. To create a new username, visit the DockerHub registry.
Build the Docker image.
$ docker build -t example-user/strapi:latest .
Push the image to DockerHub.
$ docker push example-user/strapi:latest
You must prepare the Kubernetes cluster for deploying the Strapi application by installing the required plugins and creating a few resources. This section demonstrates the steps to install the ingress-nginx
controller, install the cert-manager
plugin, and create a ClusterIssuer
resource for issuing Let's Encrypt certificates.
Before Installing the Strapi application, prepare the Kubernetes cluster with the necessary plugins and resources as described in this section.
Install CertManager to issue SSL certificates.
$ kubectl apply -f <https://github.com/cert-manager/cert-manager/releases/download/v1.12.2/cert-manager.yaml>
Get the load balancer IP address.
$ kubectl get services/ingress-nginx-controller -n ingress-nginx
Output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.103.190.245 192.0.2.102 80:30420/TCP,443:30061/TCP 7m37s
The load balancer may take up to 10 minutes to get ready. You can verify the load balancer deployment by opening the cluster page in your customer portal and navigating to the Linked Resources tab. Set up your domain A
record for strapi.example.com
to point to the load balancer's IP Address.
Create a new manifest named clusterissuer.yaml
.
$ nano clusterissuer.yaml
Add the following contents to the file.
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
Save and close the file.
The above configuration creates a ClusterIssuer
resource for issuing Let's Encrypt certificates. It uses the HTTP01 challenge solver to verify the ownership. Replace hello@example.com
with your actual email address.
Apply the Issuer to the cluster.
$ kubectl apply -f clusterissuer.yaml
Verify the deployment.
$ kubectl get clusterissuer letsencrypt-prod
Output:
NAME READY AGE
letsencrypt-prod True 8m27s
Create a new deployment file strapi-deployment.yaml
.
$ nano strapi-deployment.yaml
Add the following configurations to the file.
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: example-user/strapi:latest
imagePullPolicy: Always
command: ["npm", "run", "build", "&&", "npm", "run", "develop"]
ports:
- containerPort: 1337
env:
- name: STRAPI_URL
value: "strapi.example.com"
Save and close the file.
The above configuration creates a Deployment
resource:
It creates 2 initial pods with the container image you built, and the pods have the label strapi-app
.
It uses the regcred
secret resource to fetch the credentials. Replace the image example-user/strapi:latest
with your actual repository.
Replace strapi.example.com
with your actual domain pointing to the cluster load balancer.
Create the deployment.
$ kubectl apply -f strapi-deployment.yaml
Verify the deployment
$ kubectl get deployment strapi-deployment
Output:
NAME READY UP-TO-DATE AVAILABLE AGE
strapi-deployment 1/2 2 1 20m
Create a new service manifest.
$ nano strapi-service.yaml
Add the following configurations to the file.
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.
$ kubectl apply -f strapi-service.yaml
Create a new Ingree Manifest.
$ nano strapi-ingress.yaml
Add the following configurations to the file.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: strapi-ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
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
Save and close the file.
The 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 strapi.example.com
and store it as a secret resource strapi-tls
.
Apply the Ingress resource.
$ kubectl apply -f strapi-ingress.yaml
Verify the Ingress resource.
$ kubectl get ingress
Output:
NAME CLASS HOSTS ADDRESS PORTS AGE
strapi-ingress <none> strapi.onlustech.com 192.0.2.100 80, 443 10m
In a web browser, visit the Strapi application and verify that you can access the Strapi Application over HTTPS.
https://strapi.example.com
Scaling the Strapi application makes it efficient to handle large loads of traffic without any interruption or downtime. In this section, scale the Strapi deployments by increasing the number of replicas.
Increase the number of running replicas.
$ kubectl scale deployment/strapi-deployment --replicas=4
The above command increases the number of running replicas to 4.
Check the deployment status.
$ kubectl get deployment strapi-deployment
Output:
NAME READY UP-TO-DATE AVAILABLE AGE
strapi-deployment 0/4 4 0 29m
View available Pods.
$ kubectl get pods
Decrease the number of pods.
$ kubectl scale deployment/strapi-deployment --replicas=2
You can also run the kubectl delete pod
command to do a fault tolerance check. Kubernetes detects changes that occur by the command and automatically creates a new pod instantly. New pods are then detected by the ingress resource which starts serving requests instantly.
In this article, you created an example Strapi application using the Strapi API with PostgreSQL configuration along with its containerization, deployment, and scaling. You also configured CertManager to issue Let's Encrypt certificates and the Nginx Ingress controller to allow external access to cluster services.
Strapi focuses on efficient content management via a web interface without the need for technical knowledge which makes it to build low-code or no-code backends. For more information, visit the following resources.