Build a Blog with Flask

Stack: Python + Flask + SQLAlchemy

Flask Blog Tutorial: Build a Python-Powered Content Management System

Learn Flask by building a complete blogging platform with Python. This hands-on tutorial teaches Flask fundamentals including SQLAlchemy ORM, Jinja2 templating, user authentication, and form handling. Perfect for Python developers who want a lightweight, flexible framework for web development.

What You'll Build

  • Full-featured blog with posts, categories, and comments
  • User registration and authentication with Flask-Login
  • Admin interface for content management
  • Rich text editor for post creation
  • Image uploads and media management
  • RESTful API design with Flask blueprints

Flask Concepts You'll Learn

  • Flask Routing: Define URL routes and handle HTTP methods
  • SQLAlchemy ORM: Database models, relationships, and queries
  • Jinja2 Templates: Dynamic HTML rendering with template inheritance
  • Flask-Login: User authentication and session management
  • Flask-WTF: Form validation and CSRF protection
  • Blueprints: Modular application structure
  • Flask-Migrate: Database migrations with Alembic

Prerequisites

  • Python 3.8+ installed on your system
  • Basic Python programming knowledge (functions, classes, decorators)
  • Understanding of HTML, CSS, and basic JavaScript
  • PostgreSQL or SQLite database
  • Virtual environment management (venv or virtualenv)

Estimated Time: 5-7 hours. Progress at your own pace with AI assistance. Each step includes copyable prompts for Claude or ChatGPT to help you code faster.

Why Learn Flask for Blog Development?

Flask is the most popular Python microframework, loved for its simplicity and flexibility. Unlike heavyweight frameworks, Flask gives you exactly what you needβ€”nothing more, nothing less. This "micro" approach means you understand every part of your application, making debugging easier and learning deeper. Flask powers applications at companies like Pinterest, LinkedIn, and Netflix.

This tutorial teaches Pythonic web development with Flask best practices. You'll learn clean application structure, proper error handling, and deployment-ready configurations. Check out our Flask framework guide for more on why Flask is perfect for Python developers.

Build a Blog with Flask

Create a lightweight, Pythonic blog with Flask, the microframework that gives you the building blocks without the bloat. Flask's minimalist philosophy means you only use what you need, combining the power of Python with the flexibility to structure your application exactly as you envision. Paired with SQLAlchemy for elegant database interactions and Jinja2 for flexible templating, Flask lets you build a blog that's both simple and sophisticated. Perfect for Python developers who value explicitness, testability, and the freedom to make architectural decisions that fit their unique requirements.

Flask Blog Setup: Installation and Project Configuration

1

Initialize Flask project with venv

Let's get your Flask blog started. You will create a virtual environment to keep dependencies clean, set up the basic folder structure for templates and static files, and get Flask installed.
AI Prompt
Initialize a new Flask blog project.

Create a project directory, set up a virtual environment with python -m venv venv, activate it (source venv/bin/activate on Unix or venv\Scripts\activate on Windows).

Install Flask with pip install Flask.

Create the project structure: app/ folder for application code, templates/ for Jinja2 templates, static/ for CSS/JS/images, instance/ for config files.

Create app.py or __init__.py as the application factory.

Set up requirements.txt to track dependencies.

Initialize git repository with appropriate .gitignore for Python/Flask projects.
2

Configure VS Code with Python extensions

Time to get VS Code dialed in for Flask development. You will install the Python extension, set up debugging, and configure code formatting so everything just works.
AI Prompt
Configure VS Code for Flask development.

Install Python extension by Microsoft, Pylance for IntelliSense, Jinja template highlighting extension.

Create .vscode/settings.json with Python interpreter path pointing to venv, enable pylint or flake8 for linting, configure Black or autopep8 for formatting.

Set up launch.json for Flask debugging with FLASK_APP and FLASK_ENV variables.

Add .env file support with python-dotenv.

Configure editor to use 4-space indentation (PEP 8 standard).

Install additional extensions: Python Test Explorer for pytest integration.
3

Flask, Flask-SQLAlchemy, Flask-Migrate

Now let's install the Flask extensions you will need. You will grab Flask-SQLAlchemy for the database, Flask-Migrate for handling schema changes, and a few other essentials for forms and authentication.
AI Prompt
Install essential Flask extensions for blog functionality.

