Minimal Python deployment on Docker with uWSGI

Yoan Blanc
3 min readJun 11, 2017

So, you’ve built a great Python web application using Flask, Django, aiohttp, or Falcon. The next issue you could be facing is probably the setup regarding the deployment. We will explore how to use docker-compose to deploy a WSGI application using uWSGI and NGINX.

Container vs Virtual Environment

As Kelsey Heightower mentioned during his last PyCon keynote: don’t use system Python.

Python has become a central part of many UN*X distributions. Hence you don’t want to mess with your system Python. A Docker container is a great way to have a specific Python version.

Virtual environment for Python are gorgeous for development but you may not need them anymore in your container.

The application

Nothing fancy here, let’s do the WSGI hello world with some fancy f-strings. Next to the application, lives a public directory with a favicon.ico, and a requirements.txt file containing pyfiglet.

from pyfiglet import Figletfiglet = Figlet(font="slant")def application(environ, start_response):
start_response("200 OK", [("Content-Type", "text/plain"),
("Content-Encoding", "utf-8")])
yield figlet.renderText("Hello world!").encode("utf-8")
for k, v in environ.items():
yield f"{k:>20} => {v}".encode("utf-8")

The Python container

I am a great fan of the Alpine Linux distribution for my containers. It’s small, fresh and straightforward.

FROM alpine:3.7EXPOSE 3031
VOLUME /usr/src/app/public
WORKDIR /usr/src/app
RUN apk add --no-cache \
uwsgi-python3 \
python3
COPY . .
RUN rm -rf public/*
RUN pip3 install --no-cache-dir -r requirements.txtCMD [ "uwsgi", "--socket", "0.0.0.0:3031", \
"--uid", "uwsgi", \
"--plugins", "python3", \
"--protocol", "uwsgi", \
"--wsgi", "main:application" ]

During the build, it copies everything, installs the application’s requirements and starts uwsgi using the uwsgi procotol rather than HTTP.

NB: if you wanted to install uwsgi via pip, you would need a C compiler and some libraries making the container much bigger.

The frontend container

All we need is a good nginx setup using the uWSGI protocol to serve from the backend or the static folder.

server {
listen 80;
root /usr/share/nginx/html; location / {
try_files $uri @wsgi;
}
location @wsgi {
include uwsgi_params;
uwsgi_pass backend:3031;
}
}

Docker-compose

Our application is composed of two services: the backend running uwsgi, the frontend running nginx. Nginx loads its configuration from a local file as well as it serves the static files from a local folder.

version: '3'services:
backend:
image: me/backend
build:
context: backend
volumes:
- ./backend/public:/usr/src/app/public
frontend:
image: nginx:1.13-alpine
volumes:
- ./frontend/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./backend/public:/usr/share/nginx/html:ro
depends_on:
- backend
ports:
- 80:80

No local files

Ideally, we could build two self-contained containers and share the public folder using a proper volume. What would be required is to run some kind of collect static command before running the uWSGI command (or running it manually).

Running it

At this point, docker-compose does it all.

$ docker-compose build
$ docker-compose up

Rollbacks

The hard part is dealing with the evolution of your application and being able to roll back to previous versions of the Python code, dependencies, and so on. Running the build command creates docker images, here me/backend:latest. Once you’re happy with your version, tag it.

$ docker tag me/backend:latest me/backend:0.0.1

This way, you’ll be able to roll back to a specific version by changing the docker-compose.yml image used to restart the backend service.

Final words

This small tutorial show a straightforward way of linking a Python WSGI application to a frontend without wasting too much energy in the process.

The weak points are that:

  • you cannot horizontally scale this solution without changing the configuration;
  • uWSGI is not happy because it is running as root;
  • it relies on files on the local file system rather that a docker volume.

Did I miss anything? Drop a comment below.

--

--