Streamlit大模型部署:突破100MB上传限制的架构解耦方案 1. 项目概述为什么大模型部署在 Streamlit 上会卡在“上传失败”这一步Streamlit 是我过去三年里用得最顺手的 Python 快速原型工具——写个数据看板、做个算法 demo、甚至给客户现场演示模型推理十几行代码就能跑起来。但直到去年帮一个医疗 AI 团队做 PoC 时我才第一次被它“温柔地拒之门外”他们训练好的CT 图像分割模型.onnx 格式137MB本地streamlit run app.py能跑通可一旦推到 Streamlit Community Cloud 或自建服务器上上传环节就直接报错File size exceeds maximum allowed (100MB)。不是超时不是内存溢出是硬性拦截——连文件都进不了服务端。这背后其实是个典型的“开发友好性”与“生产约束性”的错位。Streamlit 的设计哲学是轻量、快速、面向探索式开发所以默认把整个应用含模型权重、依赖包、静态资源打包成单体镜像通过其托管平台或 Docker 部署。而 100MB 这个阈值不是技术瓶颈而是平台为保障冷启动速度、镜像分发效率和多租户稳定性设定的安全水位线。它不阻止你用大模型而是逼你换一种思路模型不该是 Streamlit 应用的“行李”而该是它随时可调用的“远程服务”。这个标题里的关键词——“Deploy Models Larger than 100MB on Streamlit”——表面看是讲“怎么绕过限制”实则是一次架构思维的切换从“把模型塞进前端”转向“让 Streamlit 做好调度员”。它适合三类人一是刚训完大模型、急着做 demo 却被上传卡住的算法工程师二是负责内部工具链建设、需要统一管理多个模型服务的 MLOps 工程师三是教学场景下想让学生直观看到大模型效果又不想让他们陷入复杂部署细节的讲师。你不需要懂 Kubernetes也不必重写整个推理逻辑只需要理解模型服务的通信边界在哪里、Streamlit 的请求生命周期如何被利用、以及哪几类模型能真正“无感迁移”。我试过五种主流方案直接压缩模型失败ONNX 不支持 LZ4 多层嵌套、拆包上传再合并失败Streamlit Cloud 不允许运行时写磁盘、用 Hugging Face Hub 托管可行但延迟高、私有模型受限、自建 FastAPI 服务稳定但需额外运维、以及最终落地的Nginx Model Server Streamlit 异步轮询组合。后两种不是“技巧”而是生产环境里真正扛住日均 2000 请求的方案。接下来我会把每一步的选型依据、参数计算、实操陷阱全部摊开讲清楚——包括为什么 Nginx 的client_max_body_size要设为 0为什么 FastAPI 的StreamingResponse比JSONResponse更适合大模型输出以及 Streamlit 的st.session_state如何成为跨请求状态同步的关键枢纽。2. 整体架构设计为什么放弃“单体打包”选择“前后端解耦”2.1 核心矛盾Streamlit 的运行机制 vs 大模型的加载特性先说清楚一个常被误解的前提Streamlit 本身并不限制模型大小。它的核心限制来自两个层面一是托管平台如 Streamlit Community Cloud对上传 ZIP 包的硬性体积限制100MB二是 Streamlit 的执行模型——每次用户交互如点击按钮、滑动 slider都会触发整个脚本重执行如果模型加载逻辑写在main()里每次交互都要重新torch.load()或onnxruntime.InferenceSession()137MB 模型光加载就要 8~12 秒用户点一次等半分钟体验直接归零。我画过一张本地调试时的火焰图当模型加载放在if st.button(Run):内部CPU 时间 92% 耗在torch._C._load_for_gpu上而实际推理只占 5%。这说明问题不在推理慢而在加载不可复用。Streamlit 的设计本意是让 UI 逻辑轻量、状态驱动模型加载这种重型操作必须剥离出“热路径”。所以解耦不是为了炫技而是回归本质Streamlit 只负责三件事渲染 UI、收集用户输入图像、文本、参数、发起 HTTP 请求、解析响应、更新 UI 状态模型服务只负责一件事接收标准化请求如POST /predict加载一次模型到内存持续提供低延迟推理两者之间用 REST API 通信协议清晰、语言无关、调试方便。提示不要试图用subprocess在 Streamlit 中调用本地python model_server.py。Streamlit Cloud 禁止子进程自建服务器也因权限和端口隔离导致不可靠。HTTP 是唯一被所有环境一致支持的通信方式。2.2 方案选型对比为什么最终锁定 FastAPI Nginx 组合我实测了四种常见替代方案表格列出了关键指标基于 137MB ONNX 模型、AWS t3.medium 实例、100 并发请求压测方案部署复杂度冷启动时间平均首字节延迟私有模型支持运维成本我的实测结论Hugging Face Inference Endpoints★☆☆☆☆极低3.2s首次调用480ms✅Pro 计划★☆☆☆☆零免费层限流严重私有模型需付费延迟波动大不适合实时交互自建 Flask API★★☆☆☆低1.8s320ms✅★★☆☆☆中轻量但无异步支持大模型推理时阻塞其他请求Gunicorn worker 数配置不当易 OOMTriton Inference Server★★★★☆高0.9s180ms✅★★★★☆高NVIDIA 生态专用CPU 推理支持弱配置 YAML 复杂小团队学习成本高FastAPI Uvicorn Nginx★★★☆☆中1.1s210ms✅★★★☆☆中胜出原生异步、Pydantic 校验强、OpenAPI 文档自动生成Nginx 缓存/负载均衡/SSL 终结一揽子解决FastAPI 胜出的关键在于它对“大模型服务”的三个隐性适配异步生命周期管理app.on_event(startup)中加载模型确保只执行一次且加载完成前拒绝请求避免 503Pydantic 模型校验用户上传的图像自动校验尺寸、格式、base64 编码合法性错误直接返回422 Unprocessable EntityStreamlit 端可精准提示StreamingResponse 支持对于生成式模型如 LLM可边推理边流式返回 tokenUI 端用st.write_stream()实现打字机效果体验远超一次性 JSON 返回。Nginx 不是可选项而是必选项。原因有三突破 100MB 上传限制Streamlit 应用本身仍 100MB只含 UI 代码模型服务独立部署Nginx 作为反向代理将/api/predict请求转发给后端上传行为发生在 Nginx 层不受 Streamlit 限制缓冲大响应体模型输出可能是 5MB 的分割掩码图Nginx 的proxy_buffering on和proxy_buffers 8 16k避免上游服务因客户端网络慢而卡死SSL 终结与负载均衡单台服务器可挂载多个模型服务/api/segment/,/api/classify/Nginx 按 path 分流未来横向扩展只需加机器改 upstream。注意Nginx 的client_max_body_size必须设为0不限制或明确大于模型输入上限如50m。默认 1MB上传 10MB 图片直接 413 Request Entity Too Large。这是新手踩坑率最高的配置项。2.3 架构拓扑图数据流向比代码更重要整个系统只有三个实体User Browser运行 Streamlit 前端用户操作入口Streamlit App Server仅含app.py和依赖监听:8501通过requests.post(https://your-domain.com/api/predict)发起请求Model Service Server运行 FastAPI监听:8000Nginx 后端加载模型处理推理。数据流严格单向Browser → Streamlit (UI 渲染) → HTTPS POST to Nginx → Nginx proxy_pass to FastAPI → FastAPI 加载模型 → 推理 → 返回 JSON/bytes → Nginx 缓冲 → Streamlit 解析 → UI 更新没有 WebSocket没有长连接全是标准 HTTP。这意味着你可以用curl -X POST https://your-domain.com/api/predict -F imagetest.jpg直接测试后端完全脱离 Streamlit。这种解耦带来的最大好处是模型服务可以独立压测、独立监控、独立升级Streamlit 应用甚至可以换成 Gradio 或纯 HTML只要 API 协议不变前端就无需修改。我见过太多团队把模型硬塞进 Streamlit结果模型迭代一次就得全量重部署UI 小修一个按钮也要等 10 分钟构建。而解耦后模型服务升级只需git pull systemctl restart model-serviceStreamlit 端git pull streamlit run app.py两者互不影响。这才是工程化的起点。3. 核心实现细节从模型加载到 Streamlit 调用的完整链路3.1 模型服务端FastAPI 的健壮加载与推理封装FastAPI 服务的核心不是写得多炫而是加载稳、容错强、日志清。以下是我生产环境使用的main.py骨架已删减非核心逻辑# main.py from fastapi import FastAPI, File, UploadFile, HTTPException, Depends from fastapi.responses import StreamingResponse, JSONResponse from pydantic import BaseModel import onnxruntime as ort import numpy as np import cv2 import logging from typing import Optional, Generator # 配置日志关键操作必须留痕 logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) # 全局模型实例startup 时加载一次 ort_session: Optional[ort.InferenceSession] None class PredictionRequest(BaseModel): threshold: float 0.5 return_mask: bool True # 启动时加载模型带重试和超时 app.on_event(startup) async def load_model(): global ort_session model_path /app/models/ct_segment.onnx # 挂载的卷路径 max_retries 3 for attempt in range(max_retries): try: logger.info(fLoading ONNX model from {model_path}, attempt {attempt 1}) # CPU 推理指定 providers 显式禁用 GPU避免 CUDA 初始化失败 ort_session ort.InferenceSession( model_path, providers[CPUExecutionProvider] ) # 验证输入输出签名 input_name ort_session.get_inputs()[0].name output_name ort_session.get_outputs()[0].name logger.info(fModel loaded successfully. Input: {input_name}, Output: {output_name}) return except Exception as e: logger.error(fFailed to load model on attempt {attempt 1}: {e}) if attempt max_retries - 1: raise HTTPException(status_code500, detailModel loading failed after retries) await asyncio.sleep(2 ** attempt) # 指数退避 app.post(/api/predict) async def predict( file: UploadFile File(...), request: PredictionRequest Depends() ): global ort_session if ort_session is None: raise HTTPException(status_code503, detailModel not ready) try: # 1. 文件读取与校验防恶意上传 contents await file.read() if len(contents) 0: raise HTTPException(status_code400, detailEmpty file uploaded) if len(contents) 50 * 1024 * 1024: # 50MB 限制 raise HTTPException(status_code413, detailFile too large) # 2. 图像解码与预处理此处简化实际需匹配训练 pipeline nparr np.frombuffer(contents, np.uint8) img cv2.imdecode(nparr, cv2.IMREAD_COLOR) if img is None: raise HTTPException(status_code400, detailInvalid image format) # 调整尺寸、归一化、转 tensor省略具体代码重点在结构 processed_img preprocess(img) # 返回 [1, 3, 512, 512] numpy array # 3. 模型推理核心耗时步骤 logger.info(Starting inference...) start_time time.time() outputs ort_session.run(None, {input: processed_img}) inference_time time.time() - start_time logger.info(fInference completed in {inference_time:.2f}s) # 4. 后处理与响应构造 mask postprocess(outputs[0], thresholdrequest.threshold) if request.return_mask: # 返回 base64 编码的 PNG避免二进制传输乱码 _, buffer cv2.imencode(.png, mask) mask_b64 base64.b64encode(buffer).decode(utf-8) return JSONResponse({ status: success, mask: mask_b64, inference_time: inference_time, model_version: v1.2.0 }) else: return JSONResponse({status: success, inference_time: inference_time}) except HTTPException: raise except Exception as e: logger.error(fPrediction error: {e}) raise HTTPException(status_code500, detailfInternal error: {str(e)})关键细节解析app.on_event(startup)加载时机Uvicorn 启动后、接受请求前执行确保模型在第一个请求到达前已就绪。async修饰符允许异步等待但模型加载本身是同步阻塞所以用await asyncio.sleep()做退避而非time.sleep()阻塞事件循环。providers[CPUExecutionProvider]显式指定 CPU避免在无 GPU 环境下尝试初始化 CUDA 导致启动失败。若需 GPU改为[CUDAExecutionProvider]并确保容器安装onnxruntime-gpu。文件校验双保险len(contents) 50MB是应用层限制Nginx 层client_max_body_size 50m是基础设施层限制两者必须一致否则 Nginx 先拦截FastAPI 根本收不到请求。日志粒度记录“开始推理”、“推理完成”时间戳配合 Prometheus 抓取可绘制 P95 推理延迟曲线。线上发现某次模型版本升级后延迟从 1.2s 升至 3.8s日志第一时间暴露问题。3.2 Streamlit 前端如何优雅处理大文件上传与异步响应Streamlit 的st.file_uploader默认支持大文件但有两个致命陷阱一是上传过程无进度条用户不知卡在哪二是st.button触发后整个脚本重执行若模型服务响应慢UI 会“假死”。解决方案是st.session_statest.rerun()st.empty()三件套。以下是app.py的核心逻辑精简版# app.py import streamlit as st import requests import base64 import json from PIL import Image import io # 初始化 session state存储上传文件和结果 if uploaded_file not in st.session_state: st.session_state.uploaded_file None if result not in st.session_state: st.session_state.result None if is_processing not in st.session_state: st.session_state.is_processing False st.title(CT Segmentation Demo) st.markdown(Upload a CT scan image (JPG/PNG) for automatic organ segmentation.) # 1. 文件上传区域带状态记忆 uploaded_file st.file_uploader( Choose an image..., type[jpg, jpeg, png], keyuploader, # 关键key 确保组件状态绑定 on_changelambda: setattr(st.session_state, uploaded_file, uploaded_file) ) # 2. 参数控制区独立于上传避免重载 st.sidebar.header(Inference Parameters) threshold st.sidebar.slider(Confidence Threshold, 0.1, 0.9, 0.5, 0.05) return_mask st.sidebar.checkbox(Return Segmentation Mask, valueTrue) # 3. 主处理逻辑分离“触发”与“执行” if uploaded_file is not None and not st.session_state.is_processing: # 显示上传成功状态 st.success(f✅ Uploaded: {uploaded_file.name} ({uploaded_file.size / 1024 / 1024:.1f} MB)) # 创建空容器用于后续动态更新 status_placeholder st.empty() result_placeholder st.empty() # 按钮点击即标记为处理中并立即 rerun避免阻塞 if st.button( Run Segmentation): st.session_state.is_processing True st.rerun() # 立即刷新进入下方 processing 分支 # 4. 处理中分支显示进度、发起请求、轮询结果 if st.session_state.is_processing and st.session_state.uploaded_file is not None: status_placeholder.info(⏳ Sending request to model server...) try: # 构造请求数据 files {file: (st.session_state.uploaded_file.name, st.session_state.uploaded_file.getvalue())} data {threshold: threshold, return_mask: return_mask} # 同步 POST 请求Streamlit 不支持原生 async用 requests 即可 response requests.post( https://your-domain.com/api/predict, filesfiles, datadata, timeout120 # 大模型必须设长 timeout ) if response.status_code 200: result response.json() st.session_state.result result st.session_state.is_processing False st.rerun() # 处理完成刷新显示结果 else: status_placeholder.error(f❌ Model server error: {response.status_code} - {response.text}) st.session_state.is_processing False except requests.exceptions.Timeout: status_placeholder.error(❌ Request timed out. Check model server status.) st.session_state.is_processing False except Exception as e: status_placeholder.error(f❌ Connection error: {str(e)}) st.session_state.is_processing False # 5. 结果展示分支仅当 result 存在时渲染 if st.session_state.result is not None: result st.session_state.result st.success(✅ Segmentation completed!) col1, col2 st.columns(2) with col1: st.subheader(Original Image) st.image(st.session_state.uploaded_file, use_column_widthTrue) if result.get(mask): with col2: st.subheader(Segmentation Mask) # base64 解码并显示 mask_bytes base64.b64decode(result[mask]) mask_img Image.open(io.BytesIO(mask_bytes)) st.image(mask_img, use_column_widthTrue) st.info(f⏱️ Inference time: {result.get(inference_time, N/A):.2f}s | Model: {result.get(model_version, N/A)})为什么这样设计st.session_state是灵魂它让 Streamlit 的“重执行”模型变得可控。上传文件、处理状态、结果数据都存在内存里st.rerun()后不会丢失用户感觉是“页面局部刷新”而非传统网页跳转。st.empty()容器是关键它创建一个可被后续内容覆盖的占位符。上传时显示“Sending request...”失败时覆盖为错误信息成功后覆盖为结果。避免了st.text(...)这种静态文本无法更新的尴尬。timeout120是底线137MB 模型在 CPU 上推理可能达 80 秒requests默认 timeout 是 (30, 30)连接 30 秒、读取 30 秒必然超时。必须显式延长。实操心得Streamlit 的st.cache_resource不能用于缓存模型对象因为它要求函数无副作用而模型加载有 IO 和内存分配但st.session_state完美替代。我曾用st.cache_resource尝试缓存ort_session结果每次st.rerun()都报RuntimeError: Session was closed根源就是缓存机制与 Streamlit 执行模型冲突。3.3 Nginx 配置不只是反向代理更是流量守门员Nginx 配置文件/etc/nginx/conf.d/streamlit.conf是整个链路的“交通指挥中心”。以下是生产环境精简版删除 SSL 证书路径等敏感信息upstream model_service { server 127.0.0.1:8000; # FastAPI 服务地址 keepalive 32; # 保持长连接减少握手开销 } server { listen 443 ssl http2; server_name your-domain.com; # SSL 配置略 # Streamlit 应用/ 路径 location / { proxy_pass http://127.0.0.1:8501; # Streamlit 默认端口 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; 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; } # 模型 API/api/ 路径关键 location /api/ { proxy_pass http://model_service/; proxy_http_version 1.1; 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; # 突破 100MB 限制核心配置 client_max_body_size 50m; # 缓冲大响应体避免 FastAPI 因客户端慢而卡住 proxy_buffering on; proxy_buffers 8 16k; proxy_buffer_size 32k; proxy_busy_buffers_size 64k; # 超时设置匹配 FastAPI 的 long-running 推理 proxy_connect_timeout 5s; proxy_send_timeout 120s; proxy_read_timeout 120s; # 传递原始 Host便于 FastAPI 日志记录 proxy_set_header Host $host; } }逐项解释location /api/而非/api/predict用前缀匹配未来可轻松扩展/api/classify,/api/detect等新接口无需改 Nginx。client_max_body_size 50m这是突破 Streamlit 100MB 限制的物理开关。必须大于用户可能上传的最大图像如 4K CT 图 20MB我设为 50m 留余量。proxy_buffers系列当模型返回 5MB 的 PNG 时Nginx 会先缓存到内存/磁盘再分块传给 Streamlit。若关闭proxy_bufferingNginx 会等 FastAPI 完全生成响应才开始传期间 FastAPI 进程被阻塞无法处理新请求。proxy_read_timeout 120s这是 Nginx 等待 FastAPI 响应的最长时间。必须 ≥ FastAPI 的timeout和 Streamlit 的requests.timeout否则 Nginx 先断连返回 504 Gateway Timeout。注意proxy_pass末尾的/很关键。proxy_pass http://model_service/;会将/api/predict重写为/predict发送给后端若写成proxy_pass http://model_service;无斜杠则原样转发/api/predict后端 FastAPI 必须定义app.post(/api/predict)增加耦合。推荐前者保持后端路由简洁。4. 实操全流程从本地验证到上线部署的每一步4.1 本地开发与联调如何在一台机器上模拟生产环境本地验证是避免上线翻车的第一道防线。我的流程是先独立验证后端再联调前端最后模拟 Nginx。Step 1独立启动 FastAPI 服务# 创建虚拟环境 python -m venv venv-model source venv-model/bin/activate # Windows: venv-model\Scripts\activate pip install fastapi[all] onnxruntime opencv-python # 启动服务不带 reload避免模型重复加载 uvicorn main:app --host 0.0.0.0 --port 8000 --workers 1然后用curl测试curl -X POST http://localhost:8000/api/predict \ -F filetest_ct.jpg \ -F threshold0.5 \ -F return_masktrue预期返回 JSON包含mask字段。若失败看 FastAPI 控制台日志90% 的问题是路径错误model_path、ONNX 版本不兼容用onnxruntime1.15.1而非最新版、或图像预处理维度不匹配。Step 2启动 Streamlit 并直连后端# 新终端激活另一个 venv python -m venv venv-streamlit source venv-streamlit/bin/activate pip install streamlit requests # 修改 app.py 中的 API 地址为 http://localhost:8000/api/predict streamlit run app.py --server.port8501此时浏览器打开http://localhost:8501上传图片观察是否正常返回。关键检查点Streamlit 控制台无ConnectionRefused错误FastAPI 控制台打印Starting inference...和Inference completed in X.XXsUI 显示原图和分割掩码。Step 3本地模拟 Nginx可选但强烈推荐安装 NginxMac:brew install nginxUbuntu:sudo apt install nginx用上述配置文件替换/usr/local/etc/nginx/nginx.confMac或/etc/nginx/nginx.confUbuntu然后sudo nginx -t # 检查配置语法 sudo nginx -s reload # 重载修改app.py中 API 地址为https://localhost/api/predict注意是 httpsNginx 默认启用了 SSL重启 Streamlit。此时流量路径为Streamlit → localhost:443 (Nginx) → localhost:8000 (FastAPI)这一步能提前暴露proxy_pass路径重写、SSL 证书、client_max_body_size等配置问题。4.2 Docker 容器化为什么必须用多阶段构建生产环境必须容器化但直接pip install所有依赖会导致镜像臃肿1GB。我的Dockerfile采用多阶段构建最终镜像仅 327MB# 构建阶段编译依赖安装 ONNX Runtime FROM python:3.9-slim AS builder RUN apt-get update apt-get install -y build-essential libglib2.0-0 rm -rf /var/lib/apt/lists/* COPY requirements.txt . RUN pip wheel --no-cache-dir --no-deps --wheel-dir /wheels -r requirements.txt # 运行阶段最小化基础镜像 FROM python:3.9-slim # 复制编译好的 wheel COPY --frombuilder /wheels /wheels # 安装 wheel不联网 RUN pip install --no-cache-dir --find-links /wheels --no-index onnxruntime # 复制应用代码 COPY . /app WORKDIR /app # 创建非 root 用户安全最佳实践 RUN adduser -u 1001 -G users -D appuser chown -R appuser:users /app USER appuser # 暴露端口 EXPOSE 8000 CMD [uvicorn, main:app, --host, 0.0.0.0:8000, --port, 8000, --workers, 1]requirements.txt内容精简到极致fastapi[all]0.104.1 onnxruntime1.15.1 opencv-python-headless4.8.1.78 pydantic1.10.12为什么不用onnxruntime-gpu因为 Streamlit Cloud 和多数低成本 VPS 无 GPUCPU 版本更通用。若确定有 GPU替换为onnxruntime-gpu1.15.1并在Dockerfile中用nvidia/cuda:11.7.1-runtime-ubuntu20.04基础镜像。构建与运行命令# 构建模型服务镜像 docker build -t ct-segment-model:latest . # 运行挂载模型文件 docker run -d \ --name ct-model \ -p 8000:8000 \ -v $(pwd)/models:/app/models \ -e PYTHONUNBUFFERED1 \ ct-segment-model:latest4.3 部署到云服务器Nginx systemd 的黄金组合我用的是 AWS EC2 t3.medium2vCPU, 4GB RAM系统 Ubuntu 22.04。部署流程如下1. 安装基础软件sudo apt update sudo apt install -y nginx python3-pip python3-venv git curl sudo systemctl enable nginx2. 部署模型服务# 创建部署目录 sudo mkdir -p /opt/model-service sudo chown $USER:$USER /opt/model-service cd /opt/model-service # 克隆代码或复制构建好的镜像 git clone https://github.com/your-org/ct-segment-api.git . # 创建虚拟环境 python3 -m venv venv source venv/bin/activate pip install -r requirements.txt # 创建 systemd 服务文件 sudo tee /etc/systemd/system/model-service.service /dev/null EOF [Unit] DescriptionCT Segmentation Model Service Afternetwork.target [Service] Typesimple Userubuntu WorkingDirectory/opt/model-service ExecStart/opt/model-service/venv/bin/uvicorn main:app --host 127.0.0.1:8000 --port 8000 --workers 1 Restartalways RestartSec10 EnvironmentPYTHONUNBUFFERED1 [Install] WantedBymulti-user.target EOF sudo systemctl daemon-reload sudo systemctl enable model-service sudo systemctl start model-service3. 配置 Nginx# 替换默认配置 sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/streamlit.conf sudo ln -sf /etc/nginx/sites-available/streamlit.conf /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx4. 部署 Streamlit 应用# 创建 Streamlit 目录 sudo mkdir -p /opt/streamlit-app sudo chown $USER:$USER /opt/streamlit-app cd /opt/streamlit-app # 复制 app.py 和 requirements.txt pip3 install streamlit requests # 创建 systemd 服务Streamlit 官方不推荐 systemd但生产环境必须 sudo tee /etc/systemd/system/streamlit-app.service /dev/null EOF [Unit] DescriptionStreamlit CT Segmentation App Afternetwork.target [Service] Typesimple Userubuntu WorkingDirectory/opt/streamlit-app ExecStart/usr/bin/streamlit run app.py --server.port8501 --server.address127.0.0.1 Restartalways RestartSec10 [Install] WantedBymulti-user.target EOF sudo systemctl daemon-reload sudo systemctl enable streamlit-app sudo systemctl start streamlit-app5. 域名与 SSL使用 Certbotsudo snap install core; sudo snap refresh core sudo snap install --classic certbot sudo