Flask and Docker images

Dockerizing Flask With Compose and Machine – From Localhost to the Cloud

by Real Python advanced devops docker flask web-dev

Docker is a powerful tool for spinning up isolated, reproducible application environment containers. This piece looks at just that—how to containerize a Flask app for local development along with delivering the application to a cloud hosting provider via Docker Compose and Docker Machine.

Updates:

  • 03/31/2019: Updated to the latest versions of Docker - Docker client (v18.09.2), Docker compose (v1.23.2), and Docker Machine (v0.16.1) Thanks Florian Dahlitz!
  • 11/16/2015: Updated to the latest versions of Docker - Docker client (v1.9.0), Docker compose (v1.5.0), and Docker Machine (v0.5.0)
  • 04/25/2015: Fixed small typo, and updated the docker-compose.yml file to properly copy static files.
  • 04/19/2015: Added a shell script for copying static files.

Interested in creating a similar environment for Django? Check out this blog post.

Local Setup

Along with Docker (v18.09.2) we’ll be using -

  • Docker Compose (v1.23.2) - previously known as fig - for orchestrating a multi-container application into a single app, and
  • Docker Machine (v0.16.1) for creating Docker hosts both locally and in the cloud.

Follow the directions here and here to install Docker Compose and Machine, respectively.

Running either older Mac OS X or Windows versions, then your best bet is to install Docker Toolbox.

Test out the installs:

Shell
$ docker-machine --version
docker-machine version 0.16.1, build cce350d7
$ docker-compose --version
docker-compose version 1.23.2, build 1110ad01

Next clone the project from the repository or create your own project based on the project structure found on the repo:

├── docker-compose.yml
├── nginx
│   ├── Dockerfile
│   └── sites-enabled
│       └── flask_project
└── web
    ├── Dockerfile
    ├── app.py
    ├── config.py
    ├── create_db.py
    ├── models.py
    ├── requirements.txt
    ├── static
    │   ├── css
    │   │   ├── bootstrap.min.css
    │   │   └── main.css
    │   ├── img
    │   └── js
    │       ├── bootstrap.min.js
    │       └── main.js
    └── templates
        ├── _base.html
        └── index.html

We’re now ready to get the containers up and running. Enter Docker Machine.

Docker Machine

To start Docker Machine, first make sure you’re in the project root and then simply run:

Shell
$ docker-machine create -d virtualbox dev;
Creating CA: /Users/realpython/.docker/machine/certs/ca.pem
Creating client certificate: /Users/realpython/.docker/machine/certs/cert.pem
Running pre-create checks...
(dev) Image cache directory does not exist, creating it at /Users/realpython/.docker/machine/cache...
(dev) No default Boot2Docker ISO found locally, downloading the latest release...
(dev) Latest release for github.com/boot2docker/boot2docker is v18.09.3
(dev) Downloading /Users/realpython/.docker/machine/cache/boot2docker.iso from https://github.com/boot2docker/boot2docker/releases/download/v18.09.3/boot2docker.iso...
(dev) 0%....10%....20%....30%....40%....50%....60%....70%....80%....90%....100%
Creating machine...
(dev) Copying /Users/realpython/.docker/machine/cache/boot2docker.iso to /Users/realpython/.docker/machine/machines/dev/boot2docker.iso...
(dev) Creating VirtualBox VM...
(dev) Creating SSH key...
(dev) Starting the VM...
(dev) Check network to re-create if needed...
(dev) Found a new host-only adapter: "vboxnet0"
(dev) Waiting for an IP...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env dev

The create command setup a “machine” (called dev) for Docker development. In essence, it downloaded boot2docker and started a VM with Docker running. Now just point the Docker client at the dev machine via:

Shell
$ eval "$(docker-machine env dev)"

Run the following command to view the currently running Machines:

Shell
$ docker-machine ls
NAME   ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER     ERRORS
dev    *        virtualbox   Running   tcp://192.168.99.100:2376           v18.09.3

Next, let’s fire up the containers with Docker Compose and get the Flask app and Postgres database up and running.

Docker Compose

Take a look at the docker-compose.yml file:

YAML
version: '3'