Run pip install Flask Flask-SQLAlchemy Flask-Migrate Flask-Login Flask-WTF for core functionality.

Add python-dotenv for environment variables, email-validator for form validation.

Install markdown2 or mistune for markdown parsing.

Add Flask-Bcrypt for password hashing.

For development, install Flask-DebugToolbar.

Update requirements.txt with pip freeze > requirements.txt.

Configure each extension in your Flask app factory with app.config settings and extension initialization (db.init_app(app), migrate.init_app(app, db), etc).

Database and Environment Configuration for Flask

4

Configure SQLite/PostgreSQL connection

Pick your database and wire it up. SQLite is perfect for local development with zero config. When you're ready for production, you can switch to PostgreSQL. Set up your config.py to handle both.
AI Prompt
Configure database connection in Flask app.

For development, use SQLite with SQLALCHEMY_DATABASE_URI = "sqlite:///blog.db".

For production, use PostgreSQL with connection string format "postgresql://username:password@localhost/dbname".

Create config.py with different configurations for development, testing, and production.

Set SQLALCHEMY_TRACK_MODIFICATIONS = False to suppress warnings.

Configure database location in instance/ folder for SQLite.

Initialize Flask-Migrate with flask db init to create migrations folder.

Set up environment variables in .env file for sensitive credentials.
5

Create Post, User models

Create your database models. You will define a User model and a Post model with SQLAlchemy, set up the relationships between them, and add some helpful methods for password hashing and slug generation.
AI Prompt
Create SQLAlchemy models in app/models.py.

Define User model with id, username, email, password_hash, created_at, relationship to posts.

Define Post model with id, title, slug, content, excerpt, created_at, updated_at, published, user_id foreign key, relationship to User.

Add __repr__ methods for debugging.

Implement slug generation from title using slugify.

Add class methods for querying (e.g., Post.published() to get only published posts).

Create password hashing methods in User model using Flask-Bcrypt (set_password, check_password).

Add unique constraints and indexes where appropriate.
6

Set up Jinja2 templates and Bootstrap

Build your template structure. Create a base layout with Jinja2 that you will extend for all your pages, drop in Bootstrap from a CDN for quick styling, and set up templates for your blog listing and post pages.
AI Prompt
Set up Jinja2 template structure in templates/ directory.

Create base.html with HTML boilerplate, Bootstrap 5 CDN links, navigation bar, flash messages display, content block, and footer.

Build layout structure with {% block content %}{% endblock %} for child templates.

Create templates for blog: index.html (post listing), post.html (single post view), create_post.html, edit_post.html (post forms).

Set up static folder structure with css/, js/, images/ subdirectories.

Add custom CSS in static/css/style.css.

Create Jinja2 macros for reusable components like post cards, pagination, form fields.

Configure Flask to serve static files correctly.

Building Blog Features: Core Functionality and Admin Panel

7

Organize app into blueprints

Organize your Flask app with blueprints. Split your code into logical modulesβ€”one for public pages, one for blog management, one for auth. This keeps everything clean as your blog grows.
AI Prompt
Organize Flask application using blueprints for better structure.

Create app/main/ blueprint for public pages (homepage, post views), app/blog/ for blog post management (create, edit, delete), app/auth/ for authentication (login, register, logout).

Each blueprint should have its own __init__.py, routes.py, and forms.py.

Register blueprints in app factory with url_prefix (e.g., /blog, /auth).

Move routes from main app.py into appropriate blueprint route files.

Configure static and template folders for each blueprint if needed.

Use url_for with blueprint name: url_for("blog.create_post").
8

Integrate markdown parser

Add markdown support so you can write blog posts in markdown. Install a parser, create a Jinja2 filter to render it as HTML, and make sure you sanitize the output to prevent XSS issues.
AI Prompt
Integrate markdown parsing for blog post content.

Install markdown2 or mistune package.

Create a Jinja2 filter or template function to convert markdown to HTML.

In your template, use {{ post.content | markdown | safe }} to render markdown as HTML.

Configure markdown parser with desired extensions: code highlighting, tables, footnotes, task lists.

Add XSS protection using bleach library to sanitize HTML output, allowing only safe tags (p, h1-h6, a, ul, ol, li, code, pre, blockquote, strong, em).

