🐳 Docker with Strapi V4 🌟

🐳 Docker with Strapi V4 🌟

Dockerize Strapi for development

Simen Daehlin's photo
Simen Daehlin
Β·Jan 10, 2022Β·

10 min read

Subscribe to my newsletter and never miss my upcoming articles

Play this article

Table of contents

In this blog, we will set up our own Strapi 🌟 setup using Docker, docker-compose 🐳. We will also use docker-compose to spin up a Postgres πŸ’Ύ database and a local strapi instance.

🚦 Requirements

  • Docker
  • NPM / Yarn (Yarn is recommended)
  • Node 14 is recommended but 16 will work if we are using yarn
  • Your favourite code editor. (I will be using VSCode) πŸ‘©β€πŸ’»

⭐️ Strapi Setup

Let's spin up a brand new V4 project.

npx create-strapi-app@latest myproject --quickstart

Note: Replace myproject with the project name

Once this is completed we can press CTRL + C in the terminal to stop the current file, we don't need to fill in the file as this is using SQLite, and we will be using PostgreSQL

Let's open the folder now in vscode (In the terminal, you can try to use this nifty quick way of opening it cd myproject && code . which will change to the project directory of strapi and open vscode.)

Note: Replace myproject with the project name

So let's get dockering shall we? πŸ€” 🐳

πŸ‘©β€πŸ’» Docker Setup

🐳 The Dockerfile

Create a file called Dockerfile (Note capital D)

If you are using YARN, please use the following πŸ‘‡

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"]

If NPM was your ☠️, then please use the following πŸ‘‡

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"]

Let's step through this gibberish of some code for those that have not used Docker 🐳before.

  • First, we get a node image 16 (We can use :14) if we wanted to use version 14 of node instead.
  • We are setting an argument as default to develop as we want to not have to provide this every time we run our setup. The ENV is a way to override it if we want to like production
  • WORKDIR we are creating a folder inside our container πŸš€ where our node_modules will live
  • We copy package.json and yarn.lock (or package-lock.json if you use npm) into our work directory. We do this FIRST as 🐳 docker caches each layer and this will then speed up our build process. Unless the file changes. πŸ“
  • We then tell Docker where to look for our node_modules
  • In case we have some network issues or a bit of slow internet we are setting a large timeout to give it some extra time.
  • We then run yarn install to install all dependencies.
  • We then change directories into /opt/app
  • We then copy the project that we created first into this folder.
  • Then we run yarn build to build our strapi project.
  • At the end, we expose the port 1337 and tell Docker to run yarn develop

πŸ™…β€β™€οΈ Let's ignore this

Create a file called .dockerignore (Note . in front)

.dockerignore

.tmp/
.cache/
.git/
build/
node_modules/
data/

This works like a .gitignore and will tell Docker 🐳 not to transfer ⛔️ these folders as we don't need them.

πŸ‘·β€β™€οΈ Let's build the image 🌁

We can now build this image in a simple way. docker build -t mystrapi:latest .

Note that mystrapi is the name of the image, and using :latest can be anything like an example docker build -t bestbackendever:1.7.7 .

This will create an image called bestbackendever and be version 1.7.7

Now go get some β˜•οΈ and sit back while Docker does its magic 🐳 πŸͺ„ = ❀️

Once this is completed as it can take a bit of time (Normally a few minutes β˜•οΈ) then we can run our project.

screenshot.png

docker run -d -p 1337:1337 mystrapi

This tells Docker to run the image mystrapi or whatever you called your project πŸ€” on port 1337 -d means detached and is a fancy word of saying "Runs in the background"

Cool tip if you want to use strapi on a different port while developing you can change first part of the run port to something else like so

docker run -d -p 8080:1337 mystrapi

And it will now run on port 8080 πŸ‘ 😏

Now, this won't help us much as currently, we are using an SQLite database, and it's always inside the container. When we stop the container, we lose all changes. So what we are doing to do is to use docker-compose to have a Postgres database and be able to run multiple instances of Docker if we want.

πŸƒβ€β™€οΈ Stepping up our game with docker-compose

So let's really use the power for development with some docker magic. 🐳 πŸͺ„ Think of docker-compose of a way of making different steps or services that we want to run.

Create a file in the root of the project call it docker-compose.yml

version: "3"
services:
  strapi:
    container_name: strapi
    build: .
    image: mystrapi:latest
    restart: unless-stopped
    env_file: .env
    environment:
      DATABASE_CLIENT: ${DATABASE_CLIENT}
      DATABASE_HOST: strapiDB
      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:
      - strapi
    depends_on:
      - strapiDB

  strapiDB:
    image: postgres:12.0-alpine
    container_name: strapiDB
    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:
      - strapi-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:
      - strapi

volumes:
    strapi-data:

networks:
  strapi:
    name: Strapi
    driver: bridge

This is a YAML file so spacing matters ☒️.

I use spaces, not tabs on this to keep consistent.

