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
yamlfiles 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
imagekey else we use
buildand 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
portskey 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
volumesusing 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
In the case of the React frontend we don't need any environment variables.
After that we've specified the
depends_onkey 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
stdin_open. These are specified because we want the frontend container to run in interactive mode. These basically make up the
-itflag 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
networkskey 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) :
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! :')