Store post content as markdown in database, render as HTML only when displaying.

Add markdown editor preview in create/edit post forms using JavaScript library like SimpleMDE or EasyMDE.
9

Image Upload & Embedding in Posts

Add image support to your blog. Handle file uploads securely, store images in your static folder or cloud storage like S3, and let users embed images in their markdown content. Keep images optimized and validated.
AI Prompt
Add image upload and embedding functionality to blog posts.

Install Flask-Uploads or use werkzeug.utils.secure_filename for file handling.

Create /static/uploads/ directory for storing uploaded images.

Add image upload field to post form using FileField from Flask-WTF.

Implement file validation: check file extension (jpg, png, gif, webp), limit file size (e.g., max 5MB), sanitize filename with secure_filename().

Create upload route that saves file to uploads/ folder, generates unique filename to prevent collisions (uuid or timestamp-based), returns image URL or markdown snippet.

For inline images in markdown: allow users to upload images separately and insert markdown syntax ![alt text](/static/uploads/filename.jpg) into post content, or implement drag-and-drop upload in markdown editor.

Optional: integrate Pillow (PIL) to resize/optimize images on upload, create thumbnails for faster loading.

Optional: use cloud storage like AWS S3, Cloudinary, or Backblaze B2 instead of local storage for better scalability and CDN support.

Add delete functionality to remove images when post is deleted or when user removes image from markdown.

Ensure proper permissions on uploads directory.
10

Build post create, read, update, delete

Build the full CRUD functionality for your blog posts. Set up routes for creating, reading, updating, and deleting posts, with proper forms and validation. Lock down the admin routes so only logged-in users can modify content.
AI Prompt
Build complete CRUD operations for blog posts.

Create routes: GET / for listing published posts with pagination, GET /post/<slug> for single post view, GET /create for new post form (login required), POST /create for handling form submission, GET /edit/<id> for edit form (author only), POST /edit/<id> for update, POST /delete/<id> for deletion (author only).

Use Flask-WTF to create PostForm with fields for title, content, excerpt, published checkbox.

Implement form validation.

Add login_required decorator from Flask-Login for protected routes.

Check post ownership before allowing edit/delete.

Use db.session.add(), db.session.commit() for database operations.

Add flash messages for user feedback.

Redirect with redirect(url_for()).
11

Implement Flask-Login

Set up user authentication with Flask-Login. You will build registration and login forms, hash passwords with Bcrypt, handle sessions, and protect your admin routes with login_required.
AI Prompt
Implement authentication using Flask-Login.

Configure LoginManager in app factory, set login_view and login_message.

Create User model with UserMixin from Flask-Login.

Implement user_loader callback function.

Create authentication forms using Flask-WTF: LoginForm (email, password, remember_me), RegisterForm (username, email, password, password_confirm).

Build routes in auth blueprint: /register (GET/POST), /login (GET/POST), /logout (POST).

Hash passwords with Flask-Bcrypt before saving.

Validate email uniqueness in registration.

Implement login with login_user(), logout with logout_user().

Add remember me functionality.

Protect routes with @login_required decorator.

Create user profile page.

Add password reset functionality with email tokens.
12

Generate RSS feed for posts

Add an RSS feed so readers can subscribe to your blog. Generate an XML feed with your recent posts, and add the auto-discovery link so feed readers can find it automatically.
AI Prompt
Create an RSS feed for blog posts.

Install feedgen or use werkzeug.contrib.atom.

Create /feed or /rss route that generates RSS XML.

Query the 20 most recent published posts ordered by created_at DESC.

Build RSS feed with blog title, description, link, and individual items for each post (title, link, description/excerpt, pubDate, guid, author).

Set proper Content-Type header to "application/rss+xml".

Add <link> tag in base.html pointing to RSS feed for auto-discovery.

Consider caching the feed to avoid regenerating on every request.

Add feed link to footer or header navigation.

Testing Your Flask Blog Application

13

Configure pytest for Flask

Get your testing environment ready with pytest. Install the necessary packages, create fixtures for your test app and database, and set up coverage reporting to track which code is tested.
AI Prompt
Set up testing with pytest for Flask application.

Install pytest, pytest-flask, pytest-cov for coverage.

