Skip to content

Deploying a Flask App

What is Flask?

Flask is a lightweight, flexible Python web framework for building web applications and REST APIs. Known for its minimalist philosophy and extensibility, Flask provides just the essentials—routing, request handling, and templating—allowing developers to choose and integrate additional libraries based on their specific needs.

Key features include:

  • Lightweight and unopinionated design
  • Built-in development server and debugger
  • Jinja2 templating engine for rendering HTML
  • URL routing with URL building
  • Request/response handling and context management
  • Cookie and session management
  • WebSocket support with Flask-SocketIO
  • Database integration with SQLAlchemy
  • Form handling with Flask-WTF
  • Authentication and authorization
  • RESTful API development with Flask-RESTful or Blueprint patterns
  • Blueprint-based application structure
  • Extension ecosystem (Flask-SQLAlchemy, Flask-Login, Flask-CORS, etc.)
  • Middleware support
  • Static file serving
  • Error handling and custom error pages
  • Testing utilities
  • Command-line interface (Flask CLI)

Flask is ideal for building web applications, RESTful APIs, microservices, real-time applications with WebSockets, content management systems, data dashboards, and rapid prototyping of web projects.

Prerequisites

Before deploying a Flask application to Klutch.sh, ensure you have:

  • Python 3.9+ installed on your local machine
  • pip or conda for dependency management
  • Git and a GitHub account
  • A Klutch.sh account with dashboard access
  • Basic understanding of Python web development
  • Optional: PostgreSQL or other database server for data persistence

Getting Started with Flask

Step 1: Create Your Project Directory and Virtual Environment

Terminal window
mkdir my-flask-app
cd my-flask-app
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate

Step 2: Install Flask and Essential Dependencies

Terminal window
pip install flask flask-sqlalchemy flask-cors python-decouple gunicorn

Key packages:

  • flask: The web framework
  • flask-sqlalchemy: SQLAlchemy integration for database operations
  • flask-cors: Cross-Origin Resource Sharing support
  • python-decouple: Environment variable management
  • gunicorn: WSGI HTTP server for production

Step 3: Create Your Flask Application

Create app.py:

from flask import Flask, render_template, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from decouple import config
from datetime import datetime
import os
# Configuration
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = config(
'DATABASE_URL',
default='sqlite:///app.db'
)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['JSON_SORT_KEYS'] = False
db = SQLAlchemy(app)
# CORS configuration
CORS(app, origins=config('ALLOWED_ORIGINS', default='http://localhost:3000,http://localhost:5000').split(','))
# Models
class Item(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False, index=True)
description = db.Column(db.String(500))
price = db.Column(db.Integer, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'description': self.description,
'price': self.price,
'created_at': self.created_at.isoformat()
}
# Create tables
with app.app_context():
db.create_all()
# Routes
@app.route('/health', methods=['GET'])
def health_check():
"""Health check endpoint for monitoring."""
return jsonify({
'status': 'healthy',
'service': 'flask-app'
}), 200
@app.route('/items', methods=['GET'])
def list_items():
"""Get list of items with pagination."""
skip = request.args.get('skip', 0, type=int)
limit = request.args.get('limit', 10, type=int)
if limit > 100:
limit = 100
items = Item.query.offset(skip).limit(limit).all()
return jsonify([item.to_dict() for item in items]), 200
@app.route('/items/<int:item_id>', methods=['GET'])
def get_item(item_id):
"""Get a single item by ID."""
item = Item.query.get(item_id)
if not item:
return jsonify({'error': 'Item not found'}), 404
return jsonify(item.to_dict()), 200
@app.route('/items', methods=['POST'])
def create_item():
"""Create a new item."""
data = request.get_json()
if not data or not data.get('name'):
return jsonify({'error': 'Name is required'}), 400
item = Item(
name=data['name'],
description=data.get('description', ''),
price=data.get('price', 0)
)
db.session.add(item)
db.session.commit()
return jsonify(item.to_dict()), 201
@app.route('/items/<int:item_id>', methods=['PUT'])
def update_item(item_id):
"""Update an existing item."""
item = Item.query.get(item_id)
if not item:
return jsonify({'error': 'Item not found'}), 404
data = request.get_json()
if 'name' in data:
item.name = data['name']
if 'description' in data:
item.description = data['description']
if 'price' in data:
item.price = data['price']
db.session.commit()
return jsonify(item.to_dict()), 200
@app.route('/items/<int:item_id>', methods=['DELETE'])
def delete_item(item_id):
"""Delete an item."""
item = Item.query.get(item_id)
if not item:
return jsonify({'error': 'Item not found'}), 404
db.session.delete(item)
db.session.commit()
return '', 204
@app.route('/stats', methods=['GET'])
def get_stats():
"""Get application statistics."""
total_items = Item.query.count()
total_value = db.session.query(db.func.sum(Item.price)).scalar() or 0
return jsonify({
'total_items': total_items,
'total_value': total_value / 100, # Convert cents to dollars
'average_price': (total_value / total_items / 100) if total_items > 0 else 0
}), 200
# Error handlers
@app.errorhandler(404)
def not_found(error):
return jsonify({'error': 'Endpoint not found'}), 404
@app.errorhandler(500)
def internal_error(error):
return jsonify({'error': 'Internal server error'}), 500
if __name__ == '__main__':
port = int(os.getenv('PORT', 5000))
app.run(host='0.0.0.0', port=port, debug=False)

