Streamlit App Custom Icon Not Displaying on iOS Home Screen (apple-touch-icon Failure)
umitarsn
HOBBYOP

19 days ago

Hello,

I am experiencing a critical issue with my Streamlit application deployed on [Railway/Streamlit Cloud]. When users attempt to save the application URL to their iOS Home Screen (as a PWA shortcut), the displayed icon is consistently the default Streamlit 'F' icon or a generic web icon, instead of my custom logo.

We have exhausted all known web-based workarounds.

mag emoji Issue Details and Exhaustive Steps Taken

We suspect this is an architectural bug stemming from how Streamlit handles the HTML <head> tag and serves static files, which prevents iOS from locating the required icon.

  1. Goal: To display a custom apple-touch-icon-180x180.png (180x180 px PNG) on the iOS home screen shortcut.

  2. Failed Workarounds (Attempted and Confirmed Failed):

    • External Links: Injecting the icon using the GitHub Raw URL via st.markdown and <link rel="apple-touch-icon"...>.

    • Base64/Manifest Injection: Converting the image to Base64 and attempting to inject it into the HTML using Streamlit's private components (st.components.v1.html) alongside a Web Manifest file.

    • Direct File Placement: Placing the icon file (apple-touch-icon.png) directly in the GitHub repository's root directory, relying on the web server's default behavior, while removing all icon-related code from app.py.

    • Version Downgrade: Testing the application on older Streamlit versions (1.32.0 and 1.24.0).

  3. Observed Root Cause: Community reports suggest that Streamlit's infrastructure often fails to serve the icon file at the root path (/apple-touch-icon.png) with a 200 OK response, leading to a 404 Not Found error during the iOS caching process.

question emoji Request for Assistance

Since all code-level fixes have failed, we believe the solution lies in the hosting environment's configuration.

Can you provide guidance on how to make Build/Deploy command for this Streamlit container to ensure the static files, specifically apple-touch-icon.png, are directly and correctly served from the root path, bypassing Streamlit's internal HTML rendering?

Thank you for your assistance in resolving this persistent brand visibility issue

Solved$10 Bounty

5 Replies

bytekeim
PRO

19 days ago

I eventually figured out why the custom apple-touch-icon never shows up when adding a Streamlit app to the iOS home screen, and it turns out it’s not an iOS problem at all—it's just how Streamlit’s server works. Streamlit runs on Tornado, and Tornado doesn’t serve files from the root path unless the app tells it to. So when iOS tries to fetch /apple-touch-icon.png, it just gets a 404. Putting the file in the repo root does nothing, adding <link> tags in Streamlit does nothing, and even the built-in “static” folder isn’t helpful because Streamlit only exposes that under /app/static/..., not /.
This lines up with what other people mentioned on the Streamlit forum: the app basically hijacks all requests at the root level, so you can’t really make iOS happy without going around Streamlit entirely.

What actually works is sticking a proper reverse proxy (Nginx) in front of Streamlit. That way Nginx can serve the icon at /apple-touch-icon.png and forwards everything else to the Streamlit server. After looking into what folks have done on Heroku, AWS, etc., this seems to be the cleanest solution. And the nice thing is: on Railway you can do this with a single Dockerfile in your repo—Railway will detect it and use that instead of Nixpacks.

Below is the setup I ended up using. Just drop these files in the repo root (next to your Streamlit app and requirements.txt), make sure the icon file is named apple-touch-icon.png and is 180×180, and Railway should build it automatically.

Dockerfile

FROM python:3.12-slim

RUN apt-get update && apt-get install -y \
    nginx \
    build-essential \
    curl \
    software-properties-common \
    git \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . .

RUN pip3 install -r requirements.txt

COPY nginx.conf /etc/nginx/sites-enabled/default

COPY start.sh /app/start.sh
RUN chmod +x /app/start.sh

EXPOSE $PORT

HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health

ENTRYPOINT ["/app/start.sh"]

nginx.conf

(Handles the icon at root and proxies everything else to Streamlit.)

server {
    listen $PORT default_server;
    server_name _;

    # Serve apple-touch-icon at root
    location = /apple-touch-icon.png {
        alias /app/apple-touch-icon.png;
        expires 1y;
        add_header Cache-Control "public";
    }

    # Proxy all other traffic to Streamlit
    location / {
        proxy_pass http://127.0.0.1:8501/;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 86400;
    }
}

start.sh

(Starts Nginx and Streamlit together.)

