HomeRolesContact
Application Migration
Dockerizing Strapi - Open-Source NodeJS Headless CMS
DevOps Engineer
DevOps Engineer
September 22, 2022
3 min

Table Of Contents

01
Step 1: Install Strapi CMS-Backend
02
Step 2: Powerful CMS-Backend GraphQL APIs
03
Step 3. Docker Setup
04
Step 4. Building & Running the Docker Image
05
Step 5. Utilizing Docker-compose for the next level
06
Next Steps
Dockerizing Strapi - Open-Source NodeJS Headless CMS

🎯 An Open-Source NodeJS-based Content Management System with a fully customizable API. You can save time and effort by creating production-ready Node.js APIs in hours rather than weeks. 🚀

🚦 Prerequisites

👨‍💻 Setup Development and Testing Environment on MacOS

  • Node.js v16
  • Yarn v1
  • Gatsby CLI
TypeAPI-Driven
HostingSelf-Hosted
Skill LevelExpert
Pricing🆓 Pricing Plans
Official Plugin✔️ gatsby-source-strapi
Webhooks✔️

Step 1: Install Strapi CMS-Backend

  • Install Strapi CMS Backend with the blog schema template outside of the Gatsby frontend directory on your machine.

    cd projects
    yarn create strapi-app cms --quickstart --template strapi-blog
    
    # yarn create strapi-app cms --quickstart --template https://github.com/Academy4U/strapi-blog
    
  • Following the installation, Strapi’s control panel will open in your browser, where you can register the admin user and create content.

  • Frontend (Gatsby or Next.js) integration

Step 2: Powerful CMS-Backend GraphQL APIs

cd cms

yarn install
yarn develop

Step 3. Docker Setup

Developers are faced with the task of launching a development environment that has different software packages of certain versions. Fortunately, Docker solves this problem in the modern development world.

Creating Dockerfile & docker-compose.yml

# cd projects/cms

curl -H "Accept: application/vnd.github.VERSION.raw" \
https://api.github.com/repos/Academy4U/docker/contents/strapi/Dockerfile\?ref\=main -o Dockerfile

curl -H "Accept: application/vnd.github.VERSION.raw" \
https://api.github.com/repos/Academy4U/docker/contents/strapi/docker-compose.yml\?ref\=main -o docker-compose.yml
  • 🐳 Dockerfile

    🐳 Creating a new `Dockerfile`: If you are using YARN, please use the following 👇
    # FROM node:16-alpine
    ## Installing libvips-dev for sharp Compatability
    # RUN apk update && apk add  build-base gcc autoconf automake zlib-dev libpng-dev nasm bash vips-dev
    FROM node:16
    ## Installing libvips-dev for sharp Compatability
    RUN apt-get update && apt-get install libvips-dev -y
    
    ARG NODE_ENV=development
    ENV NODE_ENV=${NODE_ENV}
    WORKDIR /opt/
    COPY ./package.json ./yarn.lock ./
    ENV PATH /opt/node_modules/.bin:$PATH
    RUN yarn config set network-timeout 600000 -g && yarn install
    WORKDIR /opt/app
    COPY ./ .
    RUN yarn build
    EXPOSE 1337
    CMD ["yarn", "develop"]
    
  • 🐳 Dockerfile.npm

    🐳 Creating a new `Dockerfile` tf NPM was your ☠️, then please use the following 👇
    # FROM node:16-alpine
    ## Installing libvips-dev for sharp Compatability
    # RUN apk update && apk add  build-base gcc autoconf automake zlib-dev libpng-dev nasm bash vips-dev
    FROM node:16
    ## Installing libvips-dev for sharp Compatability
    RUN apt-get update && apt-get install libvips-dev -y
    
    ARG NODE_ENV=development
    ENV NODE_ENV=${NODE_ENV}
    WORKDIR /opt/
    COPY ./package.json ./package-lock.json ./
    ENV PATH /opt/node_modules/.bin:$PATH
    RUN npm install
    WORKDIR /opt/app
    COPY ./ .
    RUN npm run build
    EXPOSE 1337
    CMD ["npm", "run", "develop"]
    
  • Quick tour of Dockerfile: 👇

    🐳 To get started, let's take a quick tour of Dockerfile: 👇
    • Initially, we’ll use node:16 (2.62 GB) or node:16-alpine (1.53~1.91 GB) as our base image.
    • We’ll install some libraries, like libvips-dev for sharp compatibility, with -y, so say yes to everything.
    • The node environment AVG will be set to development by default so we don’t have to provide this each time.
    • The ENV allows us to override it if we want to switch from development to production.
    • We’ll define our file paths and whatnot in the /opt WORKDIR working folder inside our container.
    • We copy package.json and yarn.lock (or package-lock.json if you’re using npm) into our work directory. Docker caches each layer, so doing this first will speed up our build process.
    • Docker then knows where to find our node_modules
    • In case of network problems or a bit of slow internet, we will set a large timeout 600000 to allow extra time.
    • Afterwards, yarn install installs all dependencies.
    • Then we change the directories to /opt/apps
    • Next, we copy the project we created in step 1, cms-backend, into this folder.
    • We then run yarn build to build our MEAN project.
    • Finally, we expose port 1337 and tell Docker to run yarn develop
  • .dockerignore 👇

    🐳 Docker Ignore: Create a file called `.dockerignore`: 👇
    .tmp/
    .cache/
    .git/
    build/
    node_modules/
    data/
    

    ✍️ These folders in .dockerignore will be skipped ⛔️ by Docker 🐳 since they are not necessary.


