How I Use Next.js, Strapi & Docker to Build My Personal Website

Posted on: Sunday, 29 August 2021

Here's the high-level set of technologies I ended up using:

  • Next.js (React)
  • Docker Swarm
  • Postgres
  • Strapi
  • Portainer
  • Traefik


I use Strapi as a content source for the website. I initially used static markdown files for content but it became too inconvenient to update the files. Managing content through a nice user interface is so much easier than writing content in code!

My data model is pretty simple. I used the blog template to setup my initial content model. I have the following content types defined: Article, Project. Category, User, Writer.

Combined with Next.js, Strapi gives me the power and flexibility of a CMS with the speed of a static website.

Next.js Build Overview

  • I use getStaticProps for pre-rendering the pages
  • I use an auto-generated HTTP client to interact with the Strapi API from Next.js. The client is generated using the OpenAPI Generator which reads the OpenAPI spec from Strapi.
  • I use Static HTML Export to generate static HTML files which are served by Nginx from within the docker container

Docker Build Overview

Here's a condensed overview of my Dockerfile:

FROM node:16.8.0-alpine as base

FROM base AS deps
ENV CI true
COPY package.json package-lock.json ./
RUN npm ci

FROM base AS builder
COPY . .
COPY --from=deps /app/node_modules ./node_modules
RUN NODE_ENV=production npm run build

COPY --from=builder /app/.next /app/.next
COPY --from=builder /app/out /usr/share/nginx/html
COPY ./nginx /etc/nginx

The Dockerfile above uses multi-stage builds to achieve the following:

  1. Install npm deps in the deps stage
  2. Build project in the build stage
  3. Run project in the final stage

Using a multi-stage approach results in low file-size for my runtime docker image as no build artifacts are included.


The magic happens in my GitHub Actions CI/CD pipelines, which I use to:

  1. Build and publish a new docker image
  2. Publish static assets to AWS S3
  3. Restart the Next.js docker app using a Portainer webhook

I trigger the GitHub Actions deploy workflow from Strapi whenever any content change is made. To achieve this I configured a webhook in Strapi to call my strapi webhook proxy, which calls the GitHub Actions webhook.

To prevent conflicting workflows and disable parallel runs I set the concurrency setting in my workflow definition. This allows me to make multiple consecutive changes in Strapi without generating conflicting deployments:

GitHub Action deploy workflow

With this setup I don't have to worry about the deployment at all, I just make as many changes as I like and the latest change will always be deployed. It takes around 3 minutes to deploy a new version after making a change in Strapi, which is pretty good!

View my complete GitHub Actions Workflow definition.


I use a self-hosted Plausible instance for tracking basic user activity (like page views). It took some effort getting Plausible to run in docker swarm, I faced some issues with docker secrets and also found & fixed a bug. This is the working docker swarm setup I ended up using.


I host all services on a small VPS with the following specs:

  • 2 VCPU
  • 4 GB RAM
  • 40 GB SSD DISK

I use Hetzner Cloud and the server is located in Germany.

Server Stack

I setup the VPS using a handy shell script I created: which gives me:

  • Docker Swarm to manage running Docker containers
  • Portainer to manage my swarm stacks and remotely restart services
  • Traefik to care of network routing and TLS
  • Docker Registry to host my docker images

Static assets (eg js & css) are hosted on AWS S3 (See my blog post on how I setup S3 & CloudFront.)

Stack architecture:

My website stack

View my complete Docker Swarm stack definition.

Code Source

You can find the source code to my website stack at:


(No comments)

Add a new comment