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.
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.
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:
docker-compose up -d
and how can we see what's going on while it's running?).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.
We've already mentioned Installing Strapi using Docker. Let's do this together, step by step.
docker-compose.yml
fileSo we want to work with MongoDB, so in Step 1 we click on the MongoDB
tab.
.env
file containing the following:1DATABASE_CLIENT=mongo
2DATABASE_NAME=strapi
3DATABASE_HOST=mongoexample
4DATABASE_PORT=27017
5DATABASE_USERNAME=strapi
6DATABASE_PASSWORD=password
7MONGO_INITDB_ROOT_USERNAME=strapi
8MONGO_INITDB_ROOT_PASSWORD=password
docker-compose.yml
. Here's ours:1version: "3"
2
3services:
4 strapiexample:
5 image: strapi/strapi
6 container_name: strapiexample
7 restart: unless-stopped
8 env_file: .env
9 environment:
10 DATABASE_CLIENT: ${DATABASE_CLIENT}
11 DATABASE_NAME: ${DATABASE_NAME}
12 DATABASE_HOST: ${DATABASE_HOST}
13 DATABASE_PORT: ${DATABASE_PORT}
14 DATABASE_USERNAME: ${DATABASE_USERNAME}
15 DATABASE_PASSWORD: ${DATABASE_PASSWORD}
16 # links:
17 # - mongo:mongo
18 networks:
19 - strapi-app-network
20 volumes:
21 - ./app:/srv/app
22 ports:
23 - "1337:1337"
24
25 mongoexample:
26 image: mongo
27 container_name: mongoexample
28 restart: unless-stopped
29 env_file: .env
30 environment:
31 MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}
32 MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
33 networks:
34 - strapi-app-network
35 volumes:
36 - strapidata:/data/db
37 ports:
38 - "27017:27017"
39
40networks:
41 strapi-app-network:
42 driver: bridge
43
44volumes:
45 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.strapi/strapi
, hot off the Docker Hub with the latest Strapi 3.0.0-beta.19.5 of course.container_name
and restart
mode..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).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!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 explanationstrapiexample
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!
% 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 :).
% 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
:
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!
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
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 }
>
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:
1% 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.
find
and find one
for the content type article
.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:
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 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.