In the last post, I showed you how you could dockerize a MERN app. The process required configuring three containers and using three different commands to start them. And if you remember, the commands themselves were also quite long. All of this becomes quite troublesome if you have to do it each time you want to start working.
This is where Docker Compose comes into the picture.
To put it in extremely simple terms Docker Compose will provide us two simple commands which we can use to start and stop all the required containers in the desired way. The commands being docker-compose up
and docker-compose down
respectively. Apart from this, we will write a docker-compose.yaml
file which will have the instructions related to the commands we would like to run.
Now that you have an idea of what docker-compose is let's get into action and see how we can write a docker-compose file. I'll be building upon what I talked about in the previous post, so if you haven't checked that out I highly suggest you do.
So let me first show you what our final file would look like and then I'll go on explaining in detail. Do note that the docker compose file is not a replacement for the dockerfiles we wrote earlier. We still would be absolutely needing them to build our images. I'll assume while writing this docker-compose file that our folder structure is something like this:
├── frontend
│ ├── Dockerfile
│ └── otherStuff
├── backend
│ ├── Dockerfile
│ └── otherStuff
├── env
│ ├── backend.env
│ └── mongo.env
└── docker-compose.yaml
If that is the case then our docker-compose.yaml
file would look like this:
version: '3.8'
services:
mongodb:
image: 'mongo'
volumes:
- data:/data/db
env_file:
- ./env/mongo.env
backend:
build: ./backend
ports:
- '80:80'
volumes:
- ./backend:/app
- /app/node_modules
env_file:
- ./env/backend.env
depends_on:
- mongodb
frontend:
build: ./frontend
ports:
- '3000:3000'
volumes:
- ./frontend/src:/app/src
stdin_open: true
tty: true
depends_on:
- backend
volumes:
data:
Before I start to explain this I recommend you go have a look at how our dockerfiles looked in the previous post otherwise you might not be able to understand some stuff. With that let's begin:
The very first thing we specify is the version of Docker Compose we want to use. Do remember that this has nothing to do with the version of our app.
-
Then we specify the services (containers). All services are specified at the same indentation level.
Indentation does matter in
yaml
files so do take care of that We start off by specifying the name of the service and then the other details.
-
If the service is based on an image we're getting from someplace else, like here we get the MongoDB image from Docker Hub, then we use the
image
key else we usebuild
and specify the location of the folder in which the dockerfile we want to build from is present.We specify the path to the folder containing the dockerfile and not the path to the dockefile itself.
-
After that we use the
ports
key to specify the posts we want to open. This is done in the same way as we do while starting our container from the terminal.In the case of MongoDB, we don't need a port since the backend will interact with it via the docker network.
We then specify the
volumes
using the same syntax as we used earlier.-
After that instead of passing environment variables while starting the container like we did earlier, it's better to specify them in a file and then point to the location of that file using the
env_file
key.In the case of the React frontend we don't need any environment variables.
After that we've specified the
depends_on
key which basically tells docker that the container specified in depends on should be up and running before we start the present one.In the frontend you will also notice two additional keys of
tty
andstdin_open
. These are specified because we want the frontend container to run in interactive mode. These basically make up the-it
flag we used earlier while starting the frontend individually.And finally after specifying the services we have to list down all the named volumes we used while specifying volumes in any of our services. This just is something that is required by Docker in order to function. Do note that this only has to be done for the named volumes and not for anonymous volumes and bind mounts.
You might be wondering why we haven't specified or created a docker network anywhere here like we did the last time. That is because we don't need to do that as when using Docker Compose, Docker will automatically create a new environment for all the services specified in the compose file and will add those services (containers) to a network. There does exist a
networks
key however if you want to specify some network your container should be a part of.
And voilà! This might be a lot to comprehend if you're using docker-compose for the first time. I suggest you trying re-reading this and the previous article again in order to better understand using docker-compose. I do hope you were able to learn something useful from this.
Thanks for reading!
If you have any feedback for me or just want to talk feel free to connect with me on Twitter. I'll be more than happy to help you out! :D
EDIT (02/01/2021) :
Series Conclusion
This would be the final post for this series. I'm thankful to everyone who took out the time to read the posts. I hope you all learned something that could help you. This was the first time I actively started writing technical blogs and I am extremely grateful for the response I got. I will be starting another series, on Kubernetes this time. If you liked this one, I'm sure you'll find that interesting too. Thank you all once again! :')
Top comments (2)
Hello Arsh, nice article. I have found that you are interested in docker.
If it sounds interesting for you then please checkout this app get-deck.com
It will be very helpful to have your feedback for us,
Hi! Glad you liked the article. Will give your app a try in the coming days and let you know :)