Create tests/ directory with __init__.py, conftest.py for fixtures, and test files.

In conftest.py, create fixtures: app (Flask app with testing config), client (test client), init_database (create test database).

Configure test database to use SQLite in-memory or separate test database.

Set app.config["TESTING"] = True and disable CSRF for testing.

Create pytest.ini for pytest configuration.

Add test command to make testing easier.

Create factories or fixtures for creating test users and posts.

Set up coverage reporting with pytest --cov=app --cov-report=html.
14

Test homepage renders correctly

Write your first test to verify the homepage works. Check that it loads successfully, shows your published posts, and hides unpublished ones. This is a quick win to make sure your testing setup is working.
AI Prompt
Write your first test for the homepage in tests/test_routes.py.

Test that GET / returns 200 status code, response contains expected HTML elements (blog title, navigation), displays published posts but not unpublished ones.

Create test posts using fixtures or factories.

Use client.get("/") to make request.

Assert with assert b"expected text" in response.data or use response.get_data(as_text=True) for string comparison.

Test pagination appears when more than N posts exist.

Test that clicking on post title links to correct post detail page.

Run tests with pytest -v and verify all pass.
15

Test all blog routes work

Test all your blog routes thoroughly. Make sure creating, editing, and deleting posts works correctly, that unauthorized users can't mess with content, and that form validation catches bad data.
AI Prompt
Write comprehensive route tests for all blog endpoints.

Test post listing: pagination works, posts are ordered by date.

Test single post view: returns 200 for published posts, shows correct content, 404 for non-existent posts.

Test create post: requires authentication, GET shows form, POST with valid data creates post, POST with invalid data shows errors, redirect after successful creation.

Test edit post: only author can access, form pre-populated with data, update works, unauthorized users get 403.

Test delete post: only author can delete, post removed from database, redirect after deletion.

Use test client, create test users with Flask-Login context, test both authenticated and unauthenticated requests.
16

Test models and queries

Test your database models and their methods. Verify that password hashing works, slug generation does its thing, relationships between users and posts are solid, and your database operations behave as expected.
AI Prompt
Write unit tests for SQLAlchemy models and database operations in tests/test_models.py.

Test User model: password hashing works (set_password, check_password), unique email constraint enforced, user-post relationship works.

Test Post model: slug generation from title works, published scope filters correctly, created_at/updated_at timestamps set automatically, foreign key to User works, __repr__ returns expected string.

Test database operations: creating records persists data, updating records works, deleting records removes from DB, queries return expected results.

Use test database fixture that creates/tears down tables for each test.

Test edge cases like empty strings, very long content, special characters in titles.

Deploying Your Flask Blog to Production

17

Deploy to Heroku or PythonAnywhere

Time to deploy your blog to production. Set up Gunicorn, configure your environment variables, connect to a production database, and push everything to Heroku or PythonAnywhere. Don't forget to run your migrations!
AI Prompt
Deploy Flask blog to production.

For Heroku: create Procfile with "web: gunicorn app:app", create runtime.txt with Python version, ensure requirements.txt is complete, install gunicorn, set environment variables in Heroku dashboard (SECRET_KEY, DATABASE_URL), use Heroku Postgres addon, configure SQLALCHEMY_DATABASE_URI from DATABASE_URL, run migrations with "heroku run flask db upgrade".

For PythonAnywhere: upload code via git, create virtual environment, install requirements, configure WSGI file to import app, set up static files mapping, configure environment variables in WSGI file, use MySQL database.

For both: set FLASK_ENV=production, generate strong SECRET_KEY, disable debug mode, configure proper error logging, set up database backups, test all functionality.

Consider using CDN for static files.

Docker

18

Containerize Flask app for development

Containerize your Flask blog with Docker for consistent development environments. Create a Dockerfile, docker-compose.yml with PostgreSQL and Redis, and enable hot-reload for local development.
AI Prompt
Create Docker configuration for Flask blog application.

Create Dockerfile:
- Use python:3.11-slim as base image
- Set WORKDIR /app
- Copy requirements.txt and install dependencies with pip install --no-cache-dir -r requirements.txt
- Copy application code
- Create non-root user and switch to it for security
- Expose port 5000
- Set CMD ["flask", "run", "--host=0.0.0.0"]

