Back to Scaling Python Applications guides

Containerizing Flask Applications with Docker

Stanley Ulili
Updated on May 8, 2025

Docker lets you package your app and everything it needs, like libraries and tools, into one container so it runs the same anywhere.

When you use it with Flask, a lightweight Python web framework, you get a solid middle ground between running your own servers and using fully managed platforms like Heroku.

Flask works well with Docker because containers are much lighter than virtual machines. They don’t need to simulate a whole computer; they just share the host system’s core while staying isolated. This means fewer environment issues and easier, more reliable deployments.

In this guide, you'll containerize a Flask app with Docker.

Prerequisites

Before you begin, ensure you have the following installed:

  • Python 3.8 or newer
  • Docker Desktop or Engine
  • Basic familiarity with Flask applications

Setting up the project structure

In this section, you'll build a simple Flask app structure that works well with Docker. The setup is straightforward, with just one application file.

Start by creating a new directory for your project and navigating into it:

 
mkdir flask-docker-app && cd flask-docker-app

Next, set up a virtual environment to manage your Python dependencies separately:

 
python3 -m venv venv

Activate the virtual environment:

 
source venv/bin/activate

Install Flask and Gunicorn:

 
pip install flask gunicorn

Create a requirements.txt file to track dependencies:

 
pip freeze > requirements.txt

Now, create a simple app.py file as the single entry point for your application:

app.py
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/')
def index():
    return jsonify({"message": "Welcome to Flask in Docker!"})

@app.route('/health')
def health():
    return jsonify({"status": "healthy"})

if __name__ == "__main__":
    app.run(host="0.0.0.0", debug=True)

This straightforward structure provides:

  • A simple Flask application in a single file
  • A welcome endpoint that returns JSON
  • A health check endpoint for container monitoring
  • Configured to listen on all interfaces (required for Docker)

Let's start by testing our Flask application without Docker. Launch the Flask development server with:

 
flask run
Output
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit

Visit http://127.0.0.1:5000/ in your browser, and you should see a JSON response:

 
{"message": "Welcome to Flask in Docker!"}

Screenshot of flask JSON response in the browser

After verifying it works, stop the server.

Getting started with Docker

In this section, you'll enhance your Flask application by adding Docker. With Docker, your application will gain portability, consistency, and isolation, making it easier to deploy and scale.

First, create a simple Dockerfile in the root of your project:

 
touch Dockerfile

Open the Dockerfile and add the following straightforward configuration:

Dockerfile
FROM python:3.13.3-slim-bookworm

WORKDIR /app

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

# Copy application code
COPY app.py .

EXPOSE 5000

# Run with Gunicorn for production
CMD ["gunicorn", "--bind", "0.0.0.0:4000", "app:app"]

Open the Dockerfile and add the following configuration. This Dockerfile will define how Docker builds your application image.

It starts with a slim Python 3.13.3 base image to keep the image lightweight. The working directory inside the container is set to /app, and it installs dependencies from the requirements.txt file.

It also installs Gunicorn for serving the app in production. The application code, app.py, is copied into the container, and port 5000 is exposed to handle incoming connections. Finally, the application is run with Gunicorn as the production WSGI server, binding it to 0.0.0.0:4000.

Next, create a simple .dockerignore file to prevent unnecessary files from being included:

 
touch .dockerignore

Add the following contents:

.dockerignore
venv/
__pycache__/
*.pyc
.git/
.gitignore
.idea/
.vscode/

Now, build the Docker image:

 
docker build -t flask-app .

After the build completes, run the container:

 
docker run --name flask-container -d -p 4000:4000 flask-app

The -d flag runs the container in detached mode (background), and the -p flag maps port 5000 in the container to port 5000 on your host.

Your Flask application is now running inside a Docker container! Visit http://localhost:4000/ to verify it's working:

Screenshot confirming that the flask application is now running inside a Docker container

This confirms the app is up and running.

Working with container logs

Managing logs is essential when working with containerized applications. Docker automatically captures everything written to standard output (stdout) and standard error (stderr) by your application, making it easy to inspect what's happening inside the container.

You can view the logs of your running container with:

 
docker logs flask-container
Output
[2025-05-08 16:44:18 +0000] [1] [INFO] Starting gunicorn 23.0.0
[2025-05-08 16:44:18 +0000] [1] [INFO] Listening at: http://0.0.0.0:4000 (1)
[2025-05-08 16:44:18 +0000] [1] [INFO] Using worker: sync
[2025-05-08 16:44:18 +0000] [7] [INFO] Booting worker with pid: 7

These logs show the Gunicorn WSGI server starting up successfully. The first line indicates Gunicorn's version, followed by the listening address and port. The third line shows it's using the default synchronous worker type, and the last line confirms a worker process has started with process ID 7.

