快轉到主要內容
  1. 教學文章/

Streamlit 部署實戰:Secrets、設定檔與雲端上線完整攻略

·8 分鐘· loading · loading · ·
Python Streamlit Deployment Secrets Config Cloud
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
Python 學習 - 本文屬於一個選集。
§ 54: 本文

featured

一. 前言:Demo 很快,上線才是修羅場
#

Streamlit 最迷人的地方,是你可以用很少的 Python 做出互動 App。 st.title()st.file_uploader()st.dataframe() 放一放,十分鐘就能 demo。 但 demo 跟上線是兩種生物。 Demo 的時候,你可以把 API key 寫在程式裡;上線以後,這叫事故。 Demo 的時候,你可以用本機路徑讀資料;上線以後,雲端不認識你的桌面。 Demo 的時候,你可以把檔案存在 ./uploads;上線以後,容器重啟可能全部消失。 今天拍拍君要整理 Streamlit 部署前最常撞到的工程細節:secrets、設定檔、環境變數、Cloud 部署、Docker,以及上線前 checklist。 如果你還在入門階段,可以先看 Streamlit 入門篇;如果你已經開始做多頁 App,可以接著看 Streamlit 進階篇。 這篇的目標很明確:讓你的 App 從「我電腦可以跑」變成「別人也能安心用」。

二. 建立可部署的專案骨架
#

先建立專案。 拍拍君這裡用 uv,因為 lockfile 乾淨、速度也快。

uv init streamlit-deploy-demo
cd streamlit-deploy-demo
uv add streamlit pandas requests pydantic-settings

不用 uv 也沒關係,傳統 venv 一樣可以。

python -m venv .venv
source .venv/bin/activate
pip install streamlit pandas requests pydantic-settings
pip freeze > requirements.txt

建議目錄長這樣:

streamlit-deploy-demo/
├── app.py
├── pyproject.toml
├── uv.lock
├── requirements.txt
├── .gitignore
├── .streamlit/
│   ├── config.toml
│   └── secrets.toml.example
├── src/
│   ├── settings.py
│   └── ui.py
└── README.md

注意:這裡是 secrets.toml.example,不是正式的 secrets.toml。 真正的 secret 不該進 Git。 先寫 .gitignore

.venv/
__pycache__/
.streamlit/secrets.toml
.env
.env.*
*.sqlite
*.db
.DS_Store

這個檔案是部署安全的第一道門。 很多事故不是因為駭客太厲害,而是因為我們自己把鑰匙放在門口地毯下面。

三. 分清楚 config 與 secrets
#

Streamlit 有兩種常見設定。 第一種是 Streamlit 自己的行為設定,例如 theme、server headless、port、runOnSave。 這些放在 .streamlit/config.toml,通常可以 commit。 第二種是 App 需要的秘密資訊,例如 API key、資料庫密碼、第三方服務 token。 這些放在 .streamlit/secrets.toml,或部署平台提供的 secrets 面板。 先看可 commit 的 config.toml

[theme]
base = "light"
primaryColor = "#4f46e5"
backgroundColor = "#ffffff"
secondaryBackgroundColor = "#f8fafc"
textColor = "#0f172a"

[server]
headless = true
runOnSave = false

接著建立範例 secret 檔。

# .streamlit/secrets.toml.example
APP_ENV = "local"
API_BASE_URL = "https://api.example.com"
API_KEY = "replace-me"

[database]
url = "postgresql://user:password@host:5432/app"

本機開發時複製一份:

cp .streamlit/secrets.toml.example .streamlit/secrets.toml

然後把值改成真的。 再次提醒:.streamlit/secrets.toml 必須被 .gitignore 擋住。

四. 用 st.secrets,但不要散落各頁
#

Streamlit 讀 secrets 很簡單。

import streamlit as st

api_key = st.secrets["API_KEY"]
api_base_url = st.secrets.get("API_BASE_URL", "https://api.example.com")

這樣可以跑,但 App 長大以後,你不會想在每個頁面都直接碰 st.secrets。 拍拍君建議把設定集中到 src/settings.py

from dataclasses import dataclass
import streamlit as st

@dataclass(frozen=True)
class Settings:
    app_env: str
    api_base_url: str
    api_key: str
    database_url: str | None = None