Create docker-compose.yml with services:
- web: Flask app with volume mounts (./:/app) for hot-reload, environment variables from .env, depends_on db and redis
- db: PostgreSQL 15 with persistent named volume, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB environment variables
- redis: Redis latest for session storage and caching

Create .dockerignore:
- venv/
- __pycache__/
- *.pyc
- .git/
- .env
- *.db

Configure Flask app to wait for PostgreSQL to be ready before starting (add retry logic or use wait-for-it.sh script).

Update database connection to use Docker service names: postgresql://user:password@db:5432/dbname

Add volume for database persistence and uploads.

Document commands in README:
- docker-compose up --build
- docker-compose exec web flask db upgrade
- docker-compose down -v

Set FLASK_ENV=development and FLASK_DEBUG=1 in docker-compose.yml for hot-reload.
19

Production-ready multi-stage build

Create optimized production Docker images using multi-stage builds. Add health checks, configure Gunicorn, and prepare for deployment to cloud container platforms.
AI Prompt
Create production-ready Docker configuration.

Create multi-stage Dockerfile.prod:

Stage 1 - Builder:
- FROM python:3.11-slim as builder
- Install build dependencies
- Copy requirements.txt
- Install packages to /install directory with pip install --prefix=/install

Stage 2 - Runtime:
- FROM python:3.11-slim
- Copy installed packages from builder stage
- Copy application code
- Create non-root user
- Set proper file permissions
- Install gunicorn
- Expose port 8000
- Add HEALTHCHECK instruction (curl http://localhost:8000/health)
- CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "--timeout", "120", "app:app"]

Create docker-compose.prod.yml:
- Remove volume mounts (no hot-reload needed)
- Use build: dockerfile: Dockerfile.prod
- Set FLASK_ENV=production
- Configure resource limits (memory, CPU)
- Add health check configuration
- Use secrets for sensitive environment variables

Add health check endpoint to Flask app:
@app.route('/health')
def health():
    return {'status': 'healthy'}, 200

Configure Nginx reverse proxy in docker-compose.prod.yml for serving static files and SSL termination.

Test production build locally:
- docker-compose -f docker-compose.prod.yml up --build
- Verify gunicorn workers start correctly
- Test health endpoint

Document deployment to container platforms (AWS ECS, Google Cloud Run, DigitalOcean App Platform).

Hosting

20

Deploy to Railway

Deploy your Flask blog to Railway with one-click PostgreSQL provisioning and automatic deployments from GitHub. Railway handles SSL, provides a free tier, and makes deployment dead simple.
AI Prompt
Deploy Flask blog to Railway.

Sign up at railway.app and create new project.

Connect GitHub repository to Railway dashboard.

Add PostgreSQL plugin from Railway marketplace - DATABASE_URL will be automatically provided.

Configure environment variables in Railway dashboard:
- SECRET_KEY (generate with python -c "import secrets; print(secrets.token_hex(32))")
- FLASK_ENV=production
- DATABASE_URL (auto-provided by Railway)
- SQLALCHEMY_DATABASE_URI=${{DATABASE_URL}} (Railway will substitute)

Railway auto-detects Python apps via requirements.txt, but you can customize:

Create railway.json (optional):
{
  "build": {
    "builder": "NIXPACKS"
  },
  "deploy": {
    "startCommand": "gunicorn app:app --bind 0.0.0.0:$PORT",
    "restartPolicyType": "ON_FAILURE",
    "restartPolicyMaxRetries": 10
  }
}

Or create Procfile:
web: gunicorn app:app --bind 0.0.0.0:$PORT

Ensure gunicorn is in requirements.txt.

Configure custom domain (optional):
- Click Settings β†’ Domains β†’ Add Custom Domain
- Add CNAME record in your DNS provider
- Railway handles SSL automatically

Run database migrations:
- Option 1: Use Railway CLI: railway run flask db upgrade
- Option 2: Add migration step to railway.json build phase
- Option 3: Run manually via Railway dashboard shell

Set up automatic deployments:
- Railway auto-deploys on git push to main branch
- Configure deploy branch in Settings if needed

Monitor application:
- View logs in Railway dashboard
- Set up metrics and alerting
- Check deployment history

