I just Dockerized a fantasy-premier-league web app that uses Flask framework and Bokeh library to display interactive visualizations of various Fantasy Premier League data. It is most certainly not the greatest Docker setup ever to exist, however it is quite a step on my learning path. The app is running live at fantasy.elek.hr in DigitalOcean’s OneClick Docker droplet. Since I didn’t find much material on the internet describing how one could configure such a scenario, I decided to write a post outlining the basic steps I took in order to get it working.
Why would you even do it?
Flask is a popular Python web application (micro)framework. Bokeh is a Python library for interactive visualizations. Together, they make an elegant solution for including interactive plots and dashboards in web applications. A typical Flask+Bokeh setup would consist of Bokeh server which serves interactive visualizations on a single port (usually
5006) and Flask web application which is served at another port (usually
5000) and which embeds visuals from Bokeh server.
Running such solution is a two-step process. Firstly, we would run Bokeh application with
bokeh serve <PATH_TO_BOKEH_APP>, and then the Flask application the usual python way, by executing script containing app’s entrypoint with
This is all elegant and simple in development. However, deploying this app would require installing Bokeh and Flask on target server and running two mentoioned services. Don’t know about you, but the last thing I like doing is having a production environment that is not easily reproducible and that requires performing a set of manual, repetitive, boring steps to setup. I prefer having those things automated. Enter the Docker.
In case you’ve been living under the rock for the past couple of years and thus completely missed the whole containers-devops-hype, Docker is a virtualization technology that makes it easy to package and ship any application in a lightweight container, which can run almost anywhere. This relieves you of the worry about various packages versions and dependencies in your code and in turn enables you to easily set up continuous deployments and continuous integration for your applications. No more late-night hours spent installing all the right versions of all the required tools and packages for some project you just want to try out, no more infamous But, it works on my machine! argument. How wonderful!
Docker on DigitalOcean
When it comes to deploying Docker applications, you can spin up basically any virtual machine, either in cloud or on-prem, install Docker there and deploy away all kinds of applications. But, if you are a developer, and a lazy one at that, as I am, chances are you don’t even want to be thinking about preparing your environment for Docker deploys. You probably just want to deploy your applications somewhere, anywhere, as effortlessly as possible, and want them to simply work. Now we’re talking about abstracting away not only our applications’ dependencies, but those of Docker, too. Well, you can do just that with DigitalOcean’s One-Click Docker Droplet that is essentially an Ubuntu VM with Docker and Docker Compose installed and configured as per official recommendations. They set it up properly, so you don’t have to. Merry Christmas. So, let’s see how we go about deploying Flask web application that displays Bokeh interactive visualizations to DigitalOcean.
Our application consists of two main parts: Bokeh application (in
./bokeh/ folder) and Flask web application (in
./web/ folder) which embeds interactive visuals from Bokeh application.
So, what we’re going to do here is run two Docker containers as one application, one container for each process: Bokeh and Flask. That’s why we’re going to write two Dockerfiles. Then we’re going to make our containers talk to each other by writing a docker compose configuration yml file.
Both our containers are based on
continuumio/miniconda3 image which is based on Python 3.5 and contains bootstrapped installation of Miniconda package manager, as shown on line 1.
On line 3, we install dependencies for our application. Line 5 sets up a volume where we will store our application code. We are exposing port 5006 on which Bokeh will run on line 9, and finally, we’re telling our container to start serving documents by running bokeh executable. Here, we’re passing some parameters:
/app/bokeh/vpc.py is the name of document to serve, and we can have multiple documents served simply by adding them all to the
ENTRYPOINT array. Parameter
allow-websocket-origin will allow our Flask application to access served Bokeh documents. Setting this parameter to
* will actually allow any application to access Bokeh server, and if that’s not something you want (and you probably don’t), you should set it to a more restrictive value. For example, in production, I am only allowing access from my domain fantasy.elek.hr. We can also have any number of allowed origins, just add extra
allow-websocket-origin=something elements to
FROM continuumio/miniconda3 MAINTAINER Antonia Elek <antoniaelek at hotmail.com> RUN conda install -y nomkl bokeh numpy pandas VOLUME '/app' EXPOSE 5006 ENTRYPOINT ["bokeh","serve","/app/bokeh/vpc.py","--allow-websocket-origin=*"]
Dockerfile for Flask application is rather similar, the only notable difference is in
ENTRYPOINT element, where we are now simply running our Flask app from
FROM continuumio/miniconda3 MAINTAINER Antonia Elek <antoniaelek at hotmail.com> RUN conda install -y nomkl bokeh flask pandas VOLUME '/app' EXPOSE 5000 ENTRYPOINT ["python","/app/web/app.py"]
Once we have that, we wire it up by defining our Docker Compose file with two services.
bokehapp service uses container defined in our
bokehapp.Dockerfile and exposes its port 5006 to the local port 5006, where Bokeh documents are being served.
flaskapp service uses container defined in
flaskapp.Dockerfile and maps our current directory to that container’s
/app volume. It also exposes flask-app container’s port 5000 to host port 80, where our application will be available.
Both services use
variables.env environment file, which stores some application-specific environment variables.
version: '2' services: flaskapp: env_file: - 'variables.env' build: context: . dockerfile: flaskapp.Dockerfile volumes: - './:/app' ports: - '80:5000' links: - bokehapp bokehapp: env_file: - 'variables.env' build: context: . dockerfile: bokehapp.Dockerfile ports: - '5006:5006' volumes: - './:/app'
Once we have everything defined, all that is left is to run our app simply by executing
Deploying this to DigitalOcean is as simple as creating a new One-Click Docker droplet from DigitalOcean’s dashboard, SSH-ing into it, fetching source code from GitHub or whatever you use for version control and running the above command.