def load_settings() -> Settings:
    database = st.secrets.get("database", {})
    return Settings(
        app_env=st.secrets.get("APP_ENV", "local"),
        api_base_url=st.secrets.get("API_BASE_URL", "https://api.example.com"),
        api_key=st.secrets["API_KEY"],
        database_url=database.get("url"),
    )

app.py 使用它。

import streamlit as st
from src.settings import load_settings

settings = load_settings()
st.set_page_config(page_title="拍拍君部署 Demo", page_icon="🚀")
st.title("🚀 拍拍君部署 Demo")
st.caption(f"目前環境:{settings.app_env}")

if settings.api_key:
    st.success("Secrets 載入成功。")

集中設定的好處是:本機、staging、production 都可以用同一份程式碼,只靠設定切換。

五. 不要把 secret 印出來
#

這句聽起來像廢話,但真的很常發生。 Debug 時很多人會寫:

st.write(st.secrets)
print(settings.api_key)

本機看起來沒事,上線以後 log 可能被平台保存、被團隊成員看到,甚至進入外部 log 系統。 比較安全的方式,是只確認 secret 是否存在。

if settings.api_key:
    st.sidebar.success("API key loaded")
else:
    st.sidebar.error("API key missing")

如果真的需要顯示,也只顯示遮罩版。

def mask_secret(value: str, keep: int = 4) -> str:
    if not value:
        return "<missing>"
    if len(value) <= keep:
        return "*" * len(value)
    return "*" * (len(value) - keep) + value[-keep:]

正式 UI 通常連 masked secret 都不用放。 拍拍君的原則很簡單:secret 只拿來用,不拿來展示。

六. 設定分層:local、staging、production
#

小 App 可以只有一份設定。 但只要你有測試環境與正式環境,就會需要分層。 最簡單的做法,是用 APP_ENV 決定要讀哪組預設值。

DEFAULT_API_URLS = {
    "local": "http://localhost:8000",
    "staging": "https://staging-api.example.com",
    "production": "https://api.example.com",
}

def get_api_base_url(app_env: str) -> str:
    return st.secrets.get("API_BASE_URL", DEFAULT_API_URLS[app_env])

本機 .streamlit/secrets.toml 可以長這樣:

APP_ENV = "local"
API_BASE_URL = "http://localhost:8000"
API_KEY = "dev-token"

正式環境則在雲端面板放:

APP_ENV = "production"
API_BASE_URL = "https://api.example.com"
API_KEY = "real-production-token"

同一份程式碼,靠設定切環境。 這才是舒服的部署節奏。

七. Streamlit Community Cloud 部署流程
#

Streamlit Community Cloud 是最容易上手的部署方式。 適合公開 demo、小型 dashboard、教學範例、內部低流量工具。 基本流程是:

  1. 把專案推到 GitHub
  2. 到 Streamlit Community Cloud 新增 App
  3. 選 repo、branch、main file path
  4. 在 Advanced settings 裡貼 secrets
  5. Deploy 如果你的入口檔在根目錄,Main file path 通常填:
app.py

Cloud 會依照 requirements.txtpyproject.toml 或環境設定安裝套件。 如果你用 uv,目前最保險的做法仍是輸出 requirements.txt 給平台。

uv export --format requirements-txt --output-file requirements.txt

正式專案建議鎖版本。

streamlit==1.45.0
pandas==2.2.3
requests==2.32.3
pydantic-settings==2.9.1

版本鎖住,今天能跑的環境,明天比較不會突然變神秘怪物。

八. 在 Community Cloud 設定 secrets
#

Community Cloud 的 secrets 格式跟 .streamlit/secrets.toml 很像。 你可以直接貼 TOML。

APP_ENV = "production"
API_BASE_URL = "https://api.example.com"
API_KEY = "sk_live_xxx"

[database]
url = "postgresql://user:password@host:5432/app"

部署後,在程式裡一樣用:

st.secrets["API_KEY"]
st.secrets["database"]["url"]

如果部署後出現 KeyError,通常代表三種狀況:secret 名稱拼錯、secrets 還沒儲存成功,或 App 沒有重新啟動。 可以先寫一個安全檢查,不印出值,只列出缺少哪些 key。

