Deploy Flask Website On EC2 With Docker Compose [Part 1]
Recently I have been following this awesome Flask tutorial to build a microblog. The author does a great job breaking things down, and I was able to follow step by step from hello world to containerizing my web app. However, one cruicial step is left as an exercise for the reader to figure out, which is to organize everything with docker-compose, as well as actually deploying the web app on AWS. I took a stab at it and got it working, checkout my microblog here. Looking back, it was easy but not very straight forward. So I thought I will write down what I did and what I learned for future references. Here we go!
Docker Compose
I followed along the Flask tutorial all the way up to the deployment on docker containers chapter, while skipping some chapters like translation and full text search. But either way if you follow the tutorial close enough, you will end up having a web app and a mysql database. In the deployment on docker container chapter, they will be turned into containers, with a microblog:latest image and a public mysql image. And here is where docker-compose come in. Docker-compose is a tool that was developed to help define and share multi-container applications. This means that if you are managing more than one container and their communications, docker-compose can make it easier for you. For example, without docker-compose, the way to get the web app up and running is by running the docker run command with several arguments
1 | $ docker run --name microblog -d -p 8000:5000 --rm -e SECRET_KEY=my-secret-key \ |
This feels cumbersome to me, as I will need to remember the format and arguments, or turn this into a bash script. As you make changes and add more containers, it quickly becomes messy. With docker-compose, you can specify all these commands in a single nicely formatted docker-compose.yml
. Here’s an example docker-compose.yml
that spins up two containers, one for the web app and one for the database, then link them together so they can communicate:
1 | version: '2' |
Let’s step through this line by line:
- version: ‘2’ specifies the docker-compose version being used. Newer version includes newers functionalities.
- services: specifies the services and their corresponding configurations. Think of them each as a container. For the microblog app we will have a web app container and a mysql container, thus we are looking at 2 services.
- The flask service specifies the image to use, which is the
microblog:latest
, and maps the docker host’s port 8000 to the container’s port 5000. It also takes an environment variable files namedvariables.env
. The links section specifies that the flask service should have access to the dbserver service, and will be able to refer to it with the name dbserver within the flask container. Lastly, thedepends_on
section indicates that the flask service needs to wait for the dbserver service to start first. - The dbserver section is mostly similar to the flask section, except it takes the environment variables as arguments in the envrionment section instead of taking in a env file, and it has a volumes section which specifies a named volume for where the database in the container is stored. The nice thing about the named volume is that it will persist even when the container is torn down, and it is easily migrated to a different container if needed.
- The bottom volumes section declares the shared volumes that can be accessed by both services. The flask service needs to be able to access this volume as well since it needs to be able to retrieve user data from the database stored in the volume.
Pretty straight forward and well organized, don’t you think? Once the docker-compose.yml
file is completed, save it in the same directory as the Dockerfile
. And simply do docker-compose up
to start the web app.
As a bonus, there was mentioned of using a Nginx server as a reverse proxy for extra security in the comment section. After researching around online, I found that it is a good idea to have a Nginx layer that sits in front of the web app, since it can serve as an extra layer, as well as a load balance that directs traffic. So I have decide to add a Nginx service to the docker-compose file to make the web app more secure.
Here’s the finished docker-compose.yml
with Nginx service added:
1 | version: '2' |
Here we introduced two new major change:
- Instead of links, we switched to using networks. The networks section defines a network for services to communicate within. The services in the same network have access to each other and can reference each other using the service name. This is easier than specifying links between multiple services, and also the fact that links is deprecated makes network the obvious choice.
- The Nginx section binds two ports: 80:80 for http requests, and 443:443 for https request. And now the flask service is opening its 5000 port for the Nginx server to communicate with. But where in the file did we instruct Nginx to communicate with flask service on port 5000? This is actually reflected in the volumes section of Nginx service, where a nginx.conf file is mounted from the ./nginx.conf on docker host to /etc/nginx/nginx.conf in the docker container. And the
nginx.conf
file looks like this:
1 | events {} |
The nginx.conf
file specifies which port the Nginx server is listening at, and how to handle the incoming traffic. The first server section is handling http request at port 80, where the second server section is handling https request at port 443. The http://flask:5000
line indicates the traffic should be directed to 5000 port of flask service.
With the new docker-compose.yml
, now when you spin up your service, you will have a Nginx reverse proxy for your web app, and you can access the web app from port 80. How exciting!
This post covered the docker-compose part of the deployment. In part two we will continue on to explain how to deploy this web app on an AWS EC2 machine.