Build a Blog with Flask
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
Initialize Flask project with venv
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.
Configure VS Code with Python extensions
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.
Flask, Flask-SQLAlchemy, Flask-Migrate
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
Configure SQLite/PostgreSQL connection
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.
Create Post, User models
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.
Set up Jinja2 templates and Bootstrap
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
Organize app into blueprints
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").Integrate markdown parser
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.Image Upload & Embedding in Posts
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  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.
Build post create, read, update, delete
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()).
Implement Flask-Login
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.
Generate RSS feed for posts
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
Configure pytest for Flask
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.
Test homepage renders correctly
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.Test all blog routes work
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.
Test models and queries
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
Deploy to Heroku or PythonAnywhere
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
Containerize Flask app for development
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.
Production-ready multi-stage build
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
Deploy to Railway
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.Deploy to Render
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 tasksDeploy to Fly.io
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
Deploy to Vercel
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.Deploy to AWS Lambda with Zappa
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.