Running Strapi in Docker | The Right Way to Think About It
Why containerizing your Strapi project isn't just a DevOps checkbox – it's what keeps the whole team sane, from local dev to production deployments.
Team Skalafy
Contributor
Every Strapi project eventually reaches a common roadblock. It works beautifully on your local machine. Then a colleague clones it, and after an hour or so of wrestling with Node versions, they get a different database configuration and a broken admin panel. Finally, you try to deploy it and realize it has absolutely nothing to do with how it looked during development.
Docker fixes this, and it fixes it well. If you containerize Strapi, you’re essentially setting up the whole environment once, including Node versions, database configurations, and everything else. Everyone on your team gets exactly that. There’s no need for documentation on how to set things up, or worrying about different versions, or having to deal with “it works on my machine.”
What works on your local machine will work everywhere. The image you create locally is the same one that will run on staging and production.
Development and Production Need Separate Images
One thing the Strapi documentation is quite clear on: you should not share the same Docker image for development and production. The rationale for this has to do with how Strapi works. The admin panel is a React app, and it gets compiled and bundled up into the Strapi build during the build phase. Certain environment variables, like your API URL, actually get baked into the bundle during this phase.
In development, you want hot-reload, you want the Content-Type Builder plugin enabled, and you want to run strapi develop. In production, you want to run a compiled, optimized version of Strapi, you want to run strapi start, and you want the Content-Type Builder plugin disabled. Two different behaviors, two different Docker images.
# Development Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
EXPOSE 1337
CMD ["yarn", "develop"]
docker-compose Handles the Full Stack
Strapi requires a database. In development, this is typically PostgreSQL. Instead of the developer having to install Postgres locally, docker-compose can start the whole stack of Strapi and the database as linked containers. This can all be started with one command.
# docker-compose.yml
version: "3"
services:
strapi:
build: .
ports:
- "1337:1337"
env_file: .env
depends_on:
- postgres
volumes:
- ./src:/app/src
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: strapi
POSTGRES_USER: strapi
POSTGRES_PASSWORD: strapi
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
The volumes section is important in this section. The pgdata section allows you to persist data between container restarts, so if you didn’t include this, you’d lose all data every time you restarted the stack.
Environment Variables Stay Out of the Image
Never store credentials in a Docker image! Your database passwords, JWT keys, and API tokens should all be in a .env file that you pass into the container, rather than baking them into the image itself.
# .env - never committed, never copied into the image
DATABASE_CLIENT=postgres
DATABASE_HOST=postgres
DATABASE_PORT=5432
DATABASE_NAME=strapi
DATABASE_USERNAME=strapi
DATABASE_PASSWORD=your_password_here
JWT_SECRET=your_jwt_secret
ADMIN_JWT_SECRET=your_admin_jwt_secret
APP_KEYS=key1,key2,key3,key4
API_TOKEN_SALT=your_token_salt
And in your .dockerignore, make sure .env is excluded. You don’t want it ending up inside the image - especially one that might get pushed to a registry.
# .dockerignore
.env
node_modules/
.tmp/
.cache/
build/
What This Looks Like in Practice
When this is all in place, a new developer comes in and clones the repository, copies the .env.example, renames it to .env, puts in their local values, and runs docker-compose up. That is it! No Node installation, no database setup, no troubleshooting section. The whole stack is up in under a minute.
And on the production side, all this discipline is paying off too! The Dockerfile for the production environment runs yarn build prior to starting the server, so the admin panel is compiled with the correct environment variables for production. The image is immutable, so it behaves the same regardless of whether it is on a VPS, cloud, or Kubernetes.
This is where Docker and Strapi shine, I think: it is not just that it is easy to set up locally, it is that there is a clear and direct line between what is being developed and deployed locally and what is deployed in production.
When things break in production, there is no guessing between environment and code, there is only debugging on the actual code!
Further Reading
Share this article
Written by Team Skalafy
Contributor at Skalafy
Passionate about building performant, scalable applications with modern technologies. Specializing in DevOps.