Skip to content

Python Web Applications

To reliably and safely serve a Python web application, such as one built with Flask or another web framework, needs some infrastructure around it. We’ll go through each step in detail, but the quick list is:

  • Select which port(s) your container will expose (and, optionally, publish).
  • Create and configure an Nginx Proxy container.
  • Python WSGI/ASGI Server

Container Setup

By default, our Python containers don’t expose a port. You can change this in the container’s Settings. You can expose ports, and then select whether to publish them. When you save your changes, your container will spend a few seconds restarting.

Nginx Proxy

Unlike our web images, your Python + Miniconda service image needs to be configured with a separate Nginx proxy container to ensure that it serves as expected. This is not hard to do - the container comes with an example configuration for connecting to a NodeJS container, and the same principles apply here.

  1. Create a container running the Nginx Proxy web image, and add an SSH user.

  2. SSH into the container and edit the config file at /container/config/nginx/sites-available/default.

  3. Uncomment the ‘upstream’ portion of the config file.

  4. Change the name of the upstream server from nodejs44 to something relevant to your Python container (for example pyapp). You don’t need to use your container name or labels here.

  5. You’ll see NODE_CONTAINER_HOSTNAME:8080; - change this to show the name of your Python application container and the number of the port that you have opened. (Keep the colon : and semi-colon : in place.)

  6. Uncomment the example line that says proxy_pass http://nodejs44/;

  7. Change nodejs44 to again match your upstream server name - using the same example, it should look like proxy_pass http://pyapp/;.

From your SSH session, run nginx -t to ensure your changes are valid. You want to see this line:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok

You will also see two other messages, Permission denied and configuration file test failed. These are safe to ignore. They happen because because the SSH user in the container does not have permissions over the Nginx service. What’s important is that the syntax is ok. If it is, it’s safe to restart the Nginx Proxy container to apply the new configuration.

If you’d like to understand more about why you saw those two failure messages, read up on SSH User Privileges.

Python WSGI/ASGI Server

There are a number of WSGI (Web Server Gateway Interface) and ASGI (Asynchronous Server Gateway Interface) middleman servers designed to serve Python applications. Gunicorn is the best known option, and it’s recommended by most Python frameworks.

There are plenty of tutorials that outline how to configure Gunicorn (or any other gateway), but there are a few things to keep in mind:

  • If you’re running an application that uses asynchronous calls or function definitions, you should use async-capable workers (such as Uvicorn) with Gunicorn as the master process.
  • Gunicorn will, by default, serve a distinct application per worker instance. This means that if you’re using large datasets that are loaded into memory (such as a large language model), you will probably need to configure shared memory.
  • If you’re using a database, especially using an ORM such as SQLAlchemy, ensure that you’re doing this in a thread-safe way. Django does this by default, Flask-SQLAlchemy solves this problem for you, and you can always use something like SQLAlchemy’s sessionmaker to ensure that sessions are thread-local.

Basic Example Deployment

Generally, you should follow the deployment instructions for your specific framework to configure your gateway server. An example step-by-step deployment for a very simple Flask app is included below.

We’ll assume that:

  • Your application is in /container/application,
  • Your app is a file named myapp.py,
  • The Flask module is called app,
  • Your Conda environment is called flaskapp.

An example myapp.py might look like this:

from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello world!'
  1. SSH into your Python container and activate your Conda environment:

    conda activate flaskapp
  2. Install the gateway package(s) you need alongside the framework, for example:

    conda install gunicorn flask
  3. Edit the /container/config/supervisord.conf file. Either uncomment the example [program:pyapp] block, or create a new one with the settings that you want.

  4. Make sure that your block is executing in the root directory for your application. We recommend leaving this as /container/application and setting your app up in there.

  5. Ensure that the command for your Supervisor program first activates the Conda environment using source, NOT conda (source activate flaskapp), and then runs your application via your WSGI server (python -m gunicorn --workers 2 --bind 0.0.0.0:80 myapp:app). An explanation of what this does:

    • python -m gunicorn tells Python to load the Gunicorn module.
    • --workers 2 tells Gunicorn to load up two service workers. You can change this based on available resources.
    • --bind 0.0.0.0:80 tells Gunicorn to listen to all network traffic on port 80. If you’ve exposed a different port, change this to match.
    • myapp:app tells Guncorn to look for the Python file named myapp.py and load app from it (in our example, app = Flask(__name__))
  6. Chaining these into a single runtime command would make the line look like this:

    command=/bin/bash -c "source activate flaskapp && python -m gunicorn --workers 2 --bind 0.0.0.0:80 myapp:app"
  7. Make any other changes you want to the Supervisor config, and then save them.

  8. Reload Supervisor, either with supervisorctl reload or by restarting the container.

  9. Check the URL that your Nginx Proxy is listening for. Your example app should be live!