Skip to main content

Flask Docker Deployment

Introduction

Deploying Flask applications can sometimes be challenging due to environment inconsistencies between development and production. Docker solves this problem by providing a consistent environment across all stages of your application lifecycle. In this tutorial, we'll learn how to containerize a Flask application using Docker, making it easily deployable anywhere that supports Docker containers.

Docker allows you to package your application along with all its dependencies into a standardized unit called a container. This ensures that your application runs the same way regardless of where it's deployed, eliminating the infamous "it works on my machine" problem.

Prerequisites

Before we begin, make sure you have:

  • Basic knowledge of Flask
  • Docker installed on your system (you can download it from Docker's official website)
  • A simple Flask application ready for deployment

Understanding Docker Concepts

Before diving into the deployment process, let's understand some key Docker concepts:

  1. Dockerfile: A text file containing instructions to build a Docker image
  2. Image: A blueprint for containers, containing your application code and dependencies
  3. Container: A running instance of an image
  4. Docker Compose: A tool for defining and running multi-container Docker applications

Step 1: Setting Up a Basic Flask Application

Let's start with a simple Flask application. Create a new directory for your project and add the following files:

Project Structure

flask-docker-app/
├── app.py
├── requirements.txt
├── Dockerfile
└── docker-compose.yml

app.py

python
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/')
def hello_world():
return jsonify(message="Hello, Docker Flask App!")

@app.route('/health')
def health():
return jsonify(status="OK")

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)

requirements.txt

flask==2.0.1
gunicorn==20.1.0

Step 2: Creating a Dockerfile

The Dockerfile defines how your application will be containerized. Create a file named Dockerfile in your project directory:

dockerfile
# Use Python 3.9 slim image as the base
FROM python:3.9-slim

# Set working directory in the container
WORKDIR /app

# Copy requirements first to leverage Docker cache
COPY requirements.txt .

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

# Copy the rest of the application
COPY . .

# Expose port 5000
EXPOSE 5000

# Command to run the application using Gunicorn
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]

Understanding the Dockerfile

  • FROM python:3.9-slim: Uses the official Python 3.9 slim image as a base
  • WORKDIR /app: Sets the working directory inside the container
  • COPY requirements.txt .: Copies requirements file first (for caching purposes)
  • RUN pip install...: Installs the Python dependencies
  • COPY . .: Copies the rest of the application code
  • EXPOSE 5000: Documents that the container listens on port 5000
  • CMD ["gunicorn"...]: Specifies the command to run when the container starts

Step 3: Creating a Docker Compose File

Docker Compose simplifies the process of running multi-container Docker applications. Create a file named docker-compose.yml:

yaml
version: '3'

services:
flask-app:
build: .
ports:
- "5000:5000"
volumes:
- .:/app
environment:
- FLASK_ENV=development
- FLASK_DEBUG=1

Understanding docker-compose.yml

  • version: '3': Specifies the Docker Compose file version
  • services: Defines the containers to be run
  • flask-app: Service name for our Flask application
  • build: .: Builds the image using the Dockerfile in the current directory
  • ports: - "5000:5000": Maps port 5000 of the host to port 5000 of the container
  • volumes: - .:/app: Mounts the current directory to /app in the container (useful for development)
  • environment: Sets environment variables for development mode

Step 4: Building and Running the Docker Container

Now let's build and run our containerized Flask application:

bash
# Build the Docker image
docker-compose build

# Run the container
docker-compose up

Your Flask application should now be running in a Docker container and accessible at http://localhost:5000.

Expected Output

When you run docker-compose up, you should see output similar to:

Creating network "flask-docker-app_default" with the default driver
Building flask-app
...
Successfully built 1a2b3c4d5e6f
Successfully tagged flask-docker-app_flask-app:latest
...
flask-app_1 | [2023-06-01 12:34:56 +0000] [1] [INFO] Starting gunicorn 20.1.0
flask-app_1 | [2023-06-01 12:34:56 +0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)

Step 5: Testing Your Containerized Application

Open your browser and navigate to http://localhost:5000. You should see:

json
{
"message": "Hello, Docker Flask App!"
}

