Flask: Containerizing with Docker
Containerizing a Flask application with Docker simplifies deployment, ensures consistency across environments, and enhances scalability. Flask, built on a lightweight core with Jinja2 Templating and Werkzeug WSGI, integrates seamlessly with Docker to package applications, dependencies, and configurations into portable containers. This tutorial covers Flask containerizing with Docker, providing a step-by-step guide to creating, building, and running a Flask app in a Docker container, along with best practices for production-ready deployments.
01. Why Containerize with Docker?
Docker encapsulates a Flask application and its dependencies (e.g., Python, libraries, Gunicorn) into a container, ensuring identical behavior in development, testing, and production. Combined with Flask’s compatibility with Werkzeug WSGI for request handling and Jinja2 Templating for rendering, Docker simplifies deployment to platforms like Kubernetes, AWS ECS, or local servers, while improving portability and isolation.
Example: Basic Flask App
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello, Docker!'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Explanation:
- A minimal Flask app for containerization.
host='0.0.0.0'
ensures the app is accessible outside the container.
02. Prerequisites
Ensure the following are installed:
- Docker: Install Docker Desktop (Windows/Mac) or Docker Engine (Linux) from docker.com.
- Python 3.10+: For local development and testing.
- Git (Optional): For version control.
03. Step-by-Step Guide to Containerizing Flask
This guide walks through creating a Dockerized Flask application, including a production-ready setup with Gunicorn.
3.1 Set Up the Flask Application
Example: Project Setup
# Create project directory
mkdir flask-docker
cd flask-docker
# Create virtual environment and install dependencies
python3 -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
pip install flask gunicorn
pip freeze > requirements.txt
requirements.txt (Generated):
Flask==3.0.3
gunicorn==22.0.0
Jinja2==3.1.4
Werkzeug==3.0.4
...
Explanation:
- Creates a project with Flask and Gunicorn (production WSGI server).
requirements.txt
lists dependencies for Docker.
3.2 Create a Dockerfile
Example: Dockerfile
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV FLASK_ENV=production
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]
Explanation:
FROM python:3.12-slim
: Uses a lightweight Python 3.12 image.WORKDIR /app
: Sets the working directory inside the container.COPY requirements.txt
: Copies dependencies first for caching.RUN pip install
: Installs dependencies.CMD
: Runs Gunicorn to serve the Flask app on port 8000.
3.3 Update Flask App for Production
Example: Production-Ready Flask App
# app.py
from flask import Flask
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-secret')
@app.route('/')
def index():
return 'Hello, Docker!'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))
Explanation:
- Uses environment variables for
SECRET_KEY
andPORT
. - Allows flexibility for local testing and production.
3.4 Build and Run the Docker Container
Example: Build and Run
# Build Docker image
docker build -t flask-docker .
# Run container
docker run -p 8000:8000 -e SECRET_KEY=my-secure-key flask-docker
Access the App:
Open http://localhost:8000 in a browser
Output:
Hello, Docker!
Explanation:
docker build
: Creates an image namedflask-docker
.docker run
: Maps host port 8000 to container port 8000 and sets environment variables.
3.5 Use Docker Compose for Development
Example: docker-compose.yml
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
environment:
- SECRET_KEY=my-secure-key
- FLASK_ENV=development
volumes:
- .:/app
Run with Docker Compose:
docker-compose up --build
Explanation:
docker-compose.yml
: Defines services, ports, and environment variables.volumes
: Mounts the local directory for live code changes in development.
3.6 Add a Database (e.g., PostgreSQL)
Example: Flask with PostgreSQL
# app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-secret')
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
@app.route('/')
def index():
users = User.query.all()
return f'Users: {[user.username for user in users]}'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))
Updated requirements.txt:
pip install flask-sqlalchemy psycopg2-binary
pip freeze > requirements.txt
Updated docker-compose.yml:
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
environment:
- SECRET_KEY=my-secure-key
- DATABASE_URL=postgresql://user:password@db:5432/flaskdb
depends_on:
- db
db:
image: postgres:15
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=flaskdb
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Run and Initialize Database:
docker-compose up --build
# In another terminal
docker-compose exec web flask db init # If using Flask-Migrate
Explanation:
- Adds a PostgreSQL service linked to the Flask app.
depends_on
: Ensures the database starts first.- Persists database data with a Docker volume.
3.7 Common Issues
Example: Port Binding Error
Error: Bind for 0.0.0.0:8000 failed: port is already allocated
Solution:
docker ps # Check running containers
docker stop <container-id>
docker run -p 8000:8000 flask-docker
Explanation:
- Ensure no other process uses port 8000.
- Use
docker logs <container-id>
for debugging.
04. Best Practices
- Minimize Image Size: Use
python:3.12-slim
and--no-cache-dir
for pip. - Environment Variables: Store sensitive data (e.g.,
SECRET_KEY
) in.env
or Docker Compose. - .gitignore: Exclude
venv/
,.env
, and*.pyc
.
# .gitignore
venv/
.env
*.pyc
HEALTHCHECK
in Dockerfile for production.4.1 Multi-Stage Build Example
Example: Multi-Stage Dockerfile
# Build stage
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Production stage
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY . .
ENV FLASK_ENV=production
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]
Explanation:
- Separates build and runtime environments to reduce image size.
- Copies only necessary dependencies.
4.2 Comprehensive Example
Example: Full Flask App with PostgreSQL
# app.py
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-secret')
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
@app.route('/')
def index():
users = User.query.all()
return render_template('index.html', users=users)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))
templates/index.html:
<!DOCTYPE html>
<html>
<head><title>Flask Docker</title></head>
<body>
<h1>Users</h1>
<ul style="padding: 0px 0px 0px 20px; margin-top: 0px;">{% for user in users %}<li>{{ user.username }}</li>{% endfor %}</ul>
</body>
</html>
Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV FLASK_ENV=production
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]
docker-compose.yml:
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
environment:
- SECRET_KEY=my-secure-key
- DATABASE_URL=postgresql://user:password@db:5432/flaskdb
depends_on:
- db
db:
image: postgres:15
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=flaskdb
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Run:
docker-compose up --build
Explanation:
- Combines Flask, PostgreSQL, and Gunicorn in a Dockerized setup.
- Uses Jinja2 Templating for rendering user data.
05. Common Use Cases
5.1 Deploying a Web Application
Example: Web App with Static Files
# app.py
from flask import Flask, render_template
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-secret')
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))
templates/index.html:
<!DOCTYPE html>
<html>
<head><title>Flask Docker</title><link rel="stylesheet" href="/static/style.css"></head>
<body><h1>Dockerized Flask App</h1></body>
</html>
Explanation:
- Serves static files (e.g.,
style.css
) and templates. - Containerized for consistent deployment.
5.2 Deploying an API
Example: Flask API
# app.py
from flask import Flask, jsonify
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-secret')
@app.route('/api/data')
def data():
return jsonify({'message': 'Dockerized Flask API'})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))
Explanation:
- Deploys a RESTful API in a Docker container.
- Scalable with orchestration tools like Kubernetes.
Conclusion
Containerizing Flask applications with Docker, powered by Jinja2 Templating and Werkzeug WSGI, ensures portability, consistency, and scalability. Key takeaways:
- Use a lightweight base image and Gunicorn for production.
- Leverage Docker Compose for multi-container setups (e.g., with PostgreSQL).
- Secure configurations with environment variables.
- Optimize with multi-stage builds and health checks.
Dockerized Flask applications are ready for deployment to any platform, streamlining development and production workflows with minimal overhead!
Comments
Post a Comment