REQUIRED_SECRETS = ["API_KEY", "API_BASE_URL"]
missing = [key for key in REQUIRED_SECRETS if key not in st.secrets]

if missing:
    st.error(f"缺少設定:{', '.join(missing)}")
    st.stop()

st.stop() 很好用。 必要設定缺失時,與其讓後面噴一堆不知所云的 exception,不如在一開始就乾淨停下來。

九. 本機開發也想用環境變數怎麼辦?
#

有些團隊習慣用 .env。 Streamlit 原生偏向 st.secrets,但你也可以包一層,讓本機與部署平台共存。

uv add python-dotenv

建立 .env,記得不要 commit。

APP_ENV=local
API_BASE_URL=http://localhost:8000
API_KEY=dev-token

設定讀取可以這樣寫。

import os
import streamlit as st
from dotenv import load_dotenv

load_dotenv()

def get_secret(name: str, default: str | None = None) -> str | None:
    if name in st.secrets:
        return st.secrets[name]
    return os.getenv(name, default)

這裡的優先順序是 st.secrets、環境變數、預設值。 部署平台負責正式 secrets,本機可以用 .env 開發。 不要把兩套邏輯散落在各頁面;集中在 settings.py,世界會和平很多。

十. 檔案上傳與暫存資料的陷阱
#

Streamlit 常用 st.file_uploader()。 本機 demo 時,你可能把上傳檔存到 ./uploads。 但很多雲端部署環境的檔案系統不是永久的。 App 重啟、重新部署、容器換機器,檔案就不見了。 所以要分清楚:

  • 小型暫存:可以用記憶體或暫存目錄
  • 使用者資料:要放資料庫、S3、GCS、R2 等外部儲存
  • 可重建快取:可以用 st.cache_data 例如只做本次分析:
import pandas as pd
import streamlit as st

uploaded = st.file_uploader("上傳 CSV", type=["csv"])
if uploaded:
    df = pd.read_csv(uploaded)
    st.dataframe(df.head())

這樣不需要寫硬碟。 如果你真的要寫暫存檔,至少用 tempfile

from pathlib import Path
import tempfile

with tempfile.TemporaryDirectory() as tmpdir:
    path = Path(tmpdir) / uploaded.name
    path.write_bytes(uploaded.getbuffer())

不要假設 ./uploads 會永遠存在。 這是很多 Streamlit App 上線後第一次撞牆的地方。

十一. Docker 部署版本
#

如果你要部署到自己的 VM、Cloud Run、Fly.io、Railway 或 Kubernetes,Docker 會比較可重複。 建立 Dockerfile

FROM python:3.12-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    STREAMLIT_SERVER_HEADLESS=true \
    STREAMLIT_SERVER_PORT=8501 \
    STREAMLIT_SERVER_ADDRESS=0.0.0.0
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8501
CMD ["streamlit", "run", "app.py"]

Build 與 Run:

docker build -t streamlit-deploy-demo .
docker run --rm -p 8501:8501 \
  -e API_KEY="dev-token" \
  -e API_BASE_URL="http://host.docker.internal:8000" \
  streamlit-deploy-demo

容器裡最推薦用環境變數或平台 secret manager,不要把正式 secret bake 進 image。 錯誤示範:

ENV API_KEY=sk_live_xxx

這會讓 secret 進入 image layer。 鑰匙不應該被烤進蛋糕裡。

十二. requirements、pyproject 與版本鎖定
#

部署平台最常見的另一種爆炸,是套件版本漂移。 今天 pip install streamlit pandas 可以跑。 一個月後,某個套件升版,API 行為變了,App 突然壞掉。 最簡單的防線是 requirements.txt 鎖版本。

pip freeze > requirements.txt

如果你用 uv,可以保留 uv.lock,並在需要給平台時輸出 requirements。

uv export --format requirements-txt --output-file requirements.txt

實務上拍拍君會這樣做:repo 裡保留 pyproject.toml、保留 lockfile、部署平台需要時產生 requirements.txt,重要 App 鎖 major/minor 版本。 例如:

streamlit>=1.44,<2
pandas>=2.2,<3
requests>=2.32,<3

這樣既不會太死,也不會完全裸奔。

