St.download_button inconsistent behavior in a docker deployment in cloud

I am trying to download a pdf file from within my app using st.download_button. The button works perfectly when I run it in my localhost. But when I deploy the code to the cloud (Digital Ocean) in a docker image and run as a docker container, the download becomes inconsistent. Sometimes it works, sometime it fails and only downloads a ***.bin file. Here is my sample code:

if os.path.isfile(st.session_state.image_path + "Analysis_Report.pdf") :
    with open(st.session_state.image_path + "Analysis_Report.pdf", "rb") as pdf_file:
        PDFbyte = pdf_file.read()

st.download_button(
    label="PDF Report",
    data=PDFbyte,
    file_name= idea_id+"_Analysis_Report.pdf",
    #mime="application/pdf"
    mime = "application/octet-stream",
    key = "pdf_download",
    on_click='ignore',
icon = ":material/download:",
)

Few more points:
1. The “Analysis_Report.pdf” is fetched from a NFS server in the cloud.

  1. I have tried copying the pdf to local app/static/ directory (with static serving turned on) but the result is still inconsistent.

  2. I tried both pdf and octet-stream mime types - no difference,

    How can I resolve this issue?

Welcome to the Streamlit community and thanks for your detailed question! :balloon: This is a common issue when moving from local to cloud deployments, especially with files served from NFS or dynamic storage. The inconsistent downloads (sometimes a .bin file, sometimes a PDF) are usually due to timing or file availability—Streamlit expects the file to be present and readable at the exact moment the button is rendered. If the file is missing, empty, or locked, the download may fail or return a generic .bin file.

To resolve this, ensure the PDF file is fully available and readable before rendering the download button. Always open the file in binary mode, reset any file-like object pointers with .seek(0), and avoid relying on files written to disk at runtime in cloud environments, as file persistence and access timing can be unreliable. Instead, read the file into memory (e.g., as bytes) just before passing it to st.download_button. Here’s a robust pattern:

import streamlit as st
import os

pdf_path = os.path.join(st.session_state.image_path, "Analysis_Report.pdf")
PDFbyte = None
if os.path.isfile(pdf_path):
    with open(pdf_path, "rb") as pdf_file:
        PDFbyte = pdf_file.read()

if PDFbyte:
    st.download_button(
        label="PDF Report",
        data=PDFbyte,
        file_name=f"{idea_id}_Analysis_Report.pdf",
        mime="application/pdf",
        key="pdf_download",
        on_click="ignore",
        icon=":material/download:",
    )

Make sure the file is not being written or modified at the time of download, and that your app has the correct permissions. If issues persist, try storing the PDF in memory (e.g., as a BytesIO object) and ensure the file is always ready before rendering the button. For more, see the Streamlit docs for st.download_button and forum discussions on download issues.

Sources:

This code doesn’t work.