Step 4: Create a Requirements File

Terminal window
pip freeze > requirements.txt

Your requirements.txt should contain:

flask==3.0.0
flask-sqlalchemy==3.1.1
flask-cors==4.0.0
python-decouple==3.8
gunicorn==21.2.0
sqlalchemy==2.0.23

Step 5: Test Locally

Create a .env file for local development:

DATABASE_URL=sqlite:///app.db
ALLOWED_ORIGINS=http://localhost:5000,http://localhost:3000
FLASK_ENV=development

Run the application:

Terminal window
flask run --host 0.0.0.0 --port 5000

Test the health endpoint:

Terminal window
curl http://localhost:5000/health

Create a sample item:

Terminal window
curl -X POST http://localhost:5000/items \
-H "Content-Type: application/json" \
-d '{"name": "Sample Item", "description": "A test item", "price": 9999}'

Deploying Without a Dockerfile

Klutch.sh uses Nixpacks to automatically detect and build your Flask application from your source code.

Prepare Your Repository

  1. Initialize a Git repository and commit your code:
Terminal window
git init
git add .
git commit -m "Initial Flask app commit"
  1. Create a .gitignore file:
venv/
__pycache__/
*.pyc
*.pyo
*.egg-info/
.env
.DS_Store
*.db
*.sqlite3
.flask_session/
instance/
  1. Push to GitHub:
Terminal window
git remote add origin https://github.com/YOUR_USERNAME/my-flask-app.git
git branch -M main
git push -u origin main