十三. 上線前安全 checklist
#

部署前,拍拍君建議逐項檢查:

  • .streamlit/secrets.toml 沒有被 Git 追蹤
  • .env 沒有被 Git 追蹤
  • GitHub repo 沒有歷史 secret
  • requirements.txt 或 lockfile 已更新
  • App 啟動時會檢查必要 secrets
  • UI 不會印出完整 API key
  • log 不會印出完整 token
  • production API URL 不是 localhost
  • 使用者上傳資料沒有依賴永久本機檔案
  • README 寫清楚部署方式與必要設定 可以用這個指令確認 secret 檔沒有被追蹤。
git status --short .streamlit/secrets.toml .env

如果你不小心 commit 過 secret,光是刪掉新 commit 不一定夠。 要立刻 revoke / rotate 那個 key,清理 Git history,檢查部署平台與 log。 先換鑰匙,再掃地。 順序不要反。

十四. 一個迷你完整範例
#

最後放一個乾淨版本。 src/settings.py

import os
from dataclasses import dataclass
import streamlit as st

@dataclass(frozen=True)
class Settings:
    app_env: str
    api_base_url: str
    api_key: str

def get_setting(name: str, default: str | None = None) -> str | None:
    if name in st.secrets:
        return st.secrets[name]
    return os.getenv(name, default)

def load_settings() -> Settings:
    api_key = get_setting("API_KEY")
    if not api_key:
        raise RuntimeError("API_KEY is required")
    return Settings(
        app_env=get_setting("APP_ENV", "local"),
        api_base_url=get_setting("API_BASE_URL", "http://localhost:8000"),
        api_key=api_key,
    )

app.py

import streamlit as st
from src.settings import load_settings

st.set_page_config(page_title="拍拍君部署 Demo", page_icon="🚀")
st.title("🚀 Streamlit Deploy Demo")

try:
    settings = load_settings()
except RuntimeError as exc:
    st.error(str(exc))
    st.stop()

st.caption(f"Environment: {settings.app_env}")
st.write("如果你看到這行,代表設定已經成功載入。")

這個範例小歸小,但已經包含幾個重要習慣:設定集中管理、缺少 secret 時提早停止、不把 secret 印出來、本機與部署環境可以用不同來源。 很多穩定的內部工具,就是從這種小骨架長大的。

結語:部署不是最後一步,是設計的一部分
#

Streamlit 讓我們很容易做出「可以互動」的 App。 但如果你希望 App 真的被別人使用,部署就不能只是最後丟上去的動作。 你要一開始就想:設定放哪裡?secret 怎麼管理?套件版本怎麼鎖?資料存在哪裡?壞掉時怎麼知道? 這些問題不華麗,但很重要。 拍拍君的建議是:就算只是小工具,也先養成三個習慣。 第一,所有 secret 都離開程式碼。 第二,所有環境差異都集中在 settings。 第三,部署方式寫進 README。 做到這三件事,你的 Streamlit App 就會從「我電腦可以跑」升級成「大家都可以安心用」。 這才是真正有用的小工具。🚀

延伸閱讀
#

Python 學習 - 本文屬於一個選集。
§ 54: 本文

相關文章

Streamlit 進階:session_state、cache 與多頁 Dashboard 完全攻略
·11 分鐘· loading · loading
Python Streamlit Dashboard Data-App Cache Session-State
Streamlit:用 Python 快速打造互動式資料應用
·8 分鐘· loading · loading
Python Streamlit Data-Visualization Web-App Dashboard
Python Typer 進階:巢狀 subcommands、callback 與 CLI 架構
·9 分鐘· loading · loading
Python Typer Cli Command-Line Developer-Tools Testing
Python uv 進階:workspace、lockfile、script 與專案管理完全攻略
·8 分鐘· loading · loading
Python Uv Workspace Lockfile Packaging Dev Tools
Python argparse 實戰:CLI 參數解析、旗標設計與 subcommands 完全攻略
·9 分鐘· loading · loading
Python Argparse Cli Command-Line Automation Developer-Tools
Python virtual environments 大比拼:venv vs conda vs uv 怎麼選?
·8 分鐘· loading · loading
Python Venv Conda Uv Virtual Environments Packaging