1

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?

1 Answer 1

0

The 400 Bad Request error happens because Streamlit’s file uploader uses a PUT request to its internal route (/_stcore/upload_file/...), and Modal’s web proxy doesn’t forward Streamlit’s XSRF headers the way Streamlit expects so it thinks the request is unsafe and rejects it.

To fix it, you just need to disable Streamlit’s XSRF protection (and usually CORS too) when you start the app. Try this:

import modal, subprocess

image = (
    modal.Image.debian_slim(python_version="3.12")
    .apt_install("libgl1", "libglib2.0-0", "ffmpeg")
    .uv_pip_install(
        "streamlit~=1.35",
        "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():
    cmd = (
        "streamlit run /root/streamlit_app.py "
        "--server.port 8000 "
        "--server.enableCORS=false "
        "--server.enableXsrfProtection=false "
        "--browser.gatherUsageStats=false"
    )
    subprocess.Popen(cmd, shell=True)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.