Step 4. Building & Running the Docker Image

    1. Building the Docker Image

    docker build -t cms:latest .

    • The name of the docker image is cms-backend, and it’s tagged with :latest
    • Lastly, grab a cup of coffee ☕️, normally a few minutes , and sit back while Docker does its magic 🪄

    docker system prune --all --force

    1. Running the Docker Image

    docker run -d -p 1337:1337 cms

    • Docker will run the image cms-backend, or whatever you called your project, 🤔 on port 1337.

    • -d means detached and is a fancy way of saying “Runs in the background”

    • Tip: To use strapi on another port while developing, change the first part of the run port.

      docker run -d -p 8888:1337 cms

      run on port 8888 👍

    • Finally, run on port 1337 👍

✍️ We are currently using an SQLite database, which is always inside the container. Whenever we stop a container, we lose all changes. Using docker-compose, we can use a Postgres database and run multiple instances of Docker if needed.


Step 5. Utilizing Docker-compose for the next level

  1. config/database.js

    ⚙️ `config/database.js` 👇
    const path = require('path');
    
    // module.exports = ({ env }) => ({
    //   connection: {
    //     client: 'sqlite',
    //     connection: {
    //       filename: path.join(__dirname, '..', env('DATABASE_FILENAME', '.tmp/data.db')),
    //     },
    //     useNullAsDefault: true,
    //   },
    // });
    
    /** PostgreSQL Database */
    module.exports = ({ env }) => ({
      connection: {
        client: env("DATABASE_CLIENT", "postgres"),
    
        connection: {
          host:     env("DATABASE_HOST", "127.0.0.1"),
          port:     env.int("DATABASE_PORT", 5432),
          database: env("DATABASE_NAME", "cms"),
          user:     env("DATABASE_USERNAME", "cms"),
          password: env("DATABASE_PASSWORD", "cms"),
        },
        debug: false,
      },
    });
    
    
  2. .env

    ⚙️ `.env` 👇
    HOST=0.0.0.0
    PORT=1337
    
    ...
    
    DATABASE_HOST=localhost
    DATABASE_PORT=5432
    # DATABASE_PORT=3306
    DATABASE_NAME=cms
    DATABASE_USERNAME=cms
    DATABASE_PASSWORD=cms
    NODE_ENV=development
    DATABASE_CLIENT=postgres
    # DATABASE_CLIENT=mysql
    
    🐳 In the root of the project, create a file called `docker-compose.yml`. Due to the YAML format, spacing matters, so I've used spaces rather than tabs 👇
    version: "3"
    services:
      cms:
        container_name: cms
        build: .
        image: cms:latest
        restart: unless-stopped
        env_file: .env
        environment:
          DATABASE_CLIENT: ${DATABASE_CLIENT}
          DATABASE_HOST: cmsDB
          DATABASE_NAME: ${DATABASE_NAME}
          DATABASE_USERNAME: ${DATABASE_USERNAME}
          DATABASE_PORT: ${DATABASE_PORT}
          JWT_SECRET: ${JWT_SECRET}
          ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET}
          DATABASE_PASSWORD: ${DATABASE_PASSWORD}
          NODE_ENV: ${NODE_ENV}
        volumes:
          - ./config:/opt/app/config
          - ./src:/opt/app/src
          - ./package.json:/opt/package.json
          - ./yarn.lock:/opt/yarn.lock ##Replace with package-lock.json if using npm
          - ./.env:/opt/app/.env
        ports:
          - "1337:1337"
        networks:
          - cms
        depends_on:
          - cmsDB
    
      cmsDB:
        image: postgres:12.0-alpine
        container_name: cmsDB
        platform: linux/amd64 ##for platform error on Apple M1 chips
        restart: unless-stopped
        env_file: .env
        environment:
          POSTGRES_USER: ${DATABASE_USERNAME}
          POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
          POSTGRES_DB: ${DATABASE_NAME}
        volumes:
          - cms-data:/var/lib/postgresql/data/ ##using a volume
          #- ./data:/var/lib/postgresql/data/  ##if you want to use a bind folder
        ports:
          - "5432:5432"
        networks:
          - cms
    
    volumes:
        cms-data:
    
    networks:
      cms:
        name: cms
        driver: bridge
    
    📚 I'll explain what all of this means: 👇
    • version - Docker-compose version 3

    • services - We are defining two services cms and cmsDB

    • cms - The name of the service we defined

    • container_name - The name of the container. You can call it whatever you want.

    • build - Telling cms to build the image in our project folder ..

    • image - The image name we want to build

    • restart - Unless we STOP or take down the container, it will keep restarting.

    • env_file - Providing a .env file containing the environmental variables we should keep secret.

    • environment - Here we define all the variables we want to use. Our .env file will have $[THISISOURNAME] as a placeholder.

    • volumes - mounting files into the container. Now this could be ./:/opt/app, but we might want to develop locally and just run our development server locally we are binding folders and some files to not bind node_modules There is some info about that here.

    • ports - What ports we want to expose. Note: You can change the left side to another port, such as 8080:1337, but remember that the right side needs to be 1337, which is the port inside the container where CMS is running.

    • networks - Set up a docker network so that our containers can communicate together. The Docker-Network tells Docker that before running the cms container, we need to run the postgresDB container first. This saves us some errors when we start the CMS container without a database.

    • Similarly, we give Postgres a name, but we use the official postgres:12.0-alpine image instead of building it ourselves. In addition, we are creating a volume called cms-data to hold our database.

    • ✍️ When installing Docker Desktop for MacOS, docker-compose is also automatically installed; however, for Linux, you must separately install it.

🚀 Running our project

  • 🐳 Local: This will now spin up just a Postgres database, and we can run and change files just like working on Strapi anywhere.

    docker-compose up -d cmsDB && yarn develop

  • 🐳 Full: This will run Strapi inside a Docker Container and the database in its own container.

    docker-compose up -d


Next Steps

  • 1️⃣ Backend Deployment using Render, Heroku, GCP, AWS
  • 2️⃣ Frontend: Gatsby Cloud, Netlify, AWS Amplify
  • 3️⃣ Infrastructure as Code: Build and Deploy Application to AWS App Runner || ECS/EKS using Terraform and AWS CodePipeline

Tags

#docker#cms#NodeJS
DevOps Engineer

DevOps Engineer

DevSecOps Engineer

Facilitate and undertake communication, collaboration, integration and automation to improve efficiency and workflow across different specialist IT teams.

Expertise

DevOps
Terraform
CDK

Social Media

githubyoutubefb

Related Posts

MacOS DevTest Environment
👨‍💻 Setup Development and Testing Environment on MacOS
September 19, 2022
2 min

Quick Links

🌏 About Us📸 Youtube💌 Contact Us

Social Media