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

# File path: app/__init__.py
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

# File path: config.py
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

# File path: wsgi.py
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

  1. Visitrailway.app,log in with GitHub
  2. New Project → Deploy from GitHub repo → select flask-blog
  3. Railway automatically detects Procfile and identifies the Gunicorn startup command
  4. Set environment variables in Variables.
  5. Click Deploy

Environment variable configuration

Variable NameValueDescription
SECRET_KEYRandomly generated long stringEncrypt Session and CSRF Token
DATABASE_URLAutomatically injected by RailwayProduction database (PostgreSQL provided by Railway)
FLASK_ENVproductionProduction 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 DirectionWho It's ForRecommended Starting Point
Flask REST APIBuild 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 separationWant to use Flask for the backend and Vue3/React for the frontendFlask provides a JSON API, and the frontend communicates via fetch
PostgreSQLNeed a production-grade databaseReplace SQLite, install psycopg2, modify DATABASE_URL
Docker DeploymentContainerized DeploymentWrite a Dockerfile and orchestrate with docker-compose
other extensions