Dockerize Strapi CMS: Professional Headless CMS Deployment with PostgreSQL and Docker Compose

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Why You Should Dockerize Strapi Today

Strapi is a great Headless CMS, but manual setup of Node.js or PostgreSQL versions for every deployment is a nightmare. I once spent an entire morning fixing library version mismatches between my local machine and an Ubuntu server. Docker was born to solve the “it works on my machine” problem once and for all.

Practical experience shows that Strapi can be resource-intensive. On a 2GB RAM VPS, if not handled carefully, the build process can hang the entire system. By using Multi-stage builds, I reduced the Image size from 1.2GB to about 450MB. This not only saves bandwidth but also triples CI/CD speed.

Initializing a Standard Project Structure

First, create a new Strapi project. I always prefer PostgreSQL for real-world projects due to its better load handling and data integrity compared to SQLite.

npx create-strapi-app@latest my-project --quickstart --no-run

The optimal directory structure will look like this:

.
├── strapi-app/
│   ├── Dockerfile
│   ├── .dockerignore
│   └── ... (source code)
├── docker-compose.yml
└── .env

Building an Optimized Dockerfile (Multi-stage build)

The heart of this process lies in the Dockerfile. Instead of packaging all build tools into the final image, we divide it into two stages: Build and Runtime. This technique eliminates redundant dependencies, keeping the production environment clean and lightweight.

Create a Dockerfile in the strapi-app/ directory:

# Stage 1: Build
FROM node:18-alpine as build
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev vips-dev git
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

WORKDIR /opt/
COPY package.json package-lock.json ./
RUN npm install -g node-gyp
RUN npm config set fetch-retry-maxtimeout 600000 -g && npm install --only=production

WORKDIR /opt/app
COPY . .
RUN npm run build

# Stage 2: Runtime
FROM node:18-alpine
RUN apk add --no-cache vips-dev
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

WORKDIR /opt/
COPY --from=build /opt/node_modules ./node_modules
WORKDIR /opt/app
COPY --from=build /opt/app ./

EXPOSE 1337
CMD ["npm", "run", "start"]

Important Note: Don’t forget the .dockerignore file. If you accidentally copy the node_modules folder from your local machine into the container, native libraries like sharp will immediately throw OS architecture errors.

Connecting Strapi and PostgreSQL via Docker Compose

Strapi has a “bad habit” of crashing frequently if the database isn’t fully ready. To fix this, we’ll use a healthcheck mechanism. Docker Compose will wait until PostgreSQL is actually ready before starting the Strapi container.

Content of the docker-compose.yml file in the root directory:

version: '3.8'

services:
  strapi-db:
    image: postgres:15-alpine
    container_name: strapi-db
    env_file: .env
    volumes:
      - strapi-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${DATABASE_USERNAME} -d $${DATABASE_NAME}"]
      interval: 10s
      timeout: 5s
      retries: 5

  strapi-app:
    container_name: strapi-app
    build: 
      context: ./strapi-app
    depends_on:
      strapi-db:
        condition: service_healthy
    env_file: .env
    ports:
      - "1337:1337"
    volumes:
      - ./strapi-app/uploads:/opt/app/public/uploads

volumes:
  strapi-data:

Practical Tips for Stable Operation

1. Protecting User Data

All data in a container disappears when you update to a new image. In Strapi, the uploads folder contains all your images and documents. Always map this folder to an external volume as shown in the Compose file to avoid unfortunate data loss.

2. RAM Optimization for Low-End Servers

If you deploy on cheap VPS plans (like DigitalOcean $6/mo or Linode 1GB), limit the RAM for Node.js. Add the environment variable NODE_OPTIONS=--max-old-space-size=1024 to prevent Strapi from consuming all memory and crashing the server.

3. Workflow for Development Environments

When coding locally, you need hot-reload functionality. Instead of using the production Dockerfile, mount your code directly into the container and run npm run develop. This allows you to see changes immediately without time-consuming image rebuilds.

Deploying the System

Everything is ready. Now you just need to run a single command to start the entire system:

docker-compose up -d --build

Wait about 2 minutes for Docker to download images and build the source code. Then, access http://localhost:1337/admin to create your first admin account. Congratulations, your CMS system is now neatly packed in an isolated, secure, and highly scalable environment.

Share: