Streamlit любят за скорость: дашборд или демо модели собирается за один вечер и сразу открывается на своём ноутбуке по адресу localhost:8501. Проблема начинается на следующем шаге — показать его коллеге, клиенту или преподавателю. Локальный адрес никому не виден, а классический путь в интернет — арендовать сервер, поставить Python, поднять приложение в фоне, прикрутить nginx и выпустить сертификат. На Dockbox эту часть берёт платформа: вы подключаете Git-репозиторий или загружаете архив, сервис собирает Docker-контейнер, ставит зависимости и отдаёт публичную HTTPS-ссылку. Ниже — что поправить в проекте, чтобы дашборд заработал с первого раза.
Streamlit запускается не как обычный веб-сайт
У приложения собственный сервер. Streamlit не работает через WSGI или gunicorn — его поднимает команда streamlit run app.py, внутри которой свой сервер и свой протокол общения с браузером. Поэтому «правильная» команда запуска для платформы — именно она, а не python app.py.
Скрипт выполняется сверху вниз на каждое взаимодействие. Любой виджет, нажатие кнопки или ввод в поле запускает файл заново целиком. Локально это незаметно, а на сервере превращается в главный источник нагрузки на CPU.
Настройки читаются из ~/.streamlit/config.toml. Если вы когда-то фиксировали там порт или адрес, эти значения перекроют всё, что передаст платформа. Такой файл из проекта нужно убрать.
Зависимости: requirements.txt решает всё
Без файла зависимостей сборка не знает, что ставить. Положите requirements.txt рядом с кодом и перечислите в нём всё, что импортирует приложение, включая сам streamlit. Для типичного дашборда:
streamlit==1.40.0
pandas==2.2.3
matplotlib==3.9.2
Фиксируйте версии. pandas и matplotlib — тяжёлые библиотеки, которые регулярно ломают совместимость между минорными релизами. Сборка «по последней версии» однажды даст дашборд с пустыми графиками вместо ошибки в логах.
Не кладите venv в архив. Виртуальное окружение содержит бинарники под вашу операционную систему и на сервере непригодно, а зависимости всё равно ставятся заново по requirements.txt. Папки .venv, __pycache__ и кеш тестов только раздувают загрузку.
Порт берётся из PORT, а слушать надо 0.0.0.0
Не вписывайте 8501 руками. Номер порта выдаёт платформа через переменную окружения PORT. Число, зашитое в код или в конфиг, — главная причина, по которой контейнер собирается без ошибок, а ссылка остаётся пустой. Команда запуска:
streamlit run app.py --server.address=0.0.0.0 --server.port=$PORT --server.headless=true
Адрес 0.0.0.0 обязателен. Приложение, привязанное к 127.0.0.1, доступно только самому себе: снаружи порт открыт, но отвечает пустой страницей или ошибкой соединения.
Флаг headless=true не косметика. Без него Streamlit считает, что запущен на рабочей машине, пытается открыть браузер и ведёт себя непредсказуемо. Локально горячая перезагрузка --server.runOnSave удобна, в продакшене она не нужна.
Память: датафреймы съедают тариф незаметно
Каждая тяжёлая операция держит в памяти копию данных. merge, pivot, groupby и особенно df.copy() удваивают потребление незаметно для вас, но не для контейнера. Датафрейм на 2 GB в памяти означает, что тариф с 2 GB RAM уже закончился.
Кеш по умолчанию растёт бесконечно. Декоратор st.cache_data хранит результат каждого уникального набора аргументов. Без параметров max_entries и ttl кеш постепенно занимает всю доступную память: приложение работает неделю, а на восьмой падает по OOM.
Фигуры matplotlib не освобождаются сами. Каждый построенный график остаётся в памяти до закрытия: явно вызывайте plt.close(fig) и передавайте фигуру через st.pyplot, а не полагайтесь на автоочистку.
Измерить реальный вес данных можно одной строкой — выводьте её в лог:
print(df.memory_usage(deep=True).sum() / 1024 ** 2, "MB")
Откуда приложение берёт данные
CSV рядом с кодом — это снимок. Файл уезжает на сервер вместе с архивом и обновляется только при следующей публикации. Для справочника регионов или разового отчёта это нормально, для живых продаж — нет.
Внешний API прикрывайте кешем. Каждое движение ползунка перезапускает скрипт целиком, а значит, и запрос к источнику. @st.cache_data(ttl=600) спасёт и от лимитов API, и от задержек интерфейса.
Файлы, которые пишет само приложение, живут до обновления кода. Контейнер пересобирается при каждом деплое, и всё, что сохранялось рядом со скриптом, исчезает. Для данных, которым нужно пережить публикацию, подключайте внешнюю базу или раздел постоянного хранения.
База может ехать в том же проекте. Небольшой PostgreSQL поднимается рядом с дашбордом: для аналитики на десятки тысяч строк этого хватает с запасом.
Секреты — только в переменных окружения
Streamlit умеет читать st.secrets, но файл secrets.toml в репозиторий не идёт. Положите его в .gitignore и передавайте значения через переменные окружения проекта — платформа подставит их в контейнер зашифрованными. В коде остаётся чтение:
import os
DB_URL = os.environ["DATABASE_URL"]
Ключ, попавший в Git, считайте утёкшим. История коммитов хранит его навсегда, поэтому скомпрометированный токен нужно не удалять из файла, а отзывать.
Загрузка кода и первая проверка
Подключите репозиторий или загрузите архив — включая приватный репозиторий. Платформа сама увидит requirements.txt, определит Python-проект и соберёт контейнер.
Смотрите логи в реальном времени. Ошибка импорта, отсутствующая переменная или неверный порт видны в логах прямым текстом, а не как белый экран в браузере.
Проверьте служебный адрес. Эндпоинт /_stcore/health у Streamlit отвечает словом ok, когда сервер действительно поднялся: если он молчит, а ссылка открывается, дело в проксировании и адресе прослушивания.
Следите за ресурсами в панели. Потребление RAM растёт от кеша и размеров датафреймов, а не только от числа пользователей.
Сколько это стоит
Dockbox работает на трёх тарифах, цены — в рублях. Starter — 990 ₽/мес, первый месяц бесплатно: 1 vCPU, 2 GB RAM, 20 GB SSD. Pro — 2 490 ₽/мес: 2 vCPU, 4 GB RAM и до 5 проектов. Team — 4 990 ₽/мес. Контейнеры работают постоянно: дашборд не засыпает от простоя и не «просыпается» секунды на первом запросе. Для лёгкого дашборда на одной CSV хватит Starter, а как только в памяти появляются тяжёлые датафреймы и база рядом — берите Pro. Начать можно с бесплатного месяца.
Частые вопросы
Нужно ли указывать команду запуска вручную?
Обычно нет. Если streamlit есть в requirements.txt, платформа определяет тип приложения и запускает его верной командой с нужным портом. Команду с флагами --server.address и --server.port задают только в нестандартных случаях.
Почему ссылка не открывается, хотя сборка прошла?
Первая причина — порт или адрес, зашитые в коде либо в config.toml. Вторая — приложение слушает 127.0.0.1 вместо 0.0.0.0. Третья — ошибка на старте: её текст всегда есть в логах панели.
Сохранятся ли файлы, которые пишет дашборд?
Только если они лежат во внешнем хранилище или в базе. Файл рядом со скриптом исчезнет при следующей публикации, потому что контейнер собирается заново. Постоянную папку задают через том или подключённую базу.
Приложение падает по памяти — с чего начать?
Проверьте st.cache_data: задайте max_entries и ttl, иначе кеш растёт бесконечно. Затем измерьте вес датафреймов через memory_usage(deep=True), уберите лишние df.copy() и закрывайте фигуры matplotlib. Если данные по-настоящему большие — переходите на тариф с 4 GB RAM.
Как закрыть дашборд паролем?
Ссылка публичная, поэтому проверку доступа делайте внутри приложения: попросите пароль перед показом содержимого. Сам пароль храните в переменных окружения, а не в коде.