Test deployment:
- Visit generated Railway URL (app-name.up.railway.app)
- Verify all routes work
- Test database connectivity
- Check static files serve correctly

Optional: Configure Redis for session storage by adding Redis plugin.
21

Deploy to Render

Deploy to Render with their generous free tier that includes PostgreSQL. Render offers automatic SSL, DDoS protection, and deploys directly from your GitHub repo.
AI Prompt
Deploy Flask blog to Render.

Sign up at render.com and create new Web Service.

Connect GitHub/GitLab repository.

Configure build settings in Render dashboard:
- Name: your-blog-name
- Environment: Python 3
- Build Command: pip install -r requirements.txt
- Start Command: gunicorn app:app --bind 0.0.0.0:$PORT

Create PostgreSQL database (free tier available):
- Click "New" β†’ "PostgreSQL"
- Choose free tier or paid plan
- Copy Internal Database URL

Configure environment variables in Render:
- SECRET_KEY (generate new random string)
- FLASK_ENV=production
- DATABASE_URL (paste PostgreSQL Internal Database URL from above)
- SQLALCHEMY_DATABASE_URI=${{DATABASE_URL}}
- PYTHON_VERSION=3.11.0

Render auto-provisions SSL certificates for your domain.

Add health check endpoint to Flask app:
@app.route('/health')
def health():
    try:
        # Test database connection
        db.session.execute('SELECT 1')
        return {'status': 'healthy', 'database': 'connected'}, 200
    except Exception as e:
        return {'status': 'unhealthy', 'error': str(e)}, 503

Configure health check in Render:
- Path: /health
- Health Check Grace Period: 60 seconds (for migrations)

Run database migrations:
- Option 1: Add as pre-deploy command in render.yaml
- Option 2: Use Render Shell: flask db upgrade
- Option 3: Create render.yaml:

services:
  - type: web
    name: flask-blog
    env: python
    buildCommand: pip install -r requirements.txt
    startCommand: gunicorn app:app
    preDeployCommand: flask db upgrade
    envVars:
      - key: FLASK_ENV
        value: production
      - key: DATABASE_URL
        fromDatabase:
          name: flask-blog-db
          property: connectionString

Set up automatic deployments:
- Render auto-deploys on git push to main branch
- Configure branch in dashboard if needed
- Set up deploy notifications (Slack, email)

Configure custom domain:
- Go to Settings β†’ Custom Domain
- Add your domain
- Update DNS records (CNAME or ALIAS)
- SSL automatically provisioned

Monitor deployment:
- View logs in real-time via dashboard
- Set up alerts for deployment failures
- Check metrics (CPU, memory, requests)

Test production deployment:
- Visit Render-provided URL (your-app.onrender.com)
- Verify database migrations ran
- Test all functionality
- Check static files and media uploads

Optional optimizations:
- Use Render Disk for persistent file uploads
- Add Redis for caching and sessions
- Configure auto-scaling rules
- Set up background workers for async tasks
22

Deploy to Fly.io

Deploy to Fly.io for global edge deployment. Your blog runs close to users worldwide with automatic scaling, and you can start with their generous free tier.
AI Prompt
Deploy Flask blog to Fly.io.

Install Fly.io CLI:
- Mac/Linux: curl -L https://fly.io/install.sh | sh
- Windows: powershell -Command "iwr https://fly.io/install.ps1 -useb | iex"

Authenticate: flyctl auth signup or flyctl auth login

Initialize Fly.io app in project directory:
flyctl launch

This creates fly.toml configuration file. Update it:

app = "your-blog-name"
primary_region = "sea"  # or closest region

[build]
  dockerfile = "Dockerfile"

[env]
  FLASK_ENV = "production"
  PORT = "8080"

[http_service]
  internal_port = 8080
  force_https = true
  auto_stop_machines = true
  auto_start_machines = true
  min_machines_running = 0  # Scale to zero on free tier

[[vm]]
  cpu_kind = "shared"
  cpus = 1
  memory_mb = 256

Create Dockerfile if not exists (use production multi-stage build from earlier step).

Provision PostgreSQL database:
flyctl postgres create --name your-blog-db --region sea

Attach database to app:
flyctl postgres attach your-blog-db

This sets DATABASE_URL automatically.