You can also test the health endpoint by visiting http://localhost:5000/health.

Advanced Docker Deployment Options

Using Environment Variables

For better security and configuration management, use environment variables:

dockerfile
# In your Dockerfile, add:
ENV FLASK_ENV=production

And in your app.py, access them with:

python
import os

# Access environment variable
flask_env = os.environ.get('FLASK_ENV', 'development')

Multi-Stage Builds for Production

For production deployments, you can use multi-stage builds to create smaller, more secure images:

dockerfile
# Build stage
FROM python:3.9-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Production stage
FROM python:3.9-slim
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
COPY . .
EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]

Real-World Example: Flask API with Database

Let's create a more realistic Flask application with a database using Docker Compose:

Updated docker-compose.yml

yaml
version: '3'

services:
flask-app:
build: .
ports:
- "5000:5000"
depends_on:
- db
environment:
- DATABASE_URL=postgresql://postgres:postgres@db:5432/flask_db

db:
image: postgres:13
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=postgres
- POSTGRES_USER=postgres
- POSTGRES_DB=flask_db
ports:
- "5432:5432"

volumes:
postgres_data:

Updated app.py with Database Integration

python
import os
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

class Task(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
completed = db.Column(db.Boolean, default=False)

def to_dict(self):
return {
'id': self.id,
'title': self.title,
'completed': self.completed
}

@app.before_first_request
def create_tables():
db.create_all()

@app.route('/tasks', methods=['GET'])
def get_tasks():
tasks = Task.query.all()
return jsonify([task.to_dict() for task in tasks])

@app.route('/tasks', methods=['POST'])
def create_task():
data = request.get_json()
task = Task(title=data['title'], completed=data.get('completed', False))
db.session.add(task)
db.session.commit()
return jsonify(task.to_dict()), 201

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

Updated requirements.txt

flask==2.0.1
gunicorn==20.1.0
flask-sqlalchemy==2.5.1
psycopg2-binary==2.9.1

Deploying to Production

When deploying to production, consider these best practices:

  1. Use non-root users in your Dockerfile:

    dockerfile
    RUN adduser --disabled-password --gecos '' myuser
    USER myuser
  2. Set appropriate environment variables:

    dockerfile
    ENV FLASK_ENV=production
    ENV FLASK_DEBUG=0
  3. Implement proper logging:

    python
    import logging
    logging.basicConfig(level=logging.INFO)
  4. Use a production-ready WSGI server like Gunicorn:

    dockerfile
    CMD ["gunicorn", "--workers=3", "--bind", "0.0.0.0:5000", "app:app"]
  5. Consider using Docker Swarm or Kubernetes for orchestration

Common Issues and Solutions

Issue: Container Exits Immediately

Solution: Make sure your application doesn't exit after starting. In Flask, this means ensuring that the application runs in the foreground.

Issue: Changes Not Reflecting in Container

Solution: Ensure you're using volumes correctly for development. If changes aren't reflecting, rebuild the container:

bash
docker-compose down
docker-compose build
docker-compose up

Issue: Database Connection Errors

Solution: Check that your service names in docker-compose.yml match what you're using in your connection strings. For the example above, the database host should be db (the service name).

Summary

In this tutorial, we've learned how to:

  1. Set up a basic Flask application
  2. Create a Dockerfile to containerize the application
  3. Use Docker Compose to define and run the application
  4. Implement more advanced configurations for real-world scenarios
  5. Follow best practices for production deployments

Docker provides a powerful way to package and deploy Flask applications consistently across different environments. By containerizing your Flask application, you ensure that it runs the same way in development, testing, and production, making the deployment process more reliable and maintainable.

Additional Resources

Exercises

  1. Modify the Dockerfile to use a different Python version.
  2. Add a Redis container to the docker-compose.yml file and update the Flask app to use it for caching.
  3. Implement a multi-stage build to reduce the final image size.
  4. Set up environment-specific configuration files that get loaded based on an environment variable.
  5. Create a production-ready Docker Compose configuration with appropriate security settings.

By mastering Docker for Flask deployment, you'll have a valuable skill that applies to many modern web development scenarios. Happy containerizing!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)