I have a Streamlit app that runs perfectly on my local machine but throws a 400 Bad Request error when deployed on Modal (serverless container).
Here’s the relevant setup:
streamlit_app.py
import streamlit as st
from ultralytics import YOLO
import tempfile, os
from dotenv import load_dotenv
load_dotenv("/root/.env")
os.environ["TMPDIR"] = "/tmp"
tempfile.tempdir = "/tmp"
st.title("🦺 PPE Detection App")
uploaded_file = st.file_uploader("Upload an image", type=["jpg", "png"])
if uploaded_file:
temp_path = os.path.join(tempfile.gettempdir(), uploaded_file.name)
with open(temp_path, "wb") as f:
f.write(uploaded_file.read())
model = YOLO("/root/best.pt")
results = model.predict(source=temp_path, save=True, project="/tmp", name="detections")
st.success("✅ Detection complete!")
modal_app.py:
import modal, shlex, subprocess
from pathlib import Path
image = (
modal.Image.debian_slim(python_version="3.12")
.apt_install("libgl1", "libglib2.0-0", "ffmpeg")
.uv_pip_install("streamlit", "ultralytics", "opencv-python", "supabase", "python-dotenv")
.add_local_file("src/streamlit_app.py", "/root/streamlit_app.py")
.add_local_file("../.env", "/root/.env")
)
app = modal.App(name="ppe-detection", image=image)
@app.function()
@modal.web_server(8000)
def run():
subprocess.Popen(
"streamlit run /root/streamlit_app.py --server.port 8000 --server.enableCORS=false",
shell=True,
)
When I upload even a small (46 KB) image, the log shows:
PUT /_stcore/upload_file/<uuid>/<uuid> -> 400 Bad Request
What I’ve tried:
Verified it works locally with streamlit run.
Set os.environ["TMPDIR"] = "/tmp" and ensured YOLO outputs to /tmp.
Confirmed .env and model weights load correctly.
Added write permissions for /tmp inside Modal.
Still, the upload consistently fails with 400 Bad Request.
How can I successfully use st.file_uploader() with Modal deployment? Does Modal restrict file uploads through Streamlit’s internal API, or do I need to configure Streamlit’s temp directory differently?