services:
  web:
    restart: always
    build: ./web
    expose:
      - "8000"
    links:
      - postgres:postgres
    volumes:
      - web-data:/usr/src/app/static
    env_file: 
      - .env
    command: /usr/local/bin/gunicorn -w 2 -b :8000 app:app

  nginx:
    restart: always
    build: ./nginx
    ports:
      - "80:80"
    volumes:
      - .:/www/static
      - web-data:/usr/src/app/static
    links:
      - web:web

  data:
    image: postgres:latest
    volumes:
      - db-data:/var/lib/postgresql/data
    command: "true"

  postgres:
    restart: always
    image: postgres:latest
    volumes:
      - db-data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

volumes:
  db-data:
  web-data:

Here, we’re defining four services - web, nginx, postgres, and data.

  1. First, the web service is built via the instructions in the Dockerfile within the “web” directory - where the Python environment is setup, requirements are installed, and the Flask app is fired up on port 8000. That port is then forwarded to port 80 on the host environment - e.g., the Docker Machine. This service also adds environment variables to the container that are defined in the .env file.
  2. The nginx service is used for reverse proxy to forward requests either to the Flask app or the static files.
  3. Next, the postgres service is built from the the official PostgreSQL image from Docker Hub, which install Postgres and runs the server on the default port 5432.
  4. Finally, notice how there is a separate volume container that’s used to store the database data, db-data. This helps ensure that the data persists even if the Postgres container is completely destroyed.

Now, to get the containers running, build the images and then start the services:

Shell
$ docker-compose build
$ docker-compose up -d

Tip: You can even run the above commands combined in a single one:

Shell
$ docker-compose up --build -d

Grab a cup of coffee. Or two. Check out the Real Python courses. This will take a while the first time you run it.

We also need to create the database table:

Shell
$ docker-compose run web /usr/local/bin/python create_db.py

Open your browser and navigate to the IP address associated with Docker Machine (docker-machine ip):

Flask app form

Nice!

To see which environment variables are available to the web service, run:

Shell
$ docker-compose run web env

To view the logs:

Shell
$ docker-compose logs

You can also enter the Postgres Shell - since we forward the port to the host environment in the docker-compose.yml file - to add users/roles as well as databases via:

Shell
$ docker-compose run postgres psql -h 192.168.99.100 -p 5432 -U postgres --password

Once done, stop the processes via docker-compose down.

Deployment

So, with our app running locally, we can now push this exact same environment to a cloud hosting provider with Docker Machine. Let’s deploy to a Digital Ocean droplet.

After you sign up for Digital Ocean, generate a Personal Access Token, and then run the following command:

Shell
$ docker-machine create \
-d digitalocean \
--digitalocean-access-token ADD_YOUR_TOKEN_HERE \
production

This will take a few minutes to provision the droplet and setup a new Docker Machine called production:

Shell
Running pre-create checks...
Creating machine...
Waiting for machine to be running, this may take a few minutes...
Machine is running, waiting for SSH to be available...
Detecting operating system of created instance...
Provisioning created instance...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
To see how to connect Docker to this machine, run: docker-machine env production

Now we have two Machines running, one locally and one on Digital Ocean:

Shell
$ docker-machine ls
NAME         ACTIVE   DRIVER         STATE     URL                         SWARM   DOCKER     ERRORS
dev          *        virtualbox     Running   tcp://192.168.99.100:2376           v18.09.3
production   -        digitalocean   Running   tcp://104.131.93.156:2376           v18.09.3

Then set production as the active machine and load the Docker environment into the shell:

Shell
$ eval "$(docker-machine env production)"

Finally, let’s build the Flask app again in the cloud:

Shell
$ docker-compose build
$ docker-compose up -d
$ docker-compose run web /usr/local/bin/python create_db.py

Grab the IP address associated with that Digital Ocean account from the control panel and view it in the browser. If all went well, you should see your app running.

Conclusion

Cheers!

  • Grab the code from the Github repo (star it too!).
  • Comment below with questions.
  • Next time we’ll extend this workflow to include two more Docker containers running the Flask app and incorporate load balancing into the mix. Stay tuned!

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About The Team

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.


Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!