Short answer: A Streamlit chat app is about 40 lines of Python. To put it on a VPS, run it in Docker, publish its port on 127.0.0.1 only, let Caddy handle HTTPS, keep the API key in a server-side environment variable, and add a spending guard before you share the URL.
Disclosure: the Hostinger and AICredits links are affiliate links. I ran the app locally on 23 Oct 2026 with Streamlit and the openai package installed in a clean virtual environment: it started and /_stcore/health returned ok. I did not make a model call (no key in my environment), and the Docker and Caddy parts are unrun.
Streamlit is the fastest way to give a script a web interface, which makes it popular for AI demos. The catch is that a demo URL on the open internet is also a way for strangers to spend your money.
What does the app look like?
import os
import streamlit as st
from openai import OpenAI
MODEL = os.environ.get("MODEL", "openai/gpt-4o-mini")
MAX_TURNS = 20 # cap context growth and spend per session
st.set_page_config(page_title="Ask the docs", page_icon=None)
st.title("Ask the docs")
api_key = os.environ.get("AICREDITS_API_KEY")
if not api_key:
st.error("AICREDITS_API_KEY is not set on the server.")
st.stop()
client = OpenAI(base_url="https://api.aicredits.in/v1", api_key=api_key)
if "messages" not in st.session_state:
st.session_state.messages = []
for m in st.session_state.messages:
with st.chat_message(m["role"]):
st.markdown(m["content"])
if prompt := st.chat_input("Ask a question"):
if len(st.session_state.messages) >= MAX_TURNS * 2:
st.warning("Session limit reached. Refresh the page to start over.")
st.stop()
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
with st.chat_message("assistant"):
stream = client.chat.completions.create(
model=MODEL,
messages=st.session_state.messages,
stream=True,
)
reply = st.write_stream(
chunk.choices[0].delta.content or "" for chunk in stream if chunk.choices
)
st.session_state.messages.append({"role": "assistant", "content": reply})
Save it as app.py. Notes on the choices:
- The base URL is an OpenAI-compatible endpoint, here AICredits so you can pay in rupees by UPI. Swap in any provider's base URL and key. The model ID format (
openai/gpt-4o-mini) is from the AICredits setup guide. - The key comes from
os.environ. It never reaches the browser: visitors see only what the script renders. MAX_TURNSbounds how long a conversation can grow. Every turn resends the whole history, so cost climbs faster than the turn count. See the token budgeting post.- A refresh starts a new session and resets the counter, so this is a guard against runaway chats, not against a determined abuser. That is what the next two sections are for.
I confirmed locally that it starts. The chat call itself is unrun, so test it with a small budget first.
How do you containerise it?
# Dockerfile
FROM python:3.13-slim
WORKDIR /app
RUN pip install --no-cache-dir streamlit openai
COPY app.py .
EXPOSE 8501
CMD ["streamlit", "run", "app.py", "--server.headless=true", "--server.address=0.0.0.0", "--server.port=8501"]
# docker-compose.yml
services:
chat:
build: .
restart: unless-stopped
env_file: .env
ports:
- "127.0.0.1:8501:8501"
Pin your package versions in a requirements.txt for real use. Streamlit's own default port is 8501, and the health endpoint is the one I hit locally:
curl -s http://127.0.0.1:8501/_stcore/health
ok
Run docker compose up -d --build on the server. Binding to 127.0.0.1 keeps the app off the public interface, so the only way in is the proxy.
How do you add HTTPS?
Install Caddy, point a DNS record at the server, and use this Caddyfile:
chat.example.com {
reverse_proxy localhost:8501
}
Caddy gets a certificate on its own as long as DNS points at the machine and ports 80 and 443 are open. Streamlit's interface talks over WebSockets, and I haven't verified how every proxy handles that: if the page loads but sits on a spinner, the proxy isn't passing the connection through, so check there first. The proxy comparison lists other options.
How do you keep strangers from spending your money?
- Put a cap on the key. AICredits supports per-key budgets, so create a key just for this app with a small rupee limit. A leaked or abused key then stops at that number.
- Gate the URL. Easiest is HTTP basic auth at Caddy using a hashed password (
caddy hash-passwordgenerates one), so the page asks before it loads. Add it inside the site block with thebasic_authdirective. Check Caddy's docs for the exact syntax for your version. - Set an alert. Check your usage page after the first day. The cost-per-feature script logs spend per call.
- Don't put secrets in the repo.
.envstays on the server and in.gitignore.
Server basics (key-only SSH, firewall, updates) are in the hardening checklist. For a production-shaped API with an existing front end, use the FastAPI deployment guide instead of Streamlit.
Which server and card do you need?
A chat app calling a hosted model barely uses the machine. One core and 4 GB runs it, with KVM 2 giving room for a database and a second app. See the plan sizing guide to check against your own docker stats. Hostinger KVM 2 is what I'd start on if you plan to add anything else.
For the model bill, pay in INR through AICredits or use a zero-forex card, as compared in the credit card guide.



