Flask-приложение можно выложить в сеть, не арендуя сервер: загружаете папку с исходниками, платформа видит Python-проект, ставит зависимости из requirements.txt и выдаёт публичную HTTPS-ссылку. Но у Flask есть особенность, из-за которой он чаще других фреймворков «молчит» после деплоя: встроенный сервер разработки для интернета не годится, а адрес и порт по умолчанию он слушает не те. Ниже — путь от папки с кодом до рабочей ссылки на Dockbox и то, где Flask-проекты спотыкаются чаще всего.
Почему Flask-проект по умолчанию тащат на VPS
Привычный сценарий начинается с аренды сервера: поставить Python, создать виртуальное окружение, установить зависимости, поднять процесс, поставить перед ним nginx, выпустить сертификат и придумать, как поднимать приложение после падения и перезагрузки. Flask сам по себе простой, а всё, что вокруг него, ложится на вас.
Запуск не тем сервером. flask run и app.run() — сервер разработки: один процесс, отладочный режим, без обработки конкурентных запросов. Под реальной нагрузкой он падает или отвечает по очереди. Для интернета нужен WSGI-сервер, обычно gunicorn, и это ещё одна сущность, которую надо настроить и запустить.
Обратный прокси и сертификат. Без nginx приложение не отдаёт HTTPS, а сертификат надо продлевать каждые три месяца — и легко проспать.
Автозапуск и перезапуск. Чтобы приложение вернулось в строй после падения и после перезагрузки, нужен systemd-юнит или супервизор, и его отладка — отдельный навык.
Оплата сервера независимо от нагрузки. Машина стоит денег все 30 дней, даже если сайт открывают раз в сутки.
Платформа деплоя снимает эту часть: вы загружаете код, она находит Python-проект, ставит зависимости, запускает его под production-сервером и отдаёт публичную HTTPS-ссылку. Ни сервера, ни nginx, ни ручного сертификата.
Шаг 1. Соберите requirements.txt
Положите рядом с кодом файл requirements.txt со списком библиотек. Минимум — Flask и gunicorn: без второго приложение просто нечему будет запустить в продакшене.
Flask==3.0.3
gunicorn==22.0.0
python-dotenv==1.0.1
Версии лучше фиксировать: сборка получится воспроизводимой, и однажды новая мажорная версия библиотеки не сломает проект на ровном месте. Виртуальное окружение (venv, .venv) в архив не кладите — это локальные файлы вашей машины, зависимости всё равно ставятся заново из списка. Папки __pycache__ и .pytest_cache тоже лишние.
Если приложение работает с базой или картинками, добавьте SQLAlchemy, psycopg2-binary, Pillow — по факту импортов. Проверьте, что каждый import из кода покрыт списком.
Шаг 2. В продакшене Flask запускает gunicorn, а не flask run
Это главное отличие Flask от большинства фреймворков. Встроенный сервер написан для отладки, он однопоточный и в логах прямо предупреждает, что не предназначен для продакшена. Правильная схема — gunicorn держит несколько воркеров, каждый из которых обслуживает запросы к вашему WSGI-объекту.
Объект — это переменная app в модуле: Flask создаёт её строкой app = Flask(__name__), и gunicorn ссылается на неё как модуль:объект. Если точка входа называется app.py, команда запуска выглядит так:
gunicorn app:app --bind 0.0.0.0:$PORT --workers 2
Воркеры не надо множить на всякий случай: каждый — это отдельный процесс Python, который загружает всё приложение целиком и занимает свою память. Два воркера на 2 GB RAM — разумная отправная точка, и упираться в количество процессорных ядер для небольшого сайта смысла нет.
Шаг 3. Точка входа app.py, порт из PORT, адрес 0.0.0.0
Порт задаёт платформа, а не вы. Значение приходит в переменной окружения PORT, и читать его нужно в рантайме. Плохой вариант — порт и адрес вписаны в код:
app.run(host="127.0.0.1", port=5000)
Привязка к 127.0.0.1 означает «слушать только внутри контейнера»: снаружи приложение недоступно, и ссылка останется пустой. Правильный вариант — адрес 0.0.0.0 и порт из переменной:
import os
if __name__ == "__main__":
port = int(os.environ.get("PORT", 5000))
app.run(host="0.0.0.0", port=port)
Этот блок нужен для локального запуска, а на платформе порт подставит gunicorn через --bind 0.0.0.0:$PORT, как в команде выше. Точку входа держите в корне архива и назовите app.py — так меньше поводов для двусмысленности, какой модуль запускать.
Шаг 4. Секретный ключ и строки подключения — в переменные окружения
SECRET_KEY, токены внешних сервисов и строку подключения к базе не держите константами в коде. Код вы почти наверняка зальёте в Git или кому-то покажете, а вместе с ним уедут и секреты.
import os
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ["DATABASE_URL"]
В настройках проекта Dockbox запрашивает такие значения при создании проекта и передаёт их в контейнер в зашифрованном виде, поэтому в архиве и в репозитории они не появляются. Файл .env с секретами включать не нужно.
Шаг 5. Загрузите код и дождитесь сборки
Проект попадает на платформу архивом или импортом репозитория GitHub, в том числе приватного. Дальше платформа сама определяет, что это Python с Flask, ставит зависимости и собирает образ. Команду запуска укажите свою — gunicorn app:app --bind 0.0.0.0:$PORT.
Статус и живые логи в панели показывают, на каком шаге сборка и что пишет процесс при старте. Когда сборка дошла до готового состояния, ссылка уже работает. Новая версия уезжает тем же путём, а адрес остаётся прежним.
Сколько памяти нужно Flask-приложению и сколько это стоит
Flask в простое ест десятки мегабайт, но каждый воркер gunicorn — полная копия процесса Python, а библиотеки (ORM, обработка изображений, pandas) легко съедают сотни мегабайт на воркер. Отсюда правило: если приложение падает по памяти, сначала уменьшите число воркеров.
Администрировать сервер и следить за процессом не нужно: контейнеры работают постоянно, без ограничения по часам и без «засыпания». Упавший контейнер платформа перезапускает сама.
Starter — 990 ₽/мес. 1 vCPU, 2 GB RAM, один проект. Первый месяц бесплатный. Для одного Flask-сайта с парой воркеров этого достаточно.
Pro — 2 490 ₽/мес. Больше ресурсов и до пяти проектов — вариант, если приложение прожорливое или сервисов несколько.
Team — 4 990 ₽/мес — для команд, которым нужен запас ресурсов и SLA.
Свой Dockerfile поддерживается, но не обязателен: язык и стек платформа определяет по коду сама. Если Dockerfile нужен, берите slim-образ и ставьте зависимости отдельным слоем, чтобы кеш работал:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD gunicorn app:app --bind 0.0.0.0:$PORT
Частые вопросы
Нужен ли gunicorn, если Flask и так запускается?
Да, нужен. flask run и app.run() — отладочный сервер, он однопоточный и не рассчитан на реальные запросы. В продакшене приложение запускает WSGI-сервер, обычно gunicorn, а сам Flask остаётся кодом, который этот сервер обслуживает.
Какую команду запуска указывать для Flask-приложения?
gunicorn app:app --bind 0.0.0.0:$PORT, где первое app — имя файла app.py без расширения, а второе — переменная с объектом Flask внутри него. Если переменная называется иначе, подставьте её имя. Число воркеров добавьте флагом --workers.
Сборка прошла, а ссылка не открывается. Что проверить?
Начните с адреса и порта: приложение должно слушать 0.0.0.0 и порт из переменной PORT, а не 127.0.0.1 и число из кода. Вторая частая причина — падение сразу после старта из-за отсутствующего SECRET_KEY или недоступной базы. И то и другое видно в логах панели, вместе с текстом ошибки.
Что делать с venv и __pycache__ перед загрузкой?
Не включать в архив. Виртуальное окружение содержит бинарники под вашу локальную ОС и на сервере всё равно непригодно, а зависимости ставятся заново из requirements.txt. Кеш Python и папки тестов только раздувают загрузку.
Сколько воркеров gunicorn ставить?
Для начала два: каждый воркер — отдельный процесс Python со своей памятью, и на тарифе с 2 GB RAM большее число легко упрётся в лимит. Если воркеров станет тесно по CPU, увеличивайте их постепенно и следите за потреблением памяти в панели, а не наоборот.