Menu
Szybki wybór
Hosting Domeny VPS SSL Kalkulator Porównania FAQ
Aktywne kody
Wszystkie kody rabatowe

Flask na produkcji — Gunicorn, Nginx i systemd na VPS

Opublikowano: 10 kwietnia 2026 · Kategoria: VPS

Flask to mikroframework — elegancki, minimalistyczny, bez narzucanej struktury. To jego siła i słabość. W produkcji sam Flask nie wystarcza: potrzebujesz Gunicorna jako serwera WSGI, systemd do zarządzania procesem i Nginx jako reverse proxy z obsługą plików statycznych. Ten artykuł pokazuje kompletną konfigurację od application factory pattern, przez Gunicorn workers, aż po Flask-Migrate i konfigurację logowania.

Application Factory Pattern — architektura Flask

Application factory (wzorzec fabryki) to standard w Flask — zamiast tworzyć instancję aplikacji globalnie, pakujesz ją w funkcję create_app(). Umożliwia to różne konfiguracje dla dev/test/prod i unikasz circular imports.

# Struktura projektu
# myapp/
# ├── app/
# │   ├── __init__.py       # create_app()
# │   ├── config.py         # konfiguracja per env
# │   ├── models.py         # SQLAlchemy modele
# │   ├── extensions.py     # db, migrate, login_manager
# │   └── blueprints/
# │       ├── auth.py       # Blueprint dla auth
# │       └── api.py        # Blueprint dla API
# ├── migrations/           # Flask-Migrate
# ├── wsgi.py               # entry point dla Gunicorn
# ├── .env                  # zmienne lokalne (nie commituj)
# └── requirements.txt

# app/extensions.py — singleton extensions
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager

db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()

# app/__init__.py — application factory
from flask import Flask
from .extensions import db, migrate, login_manager
from .config import config_by_name

def create_app(config_name: str = 'production') -> Flask:
    app = Flask(__name__, instance_relative_config=True)
    app.config.from_object(config_by_name[config_name])

    # Inicjalizacja extensions
    db.init_app(app)
    migrate.init_app(app, db)
    login_manager.init_app(app)

    # Rejestracja blueprintow
    from .blueprints.auth import auth_bp
    from .blueprints.api import api_bp
    app.register_blueprint(auth_bp, url_prefix='/auth')
    app.register_blueprint(api_bp, url_prefix='/api/v1')

    return app

# app/config.py
import os

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'change-this-in-prod'
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    JSON_SORT_KEYS = False

class DevelopmentConfig(Config):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///dev.db'

class ProductionConfig(Config):
    DEBUG = False
    TESTING = False
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
    SQLALCHEMY_ENGINE_OPTIONS = {
        'pool_size': 10,
        'max_overflow': 20,
        'pool_pre_ping': True,
    }

config_by_name = {
    'development': DevelopmentConfig,
    'production': ProductionConfig,
    'default': ProductionConfig,
}

# wsgi.py — entry point dla Gunicorn
from app import create_app

app = create_app('production')

if __name__ == '__main__':
    app.run()

Gunicorn — konfiguracja workers

# Instalacja Gunicorn i gevent w venv
pip install gunicorn gevent

# gunicorn.conf.py
bind = "unix:/run/flask/flask.sock"
workers = 4              # (2 * CPU) + 1
worker_class = "sync"    # "sync" | "gevent" | "eventlet" | "gthread"
# gevent:   --workers 2 --worker-class gevent --worker-connections 1000
# gthread:  --workers 4 --worker-class gthread --threads 4

timeout = 30
keepalive = 5
max_requests = 1000      # restart workera po N requestach
max_requests_jitter = 100

accesslog = "/var/log/flask/access.log"
errorlog = "/var/log/flask/error.log"
loglevel = "info"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'

# preload_app przspiesza start, ale uniemozliwia hot-reload konfiguracji
preload_app = True
proc_name = "flask-app"

# Test lokalny
gunicorn wsgi:app -c gunicorn.conf.py

Systemd service i Nginx

# /etc/systemd/system/flask-app.service
[Unit]
Description=Flask Gunicorn Application
After=network.target postgresql.service

[Service]
User=flask
Group=flask
WorkingDirectory=/srv/myapp
RuntimeDirectory=flask
EnvironmentFile=/srv/myapp/.env
ExecStart=/srv/myapp/venv/bin/gunicorn wsgi:app -c gunicorn.conf.py
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=30
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

# /etc/nginx/sites-available/flask-app
upstream flask_backend {
    server unix:/run/flask/flask.sock fail_timeout=0;
}

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Pliki statyczne Flask (katalog static/)
    location /static/ {
        alias /srv/myapp/app/static/;
        expires 30d;
        add_header Cache-Control "public";
    }

    location / {
        proxy_pass http://flask_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 30s;
    }
}

# Wlaczenie
sudo systemctl daemon-reload && sudo systemctl enable --now flask-app
sudo nginx -t && sudo systemctl reload nginx

Flask-Migrate — migracje bazy danych

# Flask-Migrate jest wrapperem nad Alembic
pip install flask-migrate

