Run Python Web Application in Docker using Nginx and uWsgi

Learn how to configure these components to host a python web application

“Pressure is something you feel when you don’t know what the hell you’re doing.” ― Peyton Manning

1. Introduction

Developing a web application is a major task by itself. It has many pieces that need to fit together – front-end HTML, styling in CSS and browser scripting in Javascript. In addition, the back-end also needs to be developed in a suitable server-side framework.

Once these tasks are completed, your headaches are not over; rather, in one sense they are just beginning. You need to think about the deployment of your application. Again here we have many aspects to consider – the operating system on which the server is hosted, the choice of web server, and how to manage the deployment of these components. This is especially true of companies that implement a “continuous integration” approach: one where changes made to the application code base are continually integrated into live production.

2. Docker for Packaging an Application

In this scenario, Docker offers a flexible and powerful solution to ease deployment woes. By making the deployment a part of the development process, many problems arising at deployment time can be caught at development. This is made possible by the ability of Docker to mock entire data center infrastrucure in a single processes. When a Dockerfile is used to build the infrastructure needed to run the application, each team member can have a virtual runtime environment for testing.

Let us now see how we can manage the deployment of a web application with the back-end in python using Docker. We have chosen to illustrate this process with Nginx as the web server.

3. Deployment Architecture

The server machine runs a version of Ubuntu as the OS. Actually, any Linux OS capable of running Docker will do.

The docker container runs Nginx web server. Nginx has been configured to serve static files such as HTML, CSS and Javascript files. The python web application is managed using uWSGI, which is a high-performance application server running several busy web sites. Requests at a particular URL prefix is redirected by Nginx to uWSGI, so uWSGI need not directly open an internet-facing port.

We have configured Nginx with an SSL certificate too so that HTTPS is supported. Click here for more details on how to SSL-enable Nginx inside Docker..

4. The Dockerfile

We start with Ubuntu Server 16.04, and update the OS. Next a bunch of needed software is installed, including Nginx.

FROM ubuntu:16.04
MAINTAINER Jay Sridhar "jay.sridhar@gmail.com"
RUN DEBIAN_FRONTEND=noninteractive apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get -y dist-upgrade
RUN DEBIAN_FRONTEND=noninteractive apt-get -yq install net-tools nginx python-pip

Next, we install the python components using pip. We also install supervisor to run both Nginx and uWSGI services.

RUN pip install --upgrade pip
RUN pip install uwsgi flask supervisor

We add a non-privileged user called aurora, and cleanup Nginx installation a bit.

RUN useradd -ms /bin/bash aurora && \
    rm -f /etc/nginx/fastcgi.conf /etc/nginx/fastcgi_params && \
    rm -f /etc/nginx/snippets/fastcgi-php.conf /etc/nginx/snippets/snakeoil.conf

We now expose the ports needed for the web: 80 and 443.

EXPOSE 80
EXPOSE 443

Next, we copy Nginx configuration which includes the SSL certificate and appropriate SSL directives. See this post for complete details of the SSL configuration.

COPY nginx/ssl /etc/nginx/ssl
COPY nginx/snippets /etc/nginx/snippets
COPY nginx/sites-available /etc/nginx/sites-available

We also need supervisor configuration and uWSGI configuration. See below where these are explained in detail. Finally, the supervisor daemon is run in the foreground in the docker environment.

COPY etc/supervisord.conf /etc/supervisord.conf
COPY etc/uwsgi/wsgi.ini /etc/uwsgi/wsgi.ini
ENTRYPOINT ["/usr/local/bin/supervisord"]

5. Configuring Nginx

We configure Nginx to redirect all requests starting with the prefix /api to uWSGI. This is done as shown below. Requests for /api are passed on to uWSGI listening on port 3031 on the localhost. Any other requests are handled by Nginx directly (the location block below).

server {
        ...

        location /api {
                uwsgi_pass 127.0.0.1:3031;
                include uwsgi_params;
        }

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
        }

        ...
}

The file uwsgi_params included in the /api location block is shown below. This configuration is required by uWSGI and is not application specific.

uwsgi_param  QUERY_STRING       $query_string;
uwsgi_param  REQUEST_METHOD     $request_method;
uwsgi_param  CONTENT_TYPE       $content_type;
uwsgi_param  CONTENT_LENGTH     $content_length;

uwsgi_param  REQUEST_URI        $request_uri;
uwsgi_param  PATH_INFO          $document_uri;
uwsgi_param  DOCUMENT_ROOT      $document_root;
uwsgi_param  SERVER_PROTOCOL    $server_protocol;
uwsgi_param  REQUEST_SCHEME     $scheme;
uwsgi_param  HTTPS              $https if_not_empty;

uwsgi_param  REMOTE_ADDR        $remote_addr;
uwsgi_param  REMOTE_PORT        $remote_port;
uwsgi_param  SERVER_PORT        $server_port;
uwsgi_param  SERVER_NAME        $server_name;

6. Supervisor

Supervisor is a system used to manage processes at system startup. Here we use it to start both Nginx and uWSGI since both are needed for running the system. It uses a configuration file /etc/supervisord.conf which is shown below. This file tells supervisor to run in the foreground and spawn Nginx and uWSGI.

[supervisord]
nodaemon=true

[program:uwsgi]
command=/usr/local/bin/uwsgi --ini /etc/uwsgi/wsgi.ini --die-on-term
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

7. Setting up uWSGI

uWSGI is a web framework for python. It manages requests to python web modules, and serves as the interface to Nginx. Using uWSGI, you can run back-end python modules with Nginx as the web server.

It is configured as shown. The process listens on the local port 3031 for requests forwarded by Nginx. The main application file is named wsgiapp.py, which can of course pull in other modules that are required. This file is located in /home/aurora/app/ directory.

[uwsgi]
socket = 127.0.0.1:3031
chown = www-data:www-data
uid = www-data
gid = www-data
chdir = /home/aurora/app/
processes = 4
threads = 2
wsgi-file = wsgiapp.py

8. Building and Deployment

Once all these parts are assembled, building the docker image is as simple as:

docker build --rm=true --force-rm=true -t mynginx:latest .

Once the build is complete, run it as follows:

docker run -p 80:80 -p 443:443 -v /home/aurora/server/docker/nginx/src/website:/home/aurora/web -v /home/aurora/server/docker/nginx/src/app:/home/aurora/app mynginx:latest
  • The command maps ports 80 and 443 to the host ports.
  • The static files comprising the web application is mapped as a docker volume to /home/aurora/web/ which is the Nginx web root.
  • The back-end application driven by uWSGI is also mounted as a docker volume at /home/aurora/app.

Once docker is running, you now have a HTTPS web server serving files and running your python application.

For test purposes, the application we have is a very simple one.

def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    return ["Hello from uwsgi"]

Now, go develop a more complete application while enjoying the flexibility provided by docker for deployment!

If you face any problems, let me know in the comments below.

Review

This article outlined the steps required for hosting a web application with a python back-end inside docker. We demonstrated how to configure Nginx for SSL and using uWSGI for python. You can now use the configuration presented here as a template for your own deployment.