✨ Strapi MCP is now Generally Available - let your agents manage your Strapi content ✨

How tos14 min read

How to run a Strapi dev stack with Docker compose

April 14, 2020Updated on July 13, 2026
How to run a Strapi dev stack with Docker compose

Our community is looking for talented writers who are passionate about our ecosystem (jamstack, open-source, javascript) and willing to share their knowledge/experiences through our Write for the community program.

Docker compose

Introduction

The website monolith is gone. At least, it's going. These days if you're publishing content, it's coming and going from and to multiple places, and it's appearing on multiple devices, motorized by multiple moving parts. Which brings flexibility. And complexity.

Coffee in hand, we sit down to work on a project and stare at that screen. Where is everything? Isn't there any way to keep the flexibility but lose the complexity and the dispersion? Wouldn't it be great to sit down and work on a project, and just, well, bring everything up?

Enter Docker Compose.

Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.

So, for example, with a single command, we can bring up a Strapi Project AND the latest version of MongoDB it needs to run smoothly, and have them both talking together without having to install each one by hand and then do all the configuring just to get some work done.

That's what we're going to do in this tutorial.

Goals

Strapi has great documentation, and includes pointers on Installing Strapi using Docker. By completing this tutorial, we're going to enjoy the experience of seeing how simple that really is, and what it feels like to have Docker Compose your workday as part of our regular development process.

To help that happen, you can follow along with:

Prerequisites

You'll need to have [Docker] and [Docker Compose] installed on your laptop or workstation, whether it's Linux, Mac or Windows. Some helpful links if you're in the middle of doing that:

If you're on a Mac, and you don't feel like installing Docker Desktop, here is a super-cool and recent description of how to install Docker and Docker Compose using homebrew: How to install Docker on MacOS.

Step 0 - Check out the Strapi docs!

We've already mentioned Installing Strapi using Docker. Let's do this together, step by step.

Step 1 - Create a docker-compose.yml file

So we want to work with MongoDB, so in Step 1 we click on the MongoDB tab.

  • First of all let's create an .env file containing the following:
DATABASE_CLIENT=mongo
DATABASE_NAME=strapi
DATABASE_HOST=mongoexample
DATABASE_PORT=27017
DATABASE_USERNAME=strapi
DATABASE_PASSWORD=password
MONGO_INITDB_ROOT_USERNAME=strapi
MONGO_INITDB_ROOT_PASSWORD=password
  • Now, we've taken the liberty of modifying the docker-compose.yml. Here's ours:
version: "3"

services:
  strapiexample:
    image: strapi/strapi
    container_name: strapiexample
    restart: unless-stopped
    env_file: .env
    environment:
      DATABASE_CLIENT: ${DATABASE_CLIENT}
      DATABASE_NAME: ${DATABASE_NAME}
      DATABASE_HOST: ${DATABASE_HOST}
      DATABASE_PORT: ${DATABASE_PORT}
      DATABASE_USERNAME: ${DATABASE_USERNAME}
      DATABASE_PASSWORD: ${DATABASE_PASSWORD}
    #    links:
    #      - mongo:mongo
    networks:
      - strapi-app-network
    volumes:
      - ./app:/srv/app
    ports:
      - "1337:1337"

  mongoexample:
    image: mongo
    container_name: mongoexample
    restart: unless-stopped
    env_file: .env
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
    networks:
      - strapi-app-network
    volumes:
      - strapidata:/data/db
    ports:
      - "27017:27017"

networks:
  strapi-app-network:
    driver: bridge

volumes:
  strapidata:

