Packaging for Production — Deploying to Railway
In this chapter, you will learn how to apply the factory pattern, configure the production environment, and deploy the Flask blog to production using Railway.
Application factory pattern create_app()
So far, app.py directly createdapp = Flask(__name__), which is sufficient for development, but there are several problems:
- Cannot create an independent app instance during testing
- Unable to switch between different configurations based on environment (development/testing/production)
- Extension initialization is tightly coupled with the app instance
Application Factory PatternThis solves these problems: encapsulate the creation of the app into a function.
Example
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_migrate import Migrate
from config import Config
db = SQLAlchemy()
login_manager = LoginManager()
migrate = Migrate()
def create_app(config_class=Config):
"""Application factory: creates a Flask application instance based on the configuration class"""
app = Flask(__name__)
app.config.from_object(config_class)
# Initialize extensions (first create the app instance, then bind extensions to the app)
db.init_app(app)
login_manager.init_app(app)
migrate.init_app(app, db)
login_manager.login_view = 'auth.login'
login_manager.login_message = 'Please log in first before accessing.'
# Register Blueprint
from app.blueprints.main import main_bp
from app.blueprints.posts import posts_bp
from app.blueprints.auth import auth_bp
from app.blueprints.user import user_bp
app.register_blueprint(main_bp)
app.register_blueprint(posts_bp)
app.register_blueprint(auth_bp)
app.register_blueprint(user_bp)
# user_loader needs to be defined after the app exists
from app.models import User
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
return app
Configuration file config.py
Example
import os
from dotenv import load_dotenv
# Load environment variables from .env file (for development environment only)
load_dotenv()
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-secret-key')
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', 'sqlite:///blog.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config):
DEBUG = True
class ProductionConfig(Config):
DEBUG = False
# Select configuration based on environment variables
config_map = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
New entry file wsgi.py
Example
from app import create_app
app = create_app()
Now the startup method becomes:
(venv) $ flask --app wsgi run --debug # 开发模式 (venv) $ gunicorn wsgi:app # 生产模式(Gunicorn)
Note the import order: Blueprint and user_loader must be imported inside create_app() (not at the top of the file), otherwise circular imports will be triggered (Blueprint references models, models references db, and db is not yet bound to the app at that point).
Generate requirements.txt and Procfile
(venv) $ pip freeze > requirements.txt
Make sure the file contains key dependencies: Flask, Flask-SQLAlchemy, Flask-Migrate, Flask-Login, Flask-WTF, Flask-Admin, gunicorn, python-dotenv.
Create in the project root directoryProcfile:
web: gunicorn wsgi:app
Push the project to GitHub
$ git init $ echo "venv/" > .gitignore $ echo "__pycache__/" >> .gitignore $ echo "*.pyc" >> .gitignore $ echo "instance/" >> .gitignore $ echo ".env" >> .gitignore $ git add . $ git commit -m "初始化 Flask 博客项目" $ git branch -M main $ git remote add origin https://github.com/你的用户名/flask-blog.git $ git push -u origin main
Be sure to
.envadd.gitignoreThe .env file contains sensitive information such as SECRET_KEY. Once committed to GitHub, it cannot be completely deleted (Git history will retain it).
Railway Deployment
- Visitrailway.app,log in with GitHub
- New Project → Deploy from GitHub repo → select flask-blog
- Railway automatically detects Procfile and identifies the Gunicorn startup command
- Set environment variables in Variables.
- Click Deploy
Environment variable configuration
| Variable Name | Value | Description |
|---|---|---|
| SECRET_KEY | Randomly generated long string | Encrypt Session and CSRF Token |
| DATABASE_URL | Automatically injected by Railway | Production database (PostgreSQL provided by Railway) |
| FLASK_ENV | production | Production environment identifier |
Quickly generate SECRET_KEY:
$ python -c "import secrets; print(secrets.token_urlsafe(50))"
After successful deployment, get a link likehttps://flask-blog.up.railway.app。
Next learning direction
| Learning Direction | Who It's For | Recommended Starting Point |
|---|---|---|
| Flask REST API | Build an API for frontend frameworks (Vue3/React) | Use jsonify instead of render_template to build RESTful APIs |
| Flask + Vue3/React front-end and back-end separation | Want to use Flask for the backend and Vue3/React for the frontend | Flask provides a JSON API, and the frontend communicates via fetch |
| PostgreSQL | Need a production-grade database | Replace SQLite, install psycopg2, modify DATABASE_URL |
| Docker Deployment | Containerized Deployment | Write a Dockerfile and orchestrate with docker-compose |