So to explain what all this is. version - Using version 3 of docker-compose for info read here services - We are defining two services strapi and strapiDB

  • strapi - this is the name of the service we defined
  • contaier_name - The term of the docker container you can call this whatever you want.
  • build - Telling strapi to build the image using our project folder. (Since this is in the root .) it will use our strapi project.
  • image - The name of the image we want to build
  • restart - Unless we STOP the container or take it down it will keep restarting.
  • env_file providing a .env with environmental variables we want to keep secret 🀫
  • environment - Here we are defining all the variables we want to use. ${THISISOURNAME} is what we have in the .env and will be the 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 use a different port like 8080:1337, remember right side needs to be 1337 as that is what is inside the container where strapi is running
  • networks - We are setting up a docker network so our containers can talk together, this can also replace link -depends_on - This is a bit deprecated in newer version but i like to use it. It tells Docker that to run strapi container, we need the postgresDB container to run first. Saving us some errors when strapi starts and there is no database.

Then we get to Postgres same thing here, We are giving it a name, but we are using the official Postgres12-alpine image no need to build it ourselves. We are also creating a volume called strapi-data, which holds our database.

🚦Bind-mounting vs Docker Volume

Bind-mounting is a way to tell Docker to use a specific FOLDER that we got control over. Though I found out it's easier to use a docker volume instead. But if you want to do it, please change the line in docker-compose as directed by comments under volumes.

πŸ‘©β€πŸ”¬ Creating Bind mount folders

So if you wanted to go down this route as the old guide was showing πŸ‘‡

In your root folder create a folder call it data Now let's update .gitignore and add it like so

.gitignore

...
data/

As we don't want to commit these files, it's the whole database 😱.

Right so now we are almost set!

πŸ”§ Changing SQLite to Postgres / MySQL

☒️ First off if you want to use MySQL / MariaDB instead of Postgres please replace the strapiDB in docker-compose with the following πŸ‘‡

  strapiDB: 
    image: mysql:latest
    command: --default-authentication-plugin=mysql_native_password
    platform: linux/amd64 #for platform error on Apple M1 chips
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${DATABASE_PASSWORD}
      MYSQL_DATABASE: ${DATABASE_NAME}
      MYSQL_USER: ${DATABASE_USERNAME}
      MYSQL_PASSWORD: ${DATABASE_PASSWORD}
    ports:
      - '3306:3306'
    volumes:
      - strapi-data:/var/lib/mysql #using a volume
      #- ./data:/var/lib/mysql # if you want to use a bind folder
    networks:
      - strapi

We can say that it's per environment or set it for all environments. For this tutorial we are going to use Postgres so we are going to set it to all. For more info about environments configs please read here

But again, for this tutorial, we will use Postgres for local development AND production, etc. The benefit here is that we can easily export data later if we need it in production etc.

So let's get cracking.

Open the following file. config/database.js and replace it with this.

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", "strapi"),
      user: env("DATABASE_USERNAME", "strapi"),
      password: env("DATABASE_PASSWORD", "strapi"),
    },
    debug: false,
  },
});
});

🫰PRO TIP if you are using MySQL/MariaDB replace client with mysql. This also applies in your .env as well, the port will change to 3306 instead of 5432

☒️ Important step below πŸ‘‡

Make sure that you install either pg or mysql using yarn or npm

# Select one
yarn add pg
yarn add mysql
npm install pg
npm install mysql

else it will fail and you have to rebuild images later.

This now tells strapi that we want to set the database host etc.

So all that is missing is to set up our .env so let's set that up. Open up .env if there is no file rename .env.example to .env

...
DATABASE_HOST=localhost
DATABASE_PORT=5432
#DATABASE_PORT=3306
DATABASE_NAME=strapi
DATABASE_USERNAME=strapi
DATABASE_PASSWORD=strapi
NODE_ENV=development
DATABASE_CLIENT=postgres
#DATABASE_CLIENT=mysql

In total you should now have a .env that looks like this

HOST=0.0.0.0
PORT=1337
APP_KEYS=XXX
API_TOKEN_SALT=XXX
ADMIN_JWT_SECRET=XXX
JWT_SECRET=XXX
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=strapi
DATABASE_USERNAME=strapi
DATABASE_PASSWORD=strapi
NODE_ENV=development
DATABASE_CLIENT=postgres

πŸš€ Running our project

Local - docker-compose up -d strapiDB && yarn develop - This will now spin up just a Postgres database, and we can run and change files just like working on strapi anywhere. 🫰PRO TIP- replace yarn develop with npm run develop if using npm

Full - docker-compose up -d' - This will runstrapi` inside a docker container and the database in its own container.

πŸŽ— Self-Sponsored Tool

If you did not follow the previous guide but want to get started. I have created a tool that will dockerize the whole app and get you upt to speed to this guide.

In your strapi application just run

npx @strapi-community/dockerize

If you on purpose select PostgresSQL and yes to docker-compose in the tool most of the steps here should be a breeze.

If you liked the tool feel free to ⭐️ it on Github

πŸŒ• Updates

πŸ›  Update: 10/07/22 Self-Sponsored Tool section where you can use my tool to automate the blog post.

πŸ›  Update: 07/02/22 Also updated the docker container to add support for libvips-dev to support sharp on build.

πŸ¦„ Update: 10/06/22

Thanks to kalle for the test of this.

πŸ• Update: 10/06/22

  • Updated docker-compose to now use volumes instead of bind mount
  • Added MYSQL/MariaDB thanks to Dennis M from the strapi team
  • Added npm setup
  • Configured yarn set up to be updated (Moved to a single step)
  • Updated variables to match the latest version, 4.1.12
  • Added pg and mysql to be installed

Hope this helps with any questions please let me know in the comment section below. πŸ‘‡ or do pour me a 🍺 or feed that πŸ¦„ at the right hand side.

Β 
Share this