It makes for the same two containers in the stack, but it's a bit longer, let's go over it:

  • strapiexample service, since we want uniquely named containers on our laptop.
    • image: same one, strapi/strapi, hot off the Docker Hub with the latest Strapi 3.0.0-beta.19.5 of course.
    • We've added the container_name and restart mode.
    • Then, we've added an .env file to avoid hard coding sensitive info in the repo (there's a .env.example file just to make it obvious which variables we need for both containers).
    • We've taken out the links entry cuz the Docker Compose file version 3 reference warns that links is legacy and may go away at any time. We know what that means, so we've replaced it with a [named network] and by placing both containers (strapiexample and mongoexample) on that network, and also by including a networks section at the bottom of the file (along with a named volume for persisting MongoDB data so we never lose it until we want to).
    • volumes for the strapiexample container spec means that when strapi whips up your app automatically when we start things up, the file system is shared in the subdirectory ./app just below the project root where we bring up the stack; effectively mirroring the path /srv/app inside the container. Cool!
    • The ports key simply indicates that the first value ("1337") will be the port number for strapi on our system, while the second value ("1337") reflects the port inside the container. If we wanted to, we could change the first value at will.
  • mongoexample service, everything follows much the same pattern as the strapiexample service (we call the functionality offered by a running container a service). The volumes value bears explanation
    • volumes: while the volumes key for strapiexample contrasts a relative path on the invoking system (i.e. my laptop), ./app with its counterpart inside the container (/srv/app), the volume key for mongoexample has a label instead of a relative path (another change I made to the Strapi docs version; oh well, there's always more than one way to do it!). That's because it's a named volume. See Docker Compose file version 3 reference on this particular topic: "You can mount a host path as part of a definition for a single service, and there is no need to define it in the top level volumes key. But, if you want to reuse a volume across multiple services, then define a named volume in the top-level volumes key. Use named volumes with services, swarms, and stack files.". So if I make another stack, this time with the same strapi container and, say, a front end, they can all use the same volume (declared on the last two lines of the docker compose file).

Phew! OK! Docker Compose file? Check!

Step 2 - Pull down the images from the Docker Hub

% docker-compose pull
Pulling strapiexample ... done
Pulling mongoexample  ... done

This may actually take a little while, depending on your internet connection, but at least it only has to be done once :).

Step 3 - Run the stack!

% docker-compose up -d
Creating network "strapi-docker-composer-example_strapi-app-network" with driver "bridge"
Creating volume "strapi-docker-composer-example_strapidata" with default driver
Creating strapiexample ... done
Creating mongoexample  ... done

So, our containers (strapiexample and mongoexample) have been born, and... they're alive and kicking!

Now, it says "done", but Strapi is getting your backend api machine ready, so a lot is churning away in the two docker containers we've just set in motion. Depending on your workstation, it might take about 5 minutes before you can point your browser at:

unless, like me, you're using docker-machine, in which case you need to find out the ip first:

% docker-machine ip default
192.168.88.888

and then point your browser at that ip instead of localhost :

How can we tell if it's ready?

Let's docker exec into the strapi container and see what's going on:

% docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                      NAMES
7dfdb8c300ed        mongo               "docker-entrypoint.s…"   About a minute ago   Up About a minute   0.0.0.0:27017->27017/tcp   mongoexample
c56195eeddf2        strapi/strapi       "docker-entrypoint.s…"   About a minute ago   Up 39 seconds       0.0.0.0:1337->1337/tcp     strapiexample

docker exec -it strapiexample /bin/bash
root@19480a9d8319:/srv/app# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 15:50 ?        00:00:00 /bin/sh /usr/local/bin/docker-entrypoint.sh strapi develop
root        13     1  0 15:50 ?        00:00:01 node /usr/local/bin/strapi new . --dbclient=mongo --dbhost=mongo --dbport=27017 --dbname=strapi --dbuserna
root        36    13 88 15:51 ?        00:04:12 node /usr/local/bin/yarnpkg install --production --no-optional
root        47     0  2 15:55 pts/0    00:00:00 /bin/bash
root        52    47  0 15:55 pts/0    00:00:00 ps -ef

So strapi new is still going on, and strapi build hasn't even started yet.

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 15:50 ?        00:00:02 node /usr/local/bin/strapi develop
root        47     0  0 15:55 pts/0    00:00:00 /bin/bash
root        83    47  0 16:00 pts/0    00:00:00 ps -ef

So strapi new has finished.

root@19480a9d8319:/srv/app# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 15:50 ?        00:00:04 node /usr/local/bin/strapi develop
root        47     0  0 15:55 pts/0    00:00:00 /bin/bash
root        88     1  0 16:00 ?        00:00:00 /bin/sh -c npm run -s build -- --no-optimization
root        89    88 68 16:00 ?        00:00:00 npm
root        96    47  0 16:00 pts/0    00:00:00 ps -ef

And now build is underway, so it won't be long now.

root@19480a9d8319:/srv/app# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 15:50 ?        00:00:05 node /usr/local/bin/strapi develop
root        47     0  0 15:55 pts/0    00:00:00 /bin/bash
root       113     1 56 16:02 ?        00:00:07 /usr/local/bin/node /usr/local/bin/strapi develop
root       124    47  0 16:02 pts/0    00:00:00 ps -ef

