How to Deploy Strapi on the Vultr Kubernetes Engine

Updated on July 12, 2023
How to Deploy Strapi on the Vultr Kubernetes Engine header image

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 flow chart

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:

Build a Strapi Application

  1. Add the Node.js repository to the server.

     $ curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -  
  2. Install Node.js

     $ sudo apt install nodejs
  3. 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.

  4. 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.

  1. Back up the original config/database.js file.

     $ mv config/database.js config/database.ORIG
  2. Using a text editor such as nano, re-create the file.

     $ nano config/database.js
  3. 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.

  4. Back up the original config/server.js file.

     $ mv config/server.js config/server.ORIG
  5. Re-create the file.

     $ nano config/server.js
  6. 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.

  7. 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.

  8. Install the PostgreSQL library for Node.js to enable the database connection.

     $ npm install pg --save
  9. Rebuild the package.

     $ npm run build

    The above command runs the app's build script to incorporate all changes to the files.

  10. 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.

Strapi Application Dashboard

Create the Docker Image

  1. Within the project directory, create a new Dockerfile.

     $ nano Dockerfile
  2. 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.

  3. Create a file named .dockerignore.

     $ nano .dockerignore

    The .dockerignore file declares a list of files and directories to ignore while building the image.

  4. 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.

  5. 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.

  6. Build the Docker image.

     $ docker build -t example-user/strapi:latest .
  7. 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.

  1. Install CertManager to issue SSL certificates.

     $ kubectl apply -f <https://github.com/cert-manager/cert-manager/releases/download/v1.12.2/cert-manager.yaml>
  2. 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.

  3. Create a new manifest named clusterissuer.yaml.

     $ nano clusterissuer.yaml
  4. 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.

  5. Apply the Issuer to the cluster.

     $ kubectl apply -f clusterissuer.yaml
  6. Verify the deployment.

     $ kubectl get clusterissuer letsencrypt-prod

    Output:

     NAME               READY   AGE
     letsencrypt-prod   True   8m27s

Deploy the Strapi Application

  1. Create a new deployment file strapi-deployment.yaml.

     $ nano strapi-deployment.yaml
  2. 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.
  3. Create the deployment.

     $ kubectl apply -f strapi-deployment.yaml
  4. Verify the deployment

     $ kubectl get deployment strapi-deployment

    Output:

       NAME                READY   UP-TO-DATE   AVAILABLE   AGE
       strapi-deployment   1/2     2            1           20m
  5. Create a new service manifest.

     $ nano strapi-service.yaml
  6. 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
  7. Apply the Service.

     $ kubectl apply -f strapi-service.yaml
  8. Create a new Ingree Manifest.

     $ nano strapi-ingress.yaml
  9. 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.

  10. Apply the Ingress resource.

     $ kubectl apply -f strapi-ingress.yaml
  11. 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
  12. 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.

  1. Increase the number of running replicas.

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

    The above command increases the number of running replicas to 4.

  2. Check the deployment status.

     $ kubectl get deployment strapi-deployment

    Output:

       NAME                READY   UP-TO-DATE   AVAILABLE   AGE
       strapi-deployment   0/4     4            0           29m
  3. View available Pods.

     $ kubectl get pods
  4. 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.