# Inicjalizacja (tylko raz)
flask --app wsgi:app db init
# Generuje katalog migrations/

# Tworzenie migracji po zmianie modelu
flask --app wsgi:app db migrate -m "add user table"
# Sprawdz wygenerowany plik migrations/versions/*.py

# Aplikowanie migracji
flask --app wsgi:app db upgrade

# Wycofanie ostatniej migracji
flask --app wsgi:app db downgrade

# Historia migracji
flask --app wsgi:app db history --verbose

# app/models.py — przykladowe modele
from datetime import datetime, timezone
from .extensions import db

class User(db.Model):
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True, nullable=False, index=True)
    hashed_password = db.Column(db.String(255), nullable=False)
    is_active = db.Column(db.Boolean, default=True, nullable=False)
    created_at = db.Column(
        db.DateTime(timezone=True),
        default=lambda: datetime.now(timezone.utc),
        nullable=False
    )

    def __repr__(self):
        return f'<User {self.email}>'

Konfiguracja logowania produkcyjnego

// app/__init__.py — logging setup (dodaj do create_app)
import logging
from logging.handlers import RotatingFileHandler

def configure_logging(app: Flask) -> None:
    if not app.debug:
        handler = RotatingFileHandler(
            '/var/log/flask/app.log',
            maxBytes=10 * 1024 * 1024,  # 10 MB
            backupCount=5,
        )
        handler.setFormatter(logging.Formatter(
            '%(asctime)s %(levelname)s %(name)s [%(filename)s:%(lineno)d] %(message)s'
        ))
        handler.setLevel(logging.INFO)
        app.logger.addHandler(handler)
        app.logger.setLevel(logging.INFO)

# Logowanie w aplikacji
from flask import current_app

@api_bp.errorhandler(Exception)
def handle_error(error):
    current_app.logger.error(f"Unhandled error: {error}", exc_info=True)
    return {"error": "Internal server error"}, 500

Flask vs FastAPI — porównanie

Cecha Flask FastAPI
Typ serwera WSGI (sync) ASGI (async)
Wydajność Dobra (z gevent) Bardzo dobra (natywny async)
Walidacja danych WTForms / Marshmallow Pydantic (wbudowany)
Dokumentacja API Flasgger (ręczna) Swagger auto-gen
Ekosystem rozszerzeń Bogaty (Flask-Login, Admin, CORS) Rosnący
Krzywa nauki Niska (popularne tutoriale) Niska–Średnia
Async support Ograniczony Natywny (cały framework)

Najczęstsze pytania

Dlaczego Flask development server nie nadaje się do produkcji? +
Flask development server (app.run()) jest jednowątkowy — obsługuje jeden request na raz. Przy dwóch równoczesnych użytkownikach drugi czeka. Nie ma auto-restartu po awarii, brakuje timeout handling, logowania requestów i zarządzania procesami. Gunicorn rozwiązuje wszystkie te problemy: wiele procesów worker, graceful shutdown, timeout, logging. Flask sam w sobie jest świetny — devserver jest tylko do lokalnego developmentu.
Gunicorn sync vs gevent vs eventlet — którą klasę workerów wybrać? +
Sync workers (domyślne) tworzą N procesów, każdy obsługuje jeden request. Dobre dla aplikacji CPU-bound lub prostych API. Gevent/eventlet używają green threads (coroutines) — jeden proces obsługuje tysiące requestów równocześnie, ale aplikacja musi być I/O-bound (baza, API zewnętrzne). Zasada: --worker-class sync --workers 4 dla większości; --worker-class gevent --workers 2 --worker-connections 1000 gdy masz dużo I/O. Flask nie jest async z natury — do heavy async użyj FastAPI.
Jak Flask Migrate różni się od Alembic? +
Flask-Migrate to wrapper nad Alembic, który integruje go z Flask aplikacją przez flask db CLI. Alembic to niezależna biblioteka migracji SQLAlchemy. Flask-Migrate dodaje komendy: flask db init, flask db migrate (autogenerate), flask db upgrade, flask db downgrade. Jeśli używasz Flask-SQLAlchemy, Flask-Migrate jest wygodniejszy. Jeśli SQLAlchemy bez Flask albo używasz FastAPI/Django, używaj Alembic bezpośrednio.
Flask vs FastAPI — co wybrać? +
Flask jest starszy, ma więcej tutoriali i rozszerzeń, jest prostszy dla początkujących. FastAPI jest szybszy (async/ASGI), ma wbudowaną walidację Pydantic i automatyczną dokumentację OpenAPI. Dla nowych projektów API FastAPI jest lepszym wyborem pod kątem wydajności i nowoczesności. Dla projektów gdzie używasz Flask-Login, Flask-Admin i bogatego ekosystemu Flask — pozostań przy Flask. Migracja z Flask na FastAPI jest możliwa, ale wymaga refaktoryzacji (synchroniczny kod → async).

Sprawdź oferty pasujące do tego scenariusza

Poniżej masz szybkie przejścia do ofert i stron z kodami rabatowymi tam, gdzie są dostępne.