Great! build has cooked up a spanking new Admin UI, we can go ahead and point our browsers there right now!

What about a log file

Yes we can!

% docker-compose logs --tail=all -f | grep strapiexample

Attaching to mongoexample, strapiexample
strapiexample    | Using strapi 3.0.0-beta.19.5
strapiexample    | No project found at /srv/app. Creating a new strapi project
strapiexample    | Creating a new Strapi application at /srv/app.
strapiexample    |
strapiexample    | Creating a project from the database CLI arguments.
strapiexample    | Creating files.
strapiexample    | - Installing dependencies:
strapiexample    | Dependencies installed successfully.
strapiexample    |
strapiexample    | Your application was created at /srv/app.
strapiexample    |
strapiexample    | Available commands in your project:
strapiexample    |
strapiexample    |   yarn develop
strapiexample    |   Start Strapi in watch mode.
strapiexample    |
strapiexample    |   yarn start
strapiexample    |   Start Strapi without watch mode.
strapiexample    |
strapiexample    |   yarn build
strapiexample    |   Build Strapi admin panel.
strapiexample    |
strapiexample    |   yarn strapi
strapiexample    |   Display all available commands.
strapiexample    |
strapiexample    | You can start by doing:
strapiexample    |
strapiexample    |   cd /srv/app
strapiexample    |   yarn develop
strapiexample    |
strapiexample    | Starting your app...
strapiexample    | Building your admin UI with development configuration ...
strapiexample    | Compiling Webpack
strapiexample    | Webpack: Compiled successfully in 1.18m
strapiexample    |
strapiexample    |  Project information
strapiexample    |
strapiexample    | ┌────────────────────┬──────────────────────────────────────────────────┐
strapiexample    | Time Mon Apr 06 2020 21:57:54 GMT+0000 (Coordinated 
strapiexample    | Launched in 10378 ms
strapiexample    | Environment development
strapiexample    | Process PID 97
strapiexample    | Version 3.0.0-beta.19.5 (node v12.13.0)                  │
strapiexample    | └────────────────────┴──────────────────────────────────────────────────┘
strapiexample    |
strapiexample    |  Actions available
strapiexample    |
strapiexample    | One more thing...
strapiexample    | Create your first administrator 💻 by going to the administration panel at:
strapiexample    |
strapiexample    | ┌─────────────────────────────┐
strapiexample    | http://localhost:1337/admin
strapiexample    | └─────────────────────────────┘
strapiexample    |
strapiexample    | [2020-04-06T21:57:54.706Z] debug HEAD /admin (30 ms) 200
strapiexample    | [2020-04-06T21:57:54.726Z] info Opening the admin panel...
strapiexample    | [2020-04-06T21:57:54.748Z] info File created: /srv/app/extensions/users-permissions/config/jwt.json
strapiexample    | [2020-04-06T21:58:08.248Z] debug GET /admin (21 ms) 200
strapiexample    | [2020-04-06T21:58:08.545Z] debug GET runtime~main.94ab5055.js (171 ms) 200
strapiexample    | [2020-04-06T21:58:08.552Z] debug GET main.e9e3876e.chunk.js (115 ms) 200
strapiexample    | [2020-04-06T21:58:09.712Z] debug GET /favicon.ico (4 ms) 200
strapiexample    | [2020-04-06T21:58:10.504Z] debug GET /users-permissions/init (38 ms) 401
strapiexample    | [2020-04-06T21:58:10.620Z] debug GET /users/me (35 ms) 401
strapiexample    | [2020-04-06T21:58:10.668Z] debug GET login (31 ms) 200
strapiexample    | [2020-04-06T21:58:12.277Z] debug GET /users-permissions/init (58 ms) 200
strapiexample    | [2020-04-06T21:58:12.471Z] debug GET /admin/init (33 ms) 200
strapiexample    | [2020-04-06T21:58:12.708Z] debug GET 2ff0049a00e47b56bffc059daf9be78b.png (92 ms) 200
strapiexample    | [2020-04-06T21:58:12.711Z] debug GET 6301a48360d263198461152504dcd42b.svg (26 ms) 200
strapiexample    | [2020-04-06T21:58:12.738Z] debug GET cccb897485813c7c256901dbca54ecf2.woff2 (24 ms) 200
strapiexample    | [2020-04-06T21:58:13.135Z] debug GET bd03a2cc277bbbc338d464e679fe9942.woff2 (391 ms) 200
strapiexample    | [2020-04-06T21:58:13.189Z] debug GET 8b4f872c5de19974857328d06d3fe48f.woff2 (39 ms) 200
strapiexample    | [2020-04-06T21:58:13.249Z] debug GET 33d5f0d956f3fc30bc51f81047a2c47d.woff2 (58 ms) 200
strapiexample    | [2020-04-06T21:58:13.263Z] debug GET b15db15f746f29ffa02638cb455b8ec0.woff2 (52 ms) 200
strapiexample    | [2020-04-06T21:58:13.366Z] debug GET 6301a48360d263198461152504dcd42b.svg (101 ms) 200

Now let's actually do something!

After creating an administrator, create a content type, article, in the usual interactive way, and an initial content item. Then see how to get into the containers if we want to and see where everything is. Also, if you leave the log running in its terminal, you can see all the usual Strapi log messages as we go.

Let's dive into the database container:

% docker exec -it mongoexample /bin/bash
root@7afecba98c09:/# mongo strapi -u strapi -p --authenticationDatabase admin
MongoDB shell version v4.2.5
Enter password:
connecting to: mongodb://127.0.0.1:27017/strapi?authSource=admin&compressors=disabled&gssapiServiceName=mongodb

> show collections
articles
core_store
strapi_administrator
strapi_webhooks
upload_file
users-permissions_permission
users-permissions_role
users-permissions_user
> db.articles.find()
{ "_id" : ObjectId("5e8b3b26845382009a59ffa7"), "title" : "First article", "body" : "## Oh yes\n\nA first article!", "published" : "2020-04-07", "createdAt" : ISODate("2020-04-06T14:22:30.063Z"), "updatedAt" : ISODate("2020-04-06T14:22:30.063Z"), "__v" : 0 }
>

Bringing it down, bringing it up again, starting and stopping...

This stops the app (both containers), deletes both containers, deletes all the images, and deletes the volume (our data!) as if nothing has happened here:

% docker-compose down --rmi all -v

If we just want to stop the containers without destroying them and be able to start them again later, in our project root, we do:

% docker-compose stop
Stopping mongoexample  ... done
Stopping strapiexample ... done

Then, to get back to work:

% docker-compose start
Starting strapiexample ... done
Starting mongoexample  ... done
% docker-compose ps
 Name               Command               State            Ports
--------------------------------------------------------------------------
mongo    docker-entrypoint.sh mongod      Up      0.0.0.0:27017->27017/tcp
strapi   docker-entrypoint.sh strap ...   Up      0.0.0.0:1337->1337/tcp

Access the log again (start is much quicker than the initial up command):

% docker-compose logs --tail=all -f | grep strapiexample

Attaching to mongoexample, strapiexample
strapiexample    | Starting your app...
strapiexample    |
strapiexample    |  Project information
strapiexample    |
strapiexample    | ┌────────────────────┬──────────────────────────────────────────────────┐
strapiexample    | Time Mon Apr 06 2020 22:45:46 GMT+0000 (Coordinated 
strapiexample    | Launched in 10041 ms
strapiexample    | Environment development
strapiexample    | Process PID 16
strapiexample    | Version 3.0.0-beta.19.5 (node v12.13.0)                  │
strapiexample    | └────────────────────┴──────────────────────────────────────────────────┘
strapiexample    |
strapiexample    |  Actions available
strapiexample    |
strapiexample    | Welcome back!
strapiexample    | To manage your project 🚀, go to the administration panel at:
strapiexample    | http://localhost:1337/admin
strapiexample    |
strapiexample    | To access the server ⚡️, go to:
strapiexample    | http://localhost:1337
strapiexample    |
strapiexample    | [2020-04-06T22:45:51.371Z] debug GET admin (46 ms) 200
strapiexample    | [2020-04-06T22:45:51.613Z] debug GET runtime~main.94ab5055.js (162 ms) 200
strapiexample    | [2020-04-06T22:45:51.620Z] debug GET main.e9e3876e.chunk.js (32 ms) 200
strapiexample    | [2020-04-06T22:45:52.743Z] debug GET /favicon.ico (4 ms) 200
strapiexample    | [2020-04-06T22:45:53.627Z] debug GET /users-permissions/init (183 ms) 200
strapiexample    | [2020-04-06T22:45:53.682Z] debug GET /admin/init (36 ms) 200
strapiexample    | [2020-04-06T22:45:53.882Z] debug GET /content-manager/content-types (105 ms) 200
strapiexample    | [2020-04-06T22:45:54.371Z] debug GET fb30313e62e3a932b32e5e1eef0f2ed6.png (200 ms) 200
strapiexample    | [2020-04-06T22:45:54.413Z] debug GET 6a7a177d2278b1a672058817a92c2870.png (81 ms) 200
strapiexample    | [2020-04-06T22:45:54.478Z] debug GET fd508c879644dd6827313d801776d714.png (104 ms) 200
strapiexample    | [2020-04-06T22:45:54.533Z] debug GET 47ffa3adcd8b08b2ea82d3ed6c448f31.png (112 ms) 200
strapiexample    | [2020-04-06T22:45:54.625Z] debug GET 75a60ac7a1a65ca23365ddab1d9da73e.png (137 ms) 200
strapiexample    | [2020-04-06T22:45:54.672Z] debug GET a4a1722f025b53b089ca2f6408abf0b7.png (133 ms) 200
strapiexample    | [2020-04-06T22:45:54.685Z] debug GET d6d70dd7cb470ff10ecc619493ae7205.png (95 ms) 200
strapiexample    | [2020-04-06T22:46:17.213Z] debug GET /content-type-builder/components (28 ms) 200
strapiexample    | [2020-04-06T22:46:17.257Z] debug GET /content-type-builder/content-types (40 ms) 200
strapiexample    | [2020-04-06T22:46:17.368Z] debug GET 3.79d3ee8f.chunk.js (34 ms) 200
strapiexample    | [2020-04-06T22:46:17.625Z] debug GET 4eb103b4d12be57cb1d040ed5e162e9d.woff2 (52 ms) 200
strapiexample    | [2020-04-06T22:47:05.883Z] debug POST /content-type-builder/content-types (380 ms) 201
strapiexample    | [2020-04-06T22:47:05.895Z] info The server is restarting
strapiexample    |
strapiexample    |

Give things a minute to get back into shape, then a refresh on the browser will have us back to work again. And our content type and first article is still there!

This will survive a reboot! So no worries.

What about the API?

Conclusion

We hope we've demystified the whole process of dockerizing a Strapi based project stack, and that the commands are beginning to feel like old friends.

There's still more stuff we need to share, so in the coming days and weeks we hope to bring more articles, videos and code on the following:

  • How to use the API instead of the interactive Admin UI for simple CRUD operations.
  • How to setup docker-compose in this way for a project that is already underway.
  • Development workflow examples (dev, staging, production workflows).

And as one article was not enough, Victor reproduced this tutorial on Youtube just for you!

Enjoy!

This article was written by Victor Kane, a member of the Strapi community and a writer in the Write for the community program. If you want to participate, you can get more information here.

We invite you to follow Victor on Twitter! Victor Kane (Owner/Worker @AWebFactory)

Please note: Since we initially published this blog post, we released new versions of Strapi and tutorials may be outdated. Sorry for the inconvenience if it's the case. Please help us by reporting it here.


Get started with Strapi by creating a project, using a starter or trying our instant live demo. Also, consult our forum if you have any questions. We will be there to help you.

See you soon!

Victor KaneOwner @ AWebFactory

Victor has been an active member of the Strapi community from the early stages. He has enjoyed great success in mentoring and in helping clients to meet their web app development challenges.

How tos·12 min read

How To Build A Static Blog Using Jekyll And Strapi

Learn to build a fast Jekyll static blog powered by Strapi v5 CMS. Step-by-step guide covering setup, API integration, and deployment.

·August 3, 2025
Build a Blog with Astro, Strapi, and Tailwind CSS
How tos·22 min read

How to Build a Blog with Astro, Strapi, and Tailwind CSS

In this tutorial, we will learn how to build a blogging application using Strapi as the CMS and Astro powered by React to build the frontend.

·October 20, 2023
Build a CRUD App with Flutter and Strapi
How tos·12 min read

How to Build a Simple CRUD Application Using Flutter & Strapi

In this tutorial, we will learn how to build a Create, Retrieve, Update and Delete (CRUD) application using Flutter and Strapi.

·August 23, 2023