Develop a Python Flask Application with Docker and Deploy it to AWS – Part 3 Bring it together with docker compose

Jan 19, 2019

Recap

Up until this point we covered:

  •  Building a custom docker container using the official python miniconda container as our base.
  •  Interactively running commands in our custom image
  •  Giving docker build time instructions and run time instructions with RUN and CMD.
  • Spinning up a webserver that we can access on our own computers by exposing ports
  •  Mounting volumes and directories in our container for real time development.

Now we will use docker-compose to do all these things, but do them even better and with style.

Get the Code

Clone the github repo to see the full code.

Turbo Charge your Docker Dev with Docker-Compose

What is Docker Compose?

Now, I'm going to discuss an incredibly powerful method of connecting and building our docker containers using docker-compose. Docker compose is simply a layer on top of docker that allows us to compose our containers functionality into services, along with additional features such as easily connecting our services and allowing them to talk to one another, declaring dependencies, such as don't bring up my web app until the database is up, and adding configuration parameters such as environmental variables, ports, and file mounts.

If you haven't already, install docker-compose by following the official instructions here. If you installed Docker Desktop for Mac or Windows there is no need for this, it's there already.

What amazing things can I do with docker-compose?

Because this is my blog and I CAN, I'm going to spend a bit of time discussing just how amazing using docker is to deploy your applications. You can use docker-compose to mix and match any number of technologies, from python or node applications, to databases, spark clusters, celery job queues, messaging systems, etc, all without needing to install or configure any of these technologies on your local computer. As a result deployment becomes a breeze (most of the time!), whether you are deploying to a remote server on digital ocean, an EC2 instance on AWS, or another cloud platform such as Google Cloud or Azure. No matter the destination the underlying concepts and technologies remain the same!

Enough talk! CODE!

For this particular application we will 'only' be deploying a single docker service. In later posts I hope to go over how to connect multiple services together. 

  version: '3'
   
  services:
  flask-app-server:
  build:
  context: flask_app
  dockerfile: Dockerfile
  ports:
  - "5000:5000"
  volumes:
  - ./flask_app:/home/flask/flask_app:Z

Docker Compose - Version

This is important. You MUST declare a version, and it must be have quotation marks around it. As of now the newest version is version 3. If you're trying to connect to any other APIs, such as deploying docker-compose directly on AWS, make sure that you are using a compliant version.

Docker Compose - Services

Now, this is where we can really start to build out our application. Each docker container becomes a service. Generally, each container encapsulates a very specific function. That could be a database, a web server, serve a tensorflow model, etc. The point is that your container has one domain, and it does that one thing very well. 

In this instance our only service is our flask app. Notice how everything we did previously, building the container with a specific build context, exposing ports, and mounting directories, we declare right in the service. This is amazing because I hate remembering stuff, and I love having a single point of truth for my entire application.

Here we are creating a volume from our host computer to our container for development purposes, but sometimes you will simply want named volumes to persist data.

Docker Compose - Networks and Volumes

I'm not covering it here, but you can also specify networks and volumes. If you hate networking (like me!!!), you very rarely need to worry about this as docker-compose takes care of it for you, and creates a network for your services. 

If you want to get more in depth with networking I suggest the official docs.

If you want to persist data, such as saving a database even when restarting the containers, you can use volumes. We are not persisting any data here, but it is a super handy feature I hope to go over later. Check out the official docs ​here.​​​

Bring up our Flask App with Docker-Compose

This is where the magic happens! Once you have your application all ready, and you have your docker-compose.yml file ready, you can bring it all up with a single command.

docker-compose up --build -d
Bash

That's it! It's not particularly impressive, because we only have 1 service, but imagine if we had 5, or 10. Instead of having to remember all the ports and commands and run each individually, we simply tell docker to remember it all for us.

Run the same curl command we did in the last post to ensure it is up and working correctly.

Also notice the -d flag, telling docker-compose to run this as a daemon in the background.

curl -X POST \
http://localhost:5000/health \
-H 'Cache-Control: no-cache' \
-H 'Content-Type: application/json' \
-d '{"hello": "world"}'
Bash

It should just spit '{"hello":"world"}' right back at you. If you want to go really crazy, make some changes to the flask app, write your own endpoints, and try it again.

Docker Compose Candy

Like I said earlier, docker-compose is a layer on top of docker. Everything you can do with docker, you can do with docker-compose.

As a quick aside, running all these commands is dependent upon staying in the same working directory, or its sub directories,  as the docker-compose file

.

Docker-Compose: List

To see all current services running as a part of this docker-compose instance, run docker-compose images.

docker-compose images
Container                                           Repository                        Tag       Image Id      Size
-----------------------------------------------------------------------------------------------------------------------------------------
deploy-a-python-flask-app-to-aws_flask-app-server_1   deploy-a-python-flask-app-to-aws_flask-app-server   latest   6deaa1009714   1.15 GB
Bash

You will notice that we don't have to specify a tag for our services, it is already taken care of as ${current_working_directory}_${service_name}. In this example the current working directory is deploy-a-python-flask-app-to-aws, and our service name is flask-app-server. So our container is named deploy-a-python-flask-app-to-aws_flask-app-server. You will also notice a _N appended to each container. We can use docker to deploy multiple instances of the same container, but don't worry about that for now.

Try running docker images, and see what you get.

 

Docker-Compose: Run

Just as we ran an interactive shell in docker in Part 1, we can do that with docker-compose.

Now, this can be a bit tricky to work with, because we have to remember the super handy way docker-compose names our containers for us as ${current_working_directory}_${service_name}. When we want to run a command interactively using docker-compose, we only use the ${service_name}, because it already knows about the current working directory. If we want to run our command directly using docker, which normally we don't, we would use the full container name,  ${current_working_directory}_${service_name}.

Run docker-compose exec flask-app-server-bash​​​​

docker-compose exec flask-app-server bash
(flask-app) flask@387ecccf6018:~/flask_app$
Bash

Alternately, drop into the shell using just plain docker.

docker run -it deploy-a-python-flask-app-to-aws_flask-app-server bash
(flask-app) flask@197cd623e6a2:~/flask_app$
Bash

Docker-Compose: Restart

If, for some reason, you need to restart the server (you think your job queue is stuck, for instance), you can restart the entire docker-compose instance.

docker-compose restart
Bash

Docker-compose: Stop

If you want to stop your docker-compose instance, perhaps to shut off your computer or work on some other project, just run: 

docker-compose stop
Bash

Docker-Compose: RM

CAUTION - DO NOT USE THESE COMMANDS UNLESS YOU ACTUALLY WANT TO REMOVE THE INSTANCE!

#Remove the containers
docker-compose rm
#Remove containers and any volumes
docker-compose rm -v
#Force remove containers (no prompt)
docker-compose rm -f
Bash

Wrap Up

Hopefully, now you have a good idea of:

  •  How to add an individual Dockerfile to a docker-compose configuration.
  •  How to start a docker-compose instance
  •  How containers are named when created with docker build vs docker-compose up
  •  How docker commands (run, list) relate to docker-compose commands

Bioinformatics Solutions on AWS Newsletter 

Get the first 3 chapters of my book, Bioinformatics Solutions on AWS, as well as weekly updates on the world of Bioinformatics and Cloud Computing, completely free, by filling out the form next to this text.

Bioinformatics Solutions on AWS

If you'd like to learn more about AWS and how it relates to the future of Bioinformatics, sign up here.

We won't send spam. Unsubscribe at any time.