Deploy to Klutch.sh

  1. Log in to Klutch.sh dashboard.

  2. Click “Create a new project” and provide a project name.

  3. Inside your project, click “Create a new app”.

  4. Repository Configuration:

    • Select your GitHub repository containing the Flask app
    • Select the branch to deploy (typically main)
  5. Traffic Settings:

    • Select “HTTP” as the traffic type
  6. Port Configuration:

    • Set the internal port to 5000 (the port the Flask app listens on)
  7. Environment Variables: Set the following environment variables in the Klutch.sh dashboard:

    • DATABASE_URL: Your PostgreSQL connection string (e.g., postgresql://user:password@postgres-host:5432/flask_db)
    • ALLOWED_ORIGINS: CORS allowed origins (e.g., https://example-app.klutch.sh,https://myapp.example.com)
    • FLASK_ENV: Set to production for production deployments
    • PYTHONUNBUFFERED: Set to 1 to ensure Python output is logged immediately
  8. Build and Start Commands (Optional): If you need to customize the build or start command, set these environment variables:

    • BUILD_COMMAND: Default runs pip install -r requirements.txt
    • START_COMMAND: Default is gunicorn app:app --workers 4 --bind 0.0.0.0:$PORT --timeout 120

    For example, to run database migrations before starting:

    START_COMMAND=gunicorn app:app --workers 4 --bind 0.0.0.0:$PORT
  9. Region, Compute, and Instances:

    • Choose your desired region for optimal latency
    • Select compute resources (Starter for prototypes, Pro/Premium for production)
    • Set the number of instances (start with 1-2 for testing, scale as needed)
  10. Click “Create” to deploy. Klutch.sh will automatically build your application using Nixpacks and deploy it.

  11. Once deployment completes, your app will be accessible at example-app.klutch.sh.

Verifying the Deployment

Test your deployed app:

Terminal window
curl https://example-app.klutch.sh/health

You should receive:

{
"status": "healthy",
"service": "flask-app"
}

Deploying With a Dockerfile

If you prefer more control over your build environment, you can provide a custom Dockerfile. Klutch.sh automatically detects and uses a Dockerfile in your repository’s root directory.

Create a Multi-Stage Dockerfile

Create a Dockerfile in your project root:

# Build stage
FROM python:3.11-slim as builder
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Runtime stage
FROM python:3.11-slim
WORKDIR /app
# Install runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 \
curl \
&& rm -rf /var/lib/apt/lists/*
# Copy Python dependencies from builder
COPY --from=builder /root/.local /root/.local
# Set PATH to use pip from builder
ENV PATH=/root/.local/bin:$PATH
ENV PYTHONUNBUFFERED=1
# Copy application code
COPY . .
# Create non-root user for security
RUN useradd -m -u 1000 flask_user && \
chown -R flask_user:flask_user /app
USER flask_user
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5000/health || exit 1
# Expose port
EXPOSE 5000
# Start the application
CMD ["gunicorn", "app:app", "--workers", "4", "--bind", "0.0.0.0:5000", "--timeout", "120"]

Deploy the Dockerfile Version

  1. Push your code with the Dockerfile to GitHub:
Terminal window
git add Dockerfile
git commit -m "Add Dockerfile for custom build"
git push
  1. Log in to Klutch.sh dashboard.

  2. Create a new app:

    • Select your GitHub repository and branch
    • Set traffic type to “HTTP”
    • Set the internal port to 5000
    • Add environment variables (same as Nixpacks deployment)
    • Click “Create”
  3. Klutch.sh will automatically detect your Dockerfile and use it for building and deployment.


Database Configuration

PostgreSQL Setup

PostgreSQL is the recommended database for Flask applications. To use a PostgreSQL instance with Klutch.sh:

  1. Deploy a PostgreSQL instance on Klutch.sh (from the marketplace)
  2. Get the connection details from the PostgreSQL dashboard
  3. Set the DATABASE_URL environment variable:
    postgresql://user:password@postgres-host:5432/flask_db

Update your app.py to use the DATABASE_URL:

from decouple import config
app.config['SQLALCHEMY_DATABASE_URI'] = config('DATABASE_URL', default='sqlite:///app.db')

SQLite for Development

For local development and testing:

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'

SQLite is not recommended for production use on Klutch.sh due to lack of concurrent write support.


Working with Flask-SQLAlchemy

Creating Models

Define your database models in a separate file models.py:

from datetime import datetime
from app import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False, index=True)
email = db.Column(db.String(120), unique=True, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
posts = db.relationship('Post', backref='author', lazy=True, cascade='all, delete-orphan')
def to_dict(self):
return {
'id': self.id,
'username': self.username,
'email': self.email,
'created_at': self.created_at.isoformat()
}
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'content': self.content,
'user_id': self.user_id,
'created_at': self.created_at.isoformat()
}

Database Migrations

Use Flask-Migrate for managing database schema changes:

Terminal window
pip install flask-migrate

Initialize migration support:

from flask_migrate import Migrate
migrate = Migrate(app, db)

Run migrations:

Terminal window
flask db init
flask db migrate -m "Initial migration"
flask db upgrade

Blueprints for Modular Application Structure

Organize your Flask app using Blueprints:

Create blueprints/api.py:

from flask import Blueprint, request, jsonify
from models import Item, db
api_bp = Blueprint('api', __name__, url_prefix='/api')
@api_bp.route('/items', methods=['GET'])
def list_items():
items = Item.query.all()
return jsonify([item.to_dict() for item in items]), 200
@api_bp.route('/items', methods=['POST'])
def create_item():
data = request.get_json()
item = Item(
name=data['name'],
description=data.get('description', ''),
price=data.get('price', 0)
)
db.session.add(item)
db.session.commit()
return jsonify(item.to_dict()), 201
@api_bp.route('/items/<int:item_id>', methods=['GET'])
def get_item(item_id):
item = Item.query.get(item_id)
if not item:
return jsonify({'error': 'Item not found'}), 404
return jsonify(item.to_dict()), 200

Register the blueprint in your main app.py:

from blueprints.api import api_bp
app.register_blueprint(api_bp)

Static Files and Templates

Serving Static Files

Flask automatically serves files from the static directory:

my-flask-app/
├── app.py
├── static/
│ ├── css/
│ │ └── style.css
│ └── js/
│ └── app.js
├── templates/
│ ├── index.html
│ └── layout.html
└── requirements.txt

Reference static files in templates:

<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/app.js') }}"></script>

Jinja2 Template Rendering

Create templates/index.html:

<!DOCTYPE html>
<html>
<head>
<title>Flask App</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h1>Welcome to Flask on Klutch.sh</h1>
{% if items %}
<ul>
{% for item in items %}
<li>{{ item.name }} - ${{ item.price / 100 }}</li>
{% endfor %}
</ul>
{% else %}
<p>No items found.</p>
{% endif %}
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
</body>
</html>

Render templates in your routes:

from flask import render_template
@app.route('/')
def index():
items = Item.query.all()
return render_template('index.html', items=items)

WebSockets with Flask-SocketIO

Add real-time communication to your Flask app:

Terminal window
pip install flask-socketio python-socketio python-engineio

Update app.py:

from flask_socketio import SocketIO, emit, join_room, leave_room
socketio = SocketIO(app, cors_allowed_origins='*')
@socketio.on('connect')
def handle_connect():
print(f'Client connected: {request.sid}')
emit('response', {'data': 'Connected to server'})
@socketio.on('disconnect')
def handle_disconnect():
print(f'Client disconnected: {request.sid}')
@socketio.on('message')
def handle_message(data):
print(f'Received message: {data}')
emit('response', {'data': data}, broadcast=True)
@socketio.on('join')
def handle_join(data):
room = data['room']
join_room(room)
emit('response', {'data': f'Joined room: {room}'}, room=room)
@socketio.on('leave')
def handle_leave(data):
room = data['room']
leave_room(room)
emit('response', {'data': f'Left room: {room}'}, room=room)
if __name__ == '__main__':
port = int(os.getenv('PORT', 5000))
socketio.run(app, host='0.0.0.0', port=port, debug=False)

Update Dockerfile CMD for SocketIO:

CMD ["gunicorn", "--worker-class", "eventlet", "-w", "1", "--bind", "0.0.0.0:5000", "app:app"]

Authentication with Flask-Login

Implement user authentication:

Terminal window
pip install flask-login werkzeug

Update app.py:

from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from werkzeug.security import generate_password_hash, check_password_hash
login_manager = LoginManager(app)
login_manager.login_view = 'login'
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(255))
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@app.route('/login', methods=['POST'])
def login():
data = request.get_json()
user = User.query.filter_by(username=data['username']).first()
if user and user.check_password(data['password']):
login_user(user)
return jsonify({'status': 'logged in'}), 200
return jsonify({'error': 'Invalid credentials'}), 401
@app.route('/logout', methods=['POST'])
@login_required
def logout():
logout_user()
return jsonify({'status': 'logged out'}), 200
@app.route('/protected')
@login_required
def protected():
return jsonify({'user': current_user.username}), 200

Environment Variables and Configuration

Essential Environment Variables

Configure these variables in the Klutch.sh dashboard:

VariableDescriptionExample
DATABASE_URLPostgreSQL connection stringpostgresql://user:pass@host:5432/db
ALLOWED_ORIGINSCORS allowed originshttps://example-app.klutch.sh
FLASK_ENVFlask environmentproduction
PYTHONUNBUFFEREDEnable unbuffered logging1
SECRET_KEYSession encryption keyAuto-generate with openssl rand -hex 32

Customization Environment Variables (Nixpacks)

For Nixpacks deployments:

VariablePurposeExample
BUILD_COMMANDBuild commandpip install -r requirements.txt
START_COMMANDStart commandgunicorn app:app --workers 4 --bind 0.0.0.0:$PORT

Persistent Storage for Files and Logs

Adding Persistent Volume

  1. In the Klutch.sh app dashboard, navigate to “Persistent Storage” or “Volumes”
  2. Click “Add Volume”
  3. Set the mount path: /app/logs (for application logs) or /app/uploads (for uploaded files)
  4. Set the size based on your needs (e.g., 10 GB for logs, 50 GB for user uploads)
  5. Save and redeploy

File Uploads

Handle file uploads in Flask:

from werkzeug.utils import secure_filename
import os
UPLOAD_FOLDER = os.getenv('UPLOAD_FOLDER', '/app/uploads')
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return jsonify({'error': 'No file provided'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'error': 'No file selected'}), 400
if not allowed_file(file.filename):
return jsonify({'error': 'File type not allowed'}), 400
filename = secure_filename(file.filename)
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(filepath)
return jsonify({'filename': filename, 'path': filepath}), 201

Logging to Persistent Storage

Update your Flask app to log to a file:

import logging
import os
log_dir = os.getenv('LOG_DIR', '/app/logs')
os.makedirs(log_dir, exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(os.path.join(log_dir, 'flask.log')),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)

Custom Domains

To serve your Flask application from a custom domain:

  1. In the Klutch.sh app dashboard, navigate to “Custom Domains”
  2. Click “Add Custom Domain”
  3. Enter your domain (e.g., api.example.com)
  4. Follow the DNS configuration instructions provided
  5. Update ALLOWED_ORIGINS to include your custom domain

Example DNS configuration:

api.example.com CNAME example-app.klutch.sh

Update environment variable:

ALLOWED_ORIGINS=https://api.example.com,https://example-app.klutch.sh

Monitoring and Logging

Application Logging

Configure structured logging in your Flask app:

import logging
from logging.handlers import RotatingFileHandler
import os
if not app.debug:
if not os.path.exists('/app/logs'):
os.mkdir('/app/logs')
file_handler = RotatingFileHandler(
'/app/logs/flask.log',
maxBytes=10240000,
backupCount=10
)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Flask app startup')

Request Logging

Add request/response logging with a middleware:

@app.before_request
def log_request():
app.logger.info(f'{request.method} {request.path}')
@app.after_request
def log_response(response):
app.logger.info(f'Response status: {response.status_code}')
return response

Health Checks

The included health check endpoint is already configured:

@app.route('/health', methods=['GET'])
def health_check():
return jsonify({
'status': 'healthy',
'service': 'flask-app'
}), 200

Access metrics from the Klutch.sh dashboard to monitor response times, error rates, memory usage, and CPU utilization.


Security Best Practices

  1. Never commit secrets: Use environment variables for sensitive data
  2. HTTPS only: Always use HTTPS in production
  3. CORS configuration: Restrict origins to trusted domains
  4. Input validation: Validate and sanitize all user input
  5. SQL injection prevention: Always use SQLAlchemy ORM with parameterized queries
  6. CSRF protection: Enable CSRF protection with Flask-WTF
  7. Password hashing: Use werkzeug for secure password hashing
  8. HTTPS redirects: Enable HTTPS enforcement
  9. Security headers: Set proper security headers
  10. Dependency updates: Keep dependencies updated for security patches

Example security configuration:

from flask_talisman import Talisman
Talisman(app, force_https=True)
@app.after_request
def set_security_headers(response):
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
return response

Troubleshooting

Issue 1: Database Connection Errors

Problem: “Connection refused” or “database not found” errors.

Solution:

  • Verify DATABASE_URL is correctly configured
  • Ensure PostgreSQL instance is running and accessible
  • Check database user has proper permissions
  • Test connection with a database client
  • Verify firewall rules allow the connection

Issue 2: CORS Errors in Frontend

Problem: Frontend requests receive CORS errors.

Solution:

  • Add frontend origin to ALLOWED_ORIGINS environment variable
  • Verify CORS middleware is properly configured in Flask
  • Check request includes correct Origin header
  • Ensure credentials are properly handled if needed

Issue 3: Static Files Not Loading

Problem: CSS, JavaScript, or image files return 404 errors.

Solution:

  • Verify files exist in the static/ directory
  • Use url_for('static', filename='path/to/file') in templates
  • Check static file paths are correct
  • Ensure Flask app is serving static files from correct directory

Issue 4: Application Crashes on Startup

Problem: Application fails during startup with runtime errors.

Solution:

  • Check application logs in Klutch.sh dashboard
  • Verify environment variables are set correctly
  • Test application locally with same environment variables
  • Check database migrations have run successfully
  • Verify all required dependencies are in requirements.txt

Issue 5: Memory or Performance Issues

Problem: Application uses excessive memory or responds slowly.

Solution:

  • Reduce worker count: lower --workers parameter in gunicorn
  • Implement caching for frequently accessed data
  • Add database query optimization and indexing
  • Use connection pooling for database connections
  • Scale instances if consistently performance-constrained

Best Practices for Production Deployment

  1. Use Multiple Gunicorn Workers: Scale with multiple worker processes

    gunicorn app:app --workers 4 --bind 0.0.0.0:5000
  2. Implement Input Validation: Validate all user input

    from flask import request
    if not request.json or 'name' not in request.json:
    return jsonify({'error': 'Invalid input'}), 400
  3. Enable CORS Selectively: Only allow trusted origins

    CORS(app, origins=['https://example-app.klutch.sh', 'https://example.com'])
  4. Use Database Connection Pooling: Configure SQLAlchemy pooling

    app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
    'pool_size': 20,
    'pool_recycle': 3600,
    'pool_pre_ping': True
    }
  5. Implement Structured Logging: Log important events for debugging

    app.logger.info(f'Processing request: {request.id}')
  6. Add Request Timeouts: Prevent long-running requests

    # In Gunicorn command
    gunicorn app:app --timeout 120
  7. Use Content Compression: Enable gzip compression

    Terminal window
    pip install flask-compress
    from flask_compress import Compress
    Compress(app)
  8. Implement API Rate Limiting: Protect against abuse

    Terminal window
    pip install flask-limiter
    from flask_limiter import Limiter
    limiter = Limiter(app)
    @app.route('/api/endpoint')
    @limiter.limit('100/hour')
    def api_endpoint():
    return jsonify({'data': 'value'})
  9. Use Environment-Specific Configuration: Separate configs for dev/prod

    class Config:
    DEBUG = False
    TESTING = False
    class DevelopmentConfig(Config):
    DEBUG = True
    class ProductionConfig(Config):
    DEBUG = False
  10. Test Thoroughly: Write and run comprehensive tests

    Terminal window
    pip install pytest pytest-flask
    pytest tests/

Resources


Conclusion

Deploying Flask applications to Klutch.sh provides a reliable platform for building and running web applications and APIs. Flask’s flexibility and extensive ecosystem make it an excellent choice for projects of any scale, from simple prototypes to complex production systems.

Key takeaways:

  • Use Nixpacks for quick deployments with automatic Python detection
  • Use Docker for complete control over the build and runtime environment
  • Leverage SQLAlchemy ORM for safe database operations
  • Implement proper CORS and security headers for production
  • Use Gunicorn with multiple workers for production deployments
  • Configure persistent storage for logs and file uploads
  • Monitor application health and performance through Klutch.sh dashboard
  • Keep dependencies updated for security and performance
  • Test applications locally before deploying to production
  • Implement structured logging for debugging and monitoring

For additional help, refer to the Flask documentation or Klutch.sh support resources.