Set additional environment variables:
flyctl secrets set SECRET_KEY=your-secret-key-here

Deploy application:
flyctl deploy

Run database migrations:
flyctl ssh console -C "flask db upgrade"

Or add release command to fly.toml:

[deploy]
  release_command = "flask db upgrade"

Configure persistent volumes for file uploads:
flyctl volumes create data --size 1 # 1GB

Update fly.toml to mount volume:

[mounts]
  source = "data"
  destination = "/app/uploads"

Set up custom domain:
flyctl certs create yourdomain.com

Add DNS records as instructed by Fly.io (CNAME or A/AAAA).

Configure regions for multi-region deployment (optional):
flyctl regions add lax fra # Add Los Angeles and Frankfurt

Scale machines:
flyctl scale count 2  # Run 2 instances

Monitor deployment:
flyctl status  # Check app status
flyctl logs    # View logs
flyctl ssh console  # SSH into machine

Configure auto-scaling based on traffic:
Update fly.toml [http_service] section with concurrency limits.

Test deployment:
- Visit your-app.fly.dev
- Test from multiple geographic locations
- Verify database connectivity
- Check logs for errors

Cost optimization:
- Use auto_stop_machines for low traffic
- Scale to zero when idle (free tier)
- Monitor usage via dashboard

Serverless

23

Deploy to Vercel

Deploy your Flask blog to Vercel as serverless functions. Configure for serverless runtime, use Vercel Postgres, and leverage their global CDN for static assets.
AI Prompt
Deploy Flask application to Vercel as serverless functions.

Install Vercel CLI:
npm install -g vercel

Restructure Flask app for serverless compatibility:

Create api/index.py (Vercel serves from /api directory):
from app import app

# Export app for Vercel serverless
def handler(request):
    return app(request.environ, request.start_response)

Or use simpler approach with app.py in root:
from flask import Flask
app = Flask(__name__)

# Your routes here

if __name__ != "__main__":
    # Running on Vercel
    from werkzeug.middleware.proxy_fix import ProxyFix
    app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1)

Create vercel.json in project root:
{
  "version": 2,
  "builds": [
    {
      "src": "app.py",
      "use": "@vercel/python"
    }
  ],
  "routes": [
    {
      "src": "/(.*)",
      "dest": "app.py"
    }
  ],
  "env": {
    "FLASK_ENV": "production"
  }
}

Install required dependencies:
pip install Flask Werkzeug
pip freeze > requirements.txt

Set up Vercel Postgres database:
- Create database in Vercel dashboard
- Copy connection string
- Install vercel postgres client if needed

Configure environment variables via Vercel dashboard or CLI:
vercel env add SECRET_KEY
vercel env add DATABASE_URL

Important serverless limitations to handle:
- No writable filesystem except /tmp
- Cold starts (optimize imports)
- Request timeout limits (10 seconds on hobby plan)
- No background workers

Adapt for stateless serverless:
- Store uploads in Vercel Blob Storage or external S3
- Use external database (Vercel Postgres, Supabase, PlanetScale)
- Sessions via signed cookies or external Redis
- No Flask-Migrate in production (run migrations locally)

Handle database migrations:
- Run migrations locally before deploying
- Or use separate migration script deployed as separate serverless function

Configure static files:
- Place in public/ directory (served via CDN automatically)
- Update Flask config to reference CDN URLs

Deploy:
vercel --prod

Or link to GitHub for automatic deployments:
vercel link
git push (Vercel auto-deploys on push)

Configure custom domain:
vercel domains add yourdomain.com

Add DNS records as instructed.

Test deployment:
- Visit your-project.vercel.app
- Test all routes
- Monitor cold start times
- Check function logs in Vercel dashboard

Optimize for serverless:
- Minimize dependencies (faster cold starts)
- Use environment variables for config
- Cache frequently accessed data
- Implement proper error handling for timeouts

Monitor and debug:
- View function logs in Vercel dashboard
- Set up error tracking (Sentry)
- Monitor performance metrics
- Check cold start frequency

Note: Flask is not ideal for serverless due to WSGI design. Consider FastAPI or Flask-like alternatives optimized for serverless if building from scratch.
24

Deploy to AWS Lambda with Zappa