You can follow the logs in real-time (similar to tail -f) with:

 
docker logs -f flask-container

This is particularly useful during development or when troubleshooting issues, as you'll see log entries appear as they happen.

To see only the most recent entries:

 
docker logs --tail 50 flask-container

And for logs within a specific time range:

 
docker logs --since 2025-05-08T16:00:00 flask-container

When you're done with the container, you need to stop and remove it:

 
docker stop flask-container
 
docker rm flask-container

For simplicity and automatic cleanup, use the --rm flag when starting, which will automatically remove the container when it stops:

 
docker run --name flask-container --rm -d -p 5000:5000 flask-app

This prevents accumulating stopped containers that take up disk space on your system.

Setting up Docker Compose for development

Now that you've successfully containerized your Flask application with Docker, let's improve your development workflow using Docker Compose. This tool simplifies managing Docker applications, especially when you need multiple containers working together.

Before proceeding, make sure to stop your currently running container from the previous section:

 
docker stop flask-container

First, create a docker-compose.yml file in your project root:

 
touch docker-compose.yml

Add the following configuration:

docker-compose.yml
services:
  web:
    build: .
    ports:
      - "4000:5000"
    volumes:
      - ./app.py:/app/app.py
    environment:
      - FLASK_DEBUG=True
    command: python app.py
    restart: unless-stopped

This configuration builds your application using the Dockerfile, maps port 4000 on the host to port 5000 in the container, and mounts the local app.py to the container for live code reloading.

It sets the FLASK_DEBUG environment variable for development, uses Flask's development server by overriding the default command, and ensures the container restarts automatically if it crashes. Start your development environment:

 
docker-compose up

You'll see output showing your application starting up:

Output
 ✔ Network flask-docker-app_default  Created                     0.0s 
 ✔ Container flask-docker-app-web-1  Created                     0.0s 
Attaching to web-1
web-1  |  * Serving Flask app 'app'
web-1  |  * Debug mode: on
web-1  | WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
web-1  |  * Running on all addresses (0.0.0.0)
web-1  |  * Running on http://127.0.0.1:5000
web-1  |  * Running on http://172.18.0.2:5000
web-1  | Press CTRL+C to quit
web-1  |  * Restarting with stat
web-1  |  * Debugger is active!
web-1  |  * Debugger PIN: 666-640-080

Now, you can edit your app.py file locally, and the changes will be reflected immediately in the running container thanks to the volume mapping. Try adding a new route to app.py:

app.py
...
@app.route('/hello')
def hello():
return jsonify({"message": "Hello from Docker Compose!"})
if __name__ == "__main__": app.run(host="0.0.0.0", debug=True)

Save the file and visit http://localhost:4000/hello in your browser:

Screenshot showing that Docker Compose is serving the flask app

You'll see the changes without rebuilding the image or restarting the container.

To run the containers in the background, use the -d flag:

 
docker-compose up -d
Output

[+] Running 1/1
 ✔ Container flask-docker-app-web-1  Started                     0.2s 

View logs from Docker Compose:

 
docker-compose logs

And to shut it down when you're done:

 
docker-compose down

This Docker Compose setup gives you a more flexible development workflow than plain Docker commands and prepares you to add additional services like databases.

Final thoughts

Containerizing your Flask app with Docker ensures consistent deployment and scalability across environments. Docker Compose simplifies managing multi-container applications, making setting up, scaling, and maintaining development and production environments easier.

For more detailed documentation on Docker and Docker Compose, refer to the official Docker documentation and Docker Compose documentation. These resources provide deeper insights into container management, multi-container setups, and best practices for production-ready applications

Author's avatar
Article by
Stanley Ulili
Stanley Ulili is a technical educator at Better Stack based in Malawi. He specializes in backend development and has freelanced for platforms like DigitalOcean, LogRocket, and AppSignal. Stanley is passionate about making complex topics accessible to developers.
Got an article suggestion? Let us know
Next article
Get Started with Job Scheduling in Python
Learn how to create and monitor Python scheduled tasks in a production environment
Licensed under CC-BY-NC-SA

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Make your mark

Join the writer's program

Are you a developer and love writing and sharing your knowledge with the world? Join our guest writing program and get paid for writing amazing technical guides. We'll get them to the right readers that will appreciate them.

Write for us
Writer of the month
Marin Bezhanov
Marin is a software engineer and architect with a broad range of experience working...
Build on top of Better Stack

Write a script, app or project on top of Better Stack and share it with the world. Make a public repository and share it with us at our email.

community@betterstack.com

or submit a pull request and help us build better products for everyone.

See the full list of amazing projects on github