How to Deploy Strapi on the Vultr Kubernetes Engine
Introduction
Strapi 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.
Prerequisites
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.
Build a Strapi Application
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 thecreate-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
Containerize the Strapi Application
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
, andpassword
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
, andwebhooks
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.
Create the Docker Image
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 thepackage.json
and thepackage-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 thenpm 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
Prepare the Kubernetes Cluster
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 Nginx Ingress Controller.
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 forstrapi.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. Replacehello@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
Deploy the Strapi Application
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 imageexample-user/strapi:latest
with your actual repository. - Replace
strapi.example.com
with your actual domain pointing to the cluster load balancer.
- It creates 2 initial pods with the container image you built, and the pods have the label
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 resourceletsencrypt-prod
to issue a new SSL certificate forstrapi.example.com
and store it as a secret resourcestrapi-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
Scale the Strapi Deployment
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.
Conclusion
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.