Deploy Flask to AWS Lambda using Zappa for true serverless scale. Configure API Gateway, RDS PostgreSQL or Aurora Serverless, and S3 for media storage.
AI Prompt
Deploy Flask application to AWS Lambda using Zappa.

Prerequisites:
- AWS account with CLI configured: aws configure
- IAM user with permissions for Lambda, API Gateway, S3, CloudFormation, RDS
- Python 3.11 installed locally

Install Zappa:
pip install zappa
pip freeze > requirements.txt

Initialize Zappa:
zappa init

This creates zappa_settings.json. Configure it:

{
  "production": {
    "app_function": "app.app",
    "aws_region": "us-east-1",
    "profile_name": "default",
    "project_name": "flask-blog",
    "runtime": "python3.11",
    "s3_bucket": "zappa-flask-blog-deployments",
    "keep_warm": false,
    "environment_variables": {
      "FLASK_ENV": "production"
    },
    "vpc_config": {
      "SubnetIds": ["subnet-xxx", "subnet-yyy"],
      "SecurityGroupIds": ["sg-xxxxx"]
    }
  }
}

Set up AWS RDS PostgreSQL:
- Create RDS PostgreSQL instance in AWS Console
- Or use Aurora Serverless v2 for auto-scaling
- Note connection endpoint, username, password
- Configure security group to allow Lambda access
- Enable VPC if RDS is in VPC (update zappa_settings.json)

Store secrets in AWS Systems Manager Parameter Store:
aws ssm put-parameter --name "/flask-blog/SECRET_KEY" --value "your-secret-key" --type SecureString
aws ssm put-parameter --name "/flask-blog/DATABASE_URL" --value "postgresql://..." --type SecureString

Update Flask app to read from Parameter Store:
import boto3
ssm = boto3.client('ssm')

def get_parameter(name):
    response = ssm.get_parameter(Name=name, WithDecryption=True)
    return response['Parameter']['Value']

app.config['SECRET_KEY'] = get_parameter('/flask-blog/SECRET_KEY')
app.config['SQLALCHEMY_DATABASE_URI'] = get_parameter('/flask-blog/DATABASE_URL')

Configure S3 for static files and uploads:
- Create S3 bucket for static assets
- Enable public read access for static files
- Configure CORS if needed
- Update Flask config to use S3 for uploads (use boto3 or flask-s3)

Deploy to Lambda:
zappa deploy production

This creates:
- Lambda function
- API Gateway endpoint
- CloudWatch log groups
- CloudFormation stack

Update existing deployment:
zappa update production

Run database migrations on Lambda:
zappa manage production "flask db upgrade"

Or run migrations locally before deploy if database is accessible.

Configure custom domain:
- Get SSL certificate in AWS Certificate Manager
- Configure in zappa_settings.json:

"domain": "blog.yourdomain.com",
"certificate_arn": "arn:aws:acm:us-east-1:..."

Then run:
zappa certify production

Update DNS:
- Add CNAME record pointing to API Gateway endpoint
- Or use Route 53 for automatic DNS

Monitor and debug:
zappa tail production  # Stream logs
zappa status production  # Check status

View logs in CloudWatch:
- Navigate to CloudWatch in AWS Console
- Find /aws/lambda/flask-blog-production log group

Optimize for cold starts:
- Keep dependencies minimal
- Use keep_warm setting (costs money)
- Set reserved concurrency if needed
- Enable Lambda SnapStart for faster cold starts

Configure auto-scaling:
Lambda auto-scales, but configure:
- Reserved concurrency (max instances)
- Provisioned concurrency (always-warm instances)
- API Gateway throttling limits

Handle sessions and caching:
- Use ElastiCache Redis for sessions (add to VPC config)
- Or use DynamoDB for session storage
- Implement caching with Redis or DAX

Set up monitoring and alerting:
- CloudWatch alarms for errors, latency
- AWS X-Ray for distributed tracing
- Enable Lambda Insights for detailed metrics

Cost optimization:
- Monitor Lambda invocations and duration
- Optimize memory allocation (affects CPU)
- Use S3 lifecycle policies for old uploads
- Consider Aurora Serverless v2 for variable traffic
- Set up budget alerts in AWS Billing

Undeploy if needed:
zappa undeploy production

This removes all AWS resources created by Zappa.