Deploying Flask on Hop3¶
This guide walks you through deploying a Flask application on Hop3. By the end, you'll have a production-ready Python web application running on your own infrastructure.
Prerequisites¶
Before you begin, ensure you have:
- A Hop3 server - Follow the Installation Guide if you haven't set one up yet
- The Hop3 CLI - Installed on your local machine
- Python 3.10+ - Install from python.org or via your package manager
- pip - Python package manager (comes with Python)
- Git - For version control and deployment
Verify your local setup:
Step 1: Create a New Flask Application¶
Create the project directory and virtual environment:
Activate the virtual environment and install Flask:
Step 2: Create the Application¶
Create the main application file:
import os
from datetime import datetime
from flask import Flask, jsonify
app = Flask(__name__)
# Configuration
app.config['DEBUG'] = os.environ.get('FLASK_DEBUG', 'False').lower() == 'true'
# SECRET_KEY is required in production - no insecure default
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
if not app.config['SECRET_KEY'] and not app.config['DEBUG']:
raise ValueError("SECRET_KEY environment variable is required in production")
@app.route('/')
def index():
"""Welcome page."""
return '''
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Hop3</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.container {
text-align: center;
padding: 2rem;
}
h1 { font-size: 3rem; margin-bottom: 1rem; }
p { font-size: 1.25rem; opacity: 0.9; }
</style>
</head>
<body>
<div class="container">
<h1>Hello from Hop3!</h1>
<p>Your Flask application is running.</p>
<p>Current time: ''' + datetime.now().isoformat() + '''</p>
</div>
</body>
</html>
'''
@app.route('/up')
def up():
"""Health check endpoint for Hop3."""
return 'OK', 200
@app.route('/health')
def health():
"""Detailed health check."""
return jsonify({
'status': 'ok',
'timestamp': datetime.now().isoformat(),
'version': '1.0.0'
})
@app.route('/api/info')
def info():
"""API information endpoint."""
return jsonify({
'name': 'hop3-tuto-flask',
'version': '1.0.0',
'python_version': os.sys.version,
'environment': os.environ.get('FLASK_ENV', 'production')
})
if __name__ == '__main__':
port = int(os.environ.get('PORT', 5000))
app.run(host='0.0.0.0', port=port)
Step 3: Create Requirements File¶
Generate the requirements file:
Verify the requirements:
Step 4: Add Configuration Files¶
Create a configuration module for different environments:
import os
class Config:
"""Base configuration."""
SECRET_KEY = os.environ.get('SECRET_KEY') # Required - no default
DEBUG = False
TESTING = False
# Database
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', 'sqlite:///app.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config):
"""Development configuration."""
DEBUG = True
class ProductionConfig(Config):
"""Production configuration."""
DEBUG = False
# Ensure we have a proper secret key in production
@property
def SECRET_KEY(self):
key = os.environ.get('SECRET_KEY')
if not key:
raise ValueError('SECRET_KEY must be set in production')
return key
class TestingConfig(Config):
"""Testing configuration."""
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig,
'default': DevelopmentConfig
}
Create a WSGI entry point for Gunicorn:
import os
from app import app
if __name__ == '__main__':
port = int(os.environ.get('PORT', 5000))
app.run(host='0.0.0.0', port=port)
Step 5: Verify the Application Works¶
Test that the application starts correctly (skipped in automated tests - local server tests are flaky):
Verify the project structure:
Step 6: Create Deployment Configuration¶
Create a Procfile¶
Create a Procfile in your project root:
# Main web process (using Gunicorn)
web: gunicorn wsgi:app --bind 0.0.0.0:$PORT --workers 2 --threads 4 --worker-class gthread
Create hop3.toml¶
Create a hop3.toml for advanced configuration:
[metadata]
id = "hop3-tuto-flask"
version = "1.0.0"
title = "My Flask Application"
[build]
packages = ["python3", "python3-pip", "python3-venv"]
[run]
start = "gunicorn wsgi:app --bind 0.0.0.0:$PORT --workers 2 --threads 4"
[env]
FLASK_ENV = "production"
PYTHONUNBUFFERED = "1"
[port]
web = 5000
[healthcheck]
path = "/up"
timeout = 30
interval = 60
Verify the deployment files exist:
Step 7: Initialize Git Repository¶
Create a .gitignore file:
# Virtual environment
venv/
.venv/
ENV/
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
*.egg-info/
dist/
build/
# Environment
.env
.env.local
.env.*.local
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db
# Database
*.db
*.sqlite3
# Logs
*.log
# Testing
.pytest_cache/
.coverage
htmlcov/
Initialize the repository:
Step 8: Deploy to Hop3¶
The following steps require a Hop3 server. Set the HOP3_SERVER environment variable to your server address before running these commands.
Configure the CLI¶
If this is your first deployment, initialize Hop3:
Deploy¶
Deploy the application (first deployment creates the app):
Set Environment Variables¶
Set the SECRET_KEY and hostname for the application:
Apply Configuration¶
Redeploy to apply the configuration:
Wait for the application to start:
Step 9: Verify Deployment¶
Check your application status:
View logs:
Open your application:
Managing Your Application¶
Restart the Application¶
Run Commands in the Application Context¶
View and Manage Environment Variables¶
# List all variables
hop3 config:show hop3-tuto-flask
# Set a variable
hop3 config:set hop3-tuto-flask NEW_VARIABLE=value
# Remove a variable
hop3 config:unset hop3-tuto-flask OLD_VARIABLE
Scaling¶
# Check current processes
hop3 ps hop3-tuto-flask
# Scale web workers
hop3 ps:scale hop3-tuto-flask web=2
Advanced Configuration¶
Adding a Database (PostgreSQL with SQLAlchemy)¶
Install SQLAlchemy and PostgreSQL driver:
Update app.py:
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL', 'sqlite:///app.db')
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)
email = db.Column(db.String(120), unique=True, nullable=False)
def __repr__(self):
return f'<User {self.username}>'
@app.route('/health')
def health():
try:
db.session.execute(db.text('SELECT 1'))
db_status = 'connected'
except Exception as e:
db_status = f'error: {str(e)}'
return jsonify({
'status': 'ok',
'database': db_status
})
Create and attach database:
hop3 addons:create postgres hop3-tuto-flask-db
hop3 addons:attach hop3-tuto-flask hop3-tuto-flask-db
Run migrations:
Adding Flask-Migrate for Database Migrations¶
Initialize migrations:
Update Procfile:
Adding Redis for Caching¶
Install Flask-Caching with Redis:
Configure caching:
from flask_caching import Cache
app.config['CACHE_TYPE'] = 'redis'
app.config['CACHE_REDIS_URL'] = os.environ.get('REDIS_URL', 'redis://localhost:6379/0')
cache = Cache(app)
@app.route('/cached')
@cache.cached(timeout=60)
def cached_view():
return jsonify({'cached': True, 'time': datetime.now().isoformat()})
Attach Redis:
hop3 addons:create redis hop3-tuto-flask-redis
hop3 addons:attach hop3-tuto-flask hop3-tuto-flask-redis
Background Tasks with Celery¶
Install Celery:
Create tasks.py:
from celery import Celery
def make_celery(app):
celery = Celery(
app.import_name,
backend=app.config.get('CELERY_RESULT_BACKEND'),
broker=app.config.get('CELERY_BROKER_URL')
)
celery.conf.update(app.config)
return celery
# In app.py
app.config['CELERY_BROKER_URL'] = os.environ.get('REDIS_URL', 'redis://localhost:6379/0')
app.config['CELERY_RESULT_BACKEND'] = os.environ.get('REDIS_URL', 'redis://localhost:6379/0')
celery = make_celery(app)
@celery.task
def send_async_email(email_data):
# Send email
pass
Add worker to Procfile:
Flask Blueprints for Larger Applications¶
For larger applications, organize with blueprints:
# api/routes.py
from flask import Blueprint, jsonify
api_bp = Blueprint('api', __name__, url_prefix='/api')
@api_bp.route('/users')
def get_users():
return jsonify([])
# app.py
from api.routes import api_bp
app.register_blueprint(api_bp)
Request Logging¶
Add request logging middleware:
import logging
from flask import request
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s %(levelname)s: %(message)s'
)
logger = logging.getLogger(__name__)
@app.before_request
def log_request():
logger.info(f'{request.method} {request.path}')
@app.after_request
def log_response(response):
logger.info(f'{request.method} {request.path} - {response.status_code}')
return response
CORS Configuration¶
Install Flask-CORS:
Configure CORS:
from flask_cors import CORS
# SECURITY: Always specify allowed origins explicitly - never use '*' in production
allowed_origins = os.environ.get('ALLOWED_ORIGINS')
if not allowed_origins:
raise ValueError("ALLOWED_ORIGINS must be set (e.g., 'https://example.com,https://app.example.com')")
CORS(app, origins=allowed_origins.split(','))
Troubleshooting¶
Application Won't Start¶
Check the logs for errors:
Common issues:
- Missing SECRET_KEY: Set it with hop3 config:set
- Module not found: Ensure all dependencies are in requirements.txt
- Port binding: Ensure Gunicorn binds to 0.0.0.0:$PORT
Database Connection Issues¶
Verify the database is attached:
Test the connection:
Import Errors¶
Ensure your application structure is correct:
hop3-tuto-flask/
├── app.py
├── wsgi.py
├── config.py
├── requirements.txt
├── Procfile
└── hop3.toml
Gunicorn Workers¶
For CPU-bound applications, increase workers:
For I/O-bound applications, use async workers:
Next Steps¶
- CLI Reference - Complete command reference
- hop3.toml Reference - Full configuration options
- Backup and Restore Guide - Protect your data
Example Files¶
Complete hop3.toml for Flask¶
# hop3.toml - Flask Application
[metadata]
id = "hop3-tuto-flask"
version = "1.0.0"
title = "My Flask Application"
author = "Your Name <you@example.com>"
[build]
packages = ["python3", "python3-pip", "libpq-dev"]
[run]
start = "gunicorn wsgi:app --bind 0.0.0.0:$PORT --workers 2 --threads 4"
before-run = "flask db upgrade"
[env]
FLASK_ENV = "production"
PYTHONUNBUFFERED = "1"
[port]
web = 5000
[healthcheck]
path = "/up"
timeout = 30
interval = 60
[[provider]]
name = "postgres"
plan = "standard"
[[provider]]
name = "redis"
plan = "basic"
Complete Procfile for Flask¶
# Procfile - Flask Application
# Build phase
# Pre-run hooks (database migrations)
prerun: flask db upgrade
# Web server
web: gunicorn wsgi:app --bind 0.0.0.0:$PORT --workers 2 --threads 4 --worker-class gthread
# Background worker (optional)
worker: celery -A app.celery worker --loglevel=info
# Beat scheduler (optional)
beat: celery -A app.celery beat --loglevel=info