#!/bin/bash
nginx -g 'daemon off;' &
streamlit run app.py --server.port=8501 --server.address=127.0.0.1 --server.enableCORS=false --server.enableXsrfProtection=false

Change app.py to your real entrypoint if needed.

Deployment & Testing Notes

  1. Commit and push everything; Railway will build based on the Dockerfile automatically.

  2. After deployment, test directly:
    https://yourapp.up.railway.app/apple-touch-icon.png
    If you get a 200 OK, you’re good.

  3. iOS aggressively caches icons, so you might need to delete the shortcut, clear Safari history, restart the device, etc.

  4. If the icon still refuses to update, you can also provide the other sizes (120×120, 152×152, etc.) and add similar location = blocks in the Nginx config.

This whole approach avoids messing with Streamlit’s internals (which is possible, but incredibly fragile—people have tried adding Tornado handlers manually and it breaks every other release). With Nginx in front, the icon finally loads from the root like iOS expects, and everything else stays working like normal.


umitarsn
HOBBYOP

19 days ago

Hello bytekeim,

Thanks again for your detailed advice about using Nginx in front of Streamlit to serve the apple-touch-icon.png. I followed your instructions and updated my repo with a Dockerfile, nginx.conf, and start.sh, but I’m now getting a build error on Railway.

I’m attaching a screenshot of the latest Railway Build Logs. The logs only show the snapshot steps and then the generic message:

“Failed to build an image. Please check the build logs for more details.”

I don’t see any explicit error message (like a Docker or pip error), so I’m not sure what is actually failing in the build process.

For reference, here is my current setup:

Dockerfile (relevant part):

FROM python:3.12-slim

RUN apt-get update && apt-get install -y \
    nginx \
    build-essential \
    curl \
    software-properties-common \
    git \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . .

RUN pip3 install -r requirements.txt

COPY nginx.conf /etc/nginx/sites-enabled/default

COPY start.sh /app/start.sh
RUN sed -i 's/\r$//' /app/start.sh
RUN chmod +x /app/start.sh

EXPOSE 8501

HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health

ENTRYPOINT ["/app/start.sh"]

start.sh:

#!/bin/bash
nginx -g 'daemon off;' &
streamlit run app.py --server.port=8501 --server.address=127.0.0.1 --server.enableCORS=false --server.enableXsrfProtection=false

Railway’s “Custom Start Command” and “Pre-deploy Command” settings are empty, so the container should just use the Dockerfile’s ENTRYPOINT.

Do you have any idea what might be causing this build failure, or how I can get more detailed error output from Railway to debug it? Any hints or suggestions would be really appreciated.

Github repo : umitarsn/BG-ArcOptimizer_v3

Railway : https://railway.com/invite/zhqmNfk1yV1

Thanks in advance!


bytekeim
PRO

19 days ago

Here are the steps you should apply to fix the build issue and get the apple-touch-icon served correctly on Railway.

1. Update your Dockerfile

The main issue comes from installing packages without running apt-get update first. You should adjust your Dockerfile like this:

FROM python:3.12-slim
ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
    && apt-get install -y --no-install-recommends nginx curl build-essential \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . /app

RUN pip install --no-cache-dir -r requirements.txt

COPY nginx.conf /etc/nginx/nginx.conf
COPY start.sh /start.sh
RUN chmod +x /start.sh

EXPOSE 80 8501
ENTRYPOINT ["/start.sh"]

This ensures the system packages install properly and avoids the build hanging.

2. Consider the Python base image

Python 3.12 slim works on Railway, but some packages now require extra build tools. If your build logs show missing components, install the needed Debian packages or switch to python:3.11-slim.

3. Verify your requirements.txt

Make sure the packages listed are available on PyPI and do not require additional system libraries. If a package needs native dependencies (Pillow, NumPy, SciPy, etc.), install the matching libraries via apt.

4. Add a specific Nginx rule for the apple-touch-icon

To make the icon available at the root path, add this block in your nginx.conf:

location = /apple-touch-icon.png {
    alias /app/apple-touch-icon.png;
}

Since the project files are copied to /app, this makes the icon accessible directly without going through Streamlit.

5. Redeploy

Push the updated Dockerfile and nginx config. Railway will rebuild the image and the app will run normally, with the icon now served correctly.


umitarsn
HOBBYOP

18 days ago

Hello thanks, it worked well!


bytekeim
PRO

18 days ago

You're welcome! I'm glad to hear it worked well for you.


Status changed to Solved brody 18 days ago


Loading...