Baeldung Pro – Ops – NPI EA (cat = Baeldung on Ops)
announcement - icon

Learn through the super-clean Baeldung Pro experience:

>> Membership and Baeldung Pro.

No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.

Partner – Orkes – NPI EA (cat=Kubernetes)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

1. Overview

When working with Docker, running a Gunicorn server can significantly improve the scalability and performance of applications, for instance, Flask applications. However, transitioning from running a Flask app directly with flask run to running it with Gunicorn can be challenging.

In this tutorial, we present a step-by-step guide for running a Gunicorn server in Docker.

2. Defining Gunicorn

Gunicorn, a Python WSGI HTTP server, enables us to run Flask, Django, or other Python web applications efficiently. To explain, Flask’s built-in development server is not suitable for production since it’s single-threaded, unlike Gunicorn, which can handle multiple requests simultaneously.

Meanwhile, Docker facilitates us with an isolated environment for packaging our application with all its dependencies, ensuring consistency across different environments.

Now, running a Gunicorn Server in Docker alongside our Flask application enables the Flask application to handle multiple requests. So, Gunicorn manages multiple worker processes to handle requests concurrently, offloads WSGI handling from Flask’s built-in server, and we can fine-tune it with worker types, logging, and timeouts.

3. Setting Up Gunicorn Server in Docker

In this section, we create a simple project to show how we can set up and run a Gunicorn server in Docker.

3.1. Setting Up the Flask Application

First, let’s create a simple Flask application:

from flask import Flask, request, jsonify

app = Flask(__name__)

default_message = "Hello, this is a Flask app running on Gunicorn inside Docker!"

@app.route('/', methods=['GET'])
def home():
    return jsonify({"message": default_message})

if __name__ == "__main__":
    app.run(debug=False, host='0.0.0.0', port=8080)

For this example, let’s name the file flaskfile.py. Although we use app.run() for local development, we set Gunicorn as the server when running inside Docker.

3.2. Create the Dockerfile

Next, let’s create the file in the same directory as the Flask application and add the content:

# Use an official Python runtime as a parent image
FROM python:3.9

# Handles specifying the working directory within the container
WORKDIR /code

# Copy the local files to the container
COPY . /code

# Install required dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Configure the Flask application to listen on port 8080
EXPOSE 8080

# Start Gunicorn with 4 worker processes
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8080", "flaskfile:app"]

Here is the breakdown:

  • FROM python:3.9 – defines the base image
  • WORKDIR /code – takes care of setting up the working directory inside the container
  • COPY . /code – copies all the files in our local working directory to code inside the container
  • RUN pip install –no-cache-dir -r requirements.txt – installs dependencies
  • EXPOSE 8080 – opens port 8080 inside the container for the Flask application to listen on
  • CMD [“gunicorn”, “-w”, “4”, “-b”, “0.0.0.0:8080”, “flaskfile:app”] – executes Gunicorn with 4 worker processes while listening on port 8080

To clarify, “flaskfile:app” refers to the app object specified inside the flaskfile.py file.

3.3. Create requirements.txt

In addition, we need to ensure we have the requirements.txt file:

flask
gunicorn

Above, we list all the necessary dependencies.

3.4. Build and Run the Container

Now, let’s build the image:

$ docker build -t my_flask_app .

The command above builds the image my_flask_app based on the instructions specified in the Dockerfile.

After this, let’s run the container:

$ docker run -it -p 8080:8080 my_flask_app

In this command:

  • -it – responsible for running the container in interactive mode
  • -p 8080:8080 – takes care of mapping the host port 8080 to the container port 8080
  • my_flask_app – defines the specific image to use when running the container

At this point, let’s verify that the application runs as expected.

3.5. Verify the Application Is Running

Once the container is up and running, let’s test our Flask API:

$ curl http://localhost:8080/
{"message":"Hello, this is a Flask app running on Gunicorn inside Docker!"}

For the testing, we use curl.

4. Best Practices

Here are some best practices we can consider for production.

Firstly, we can add the –access-logfile flag to enable Gunicorn logging:

CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8080", "--access-logfile", "-", "flaskfile:app"]

The above modification enables us to monitor traffic and troubleshoot issues more easily.

Secondly, we can apply load balancing. We can consider deploying multiple containers behind the reverse proxy, such as Nginx, for larger applications to distribute traffic efficiently across multiple Gunicorn instances.

Further, we can use environment variables to avoid hard-coding configurations, such as ports, for instance, defining the configurations in a .env file.

5. Common Issues and Troubleshooting Tips

Here are a few issues we may encounter while running Gunicorn in Docker and how to resolve them.

So, we may encounter a situation where the port is already in use. In this case, the port 8080 is already occupied on our machine. In this situation, we can map a different host port:

$ docker run -it -p 5000:8080 my_flask_app

As a result of this change, we can access the application at http://localhost:5000/.

At times, the Gunicorn workers may crash when a request takes too long:

CMD ["gunicorn", "-w", "4", "-t", "60", "-b", "0.0.0.0:8080", "--access-logfile", "-", "flaskfile:app"]

Above, we handle requests that take too long by setting the timeout to 60 seconds.

Another issue that’s not uncommon is the application import error. To explain, we need to ensure that we write flaskfile.py file correctly, whereby the app object is defined at the top level:

from flask import Flask

app = Flask(__name__)

This enables Gunicorn to import the app object.

6. Conclusion

In this article, we explored how to run a Gunicorn server in Docker.

So, running a Gunicorn server in Docker provides an efficient way to deploy Python web applications in a production-like environment. To demonstrate, we developed a Flask app running inside Docker with Gunicorn as the WSGI server.

Thus, we can now containerize and scale Python web applications using Gunicorn and Docker.