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:
- Dockerfile: A text file containing instructions to build a Docker image
- Image: A blueprint for containers, containing your application code and dependencies
- Container: A running instance of an image
- 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
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:
# 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 baseWORKDIR /app
: Sets the working directory inside the containerCOPY requirements.txt .
: Copies requirements file first (for caching purposes)RUN pip install...
: Installs the Python dependenciesCOPY . .
: Copies the rest of the application codeEXPOSE 5000
: Documents that the container listens on port 5000CMD ["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
:
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 versionservices
: Defines the containers to be runflask-app
: Service name for our Flask applicationbuild: .
: Builds the image using the Dockerfile in the current directoryports: - "5000:5000"
: Maps port 5000 of the host to port 5000 of the containervolumes: - .:/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:
# 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:
{
"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:
# In your Dockerfile, add:
ENV FLASK_ENV=production
And in your app.py
, access them with:
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:
# 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
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
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:
-
Use non-root users in your Dockerfile:
dockerfileRUN adduser --disabled-password --gecos '' myuser
USER myuser -
Set appropriate environment variables:
dockerfileENV FLASK_ENV=production
ENV FLASK_DEBUG=0 -
Implement proper logging:
pythonimport logging
logging.basicConfig(level=logging.INFO) -
Use a production-ready WSGI server like Gunicorn:
dockerfileCMD ["gunicorn", "--workers=3", "--bind", "0.0.0.0:5000", "app:app"]
-
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:
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:
- Set up a basic Flask application
- Create a Dockerfile to containerize the application
- Use Docker Compose to define and run the application
- Implement more advanced configurations for real-world scenarios
- 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
- Modify the Dockerfile to use a different Python version.
- Add a Redis container to the docker-compose.yml file and update the Flask app to use it for caching.
- Implement a multi-stage build to reduce the final image size.
- Set up environment-specific configuration files that get loaded based on an environment variable.
- 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! :)