Python apps
Deploy a Streamlit, Gradio or FastAPI app only your team can open
Updated
Run forculus deploy in the app's folder. It detects Streamlit, Gradio, FastAPI, Flask or Django, builds a container and puts it behind sign-in, so only the people you share it with can open it, with no login code in your app. Container apps need the Team plan or above.
What you need
- The forculus CLI, signed in with
forculus login. - Docker running on your computer. The image is built there, for linux/amd64.
- A workspace on Team, which runs one container app, or Business, which runs four. See pricing.
Deploy it
In the app's folder:
forculus deploy acme/report . --create
The first lines say what it found and how the app will start. Shortened, it looks like this:
a container (Streamlit)
because it has requirements.txt, runs as a python container
start: streamlit run app.py --server.port $PORT --server.address 0.0.0.0 --server.headless true
python: 3.13
Building . for linux/amd64 ...
Image uploaded. Waiting for it to go live ...
It listens on port 8080 and runs at size basic.
Version 1 (7c3a91e0d5f2) is live.
https://report--acme.forculuslabs-apps.tech
With no Dockerfile of yours, the deploy writes one: Python 3.13 unless .python-version or runtime.txt asks for 3.11 or 3.12, installed with uv, Poetry or pip from your lock file, requirements.txt or pyproject.toml. forculus dockerfile prints it. The start command comes from what the project depends on:
| Depends on | Starts with |
|---|---|
streamlit | streamlit run app.py, or main.py or streamlit_app.py |
gradio | python app.py |
fastapi | uvicorn main:app |
django | gunicorn <project>.wsgi:application |
flask | gunicorn app:app |
Set your own in forculus.json as container.start. See Containers and How deploy decides what to build. To start from nothing, forculus init fastapi writes a FastAPI app that already checks who is calling.
Share it
forculus share acme/report sam@example.com
forculus share acme/report @example.com
The first lets one person in, and the second everyone at your company's domain. People sign in with a code sent to their address, or with Google or Microsoft, and don't need an account first. Remove someone and they lose access within 60 seconds, open connections included.
Know who's using it
Every request reaches your app with a signed token saying who is calling. The forculus package checks it, with an extra for each framework:
pip install "forculus[streamlit]" # or [gradio], [fastapi], [flask], [django]
Streamlit
import streamlit as st
from forculus.streamlit import get_user
user = get_user()
if user is None:
st.error("sign in to use this")
st.stop()
st.write(f"hello {user.email}")
The caller is checked once per session. On Forculus the edge closes the connection within a minute of someone's access being removed, and the session ends with it.
Gradio
import os
import gradio as gr
from forculus.gradio import require_user
def answer(question, request: gr.Request):
user = require_user(request)
return f"{user.email} asked: {question}"
gr.Interface(answer, "textbox", "textbox").launch(
server_name="0.0.0.0", server_port=int(os.environ.get("PORT", "7860"))
)
The app has to listen on 0.0.0.0 at the port in PORT. Gradio's defaults can't be reached here.
FastAPI
from forculus.fastapi import CurrentUser
@app.get("/")
async def index(user: CurrentUser):
return {"hello": user.email}
A request without a valid caller gets a 401 and never reaches your handler. requires("editor") asks for a role too. Flask and Django have the same in forculus.flask and forculus.django. See Verify the caller in a Python app.
forculus dev runs any of these on your computer behind a copy of our sign-in, with real tokens, so you can try each role before you deploy.
Why not a share link or a password?
Gradio's share=True link is public: anyone who has it can use the app, and it lasts a week (Gradio's guide). A shared password gets passed around, and tells the app nothing about who is using it. Here each person signs in as themselves, you can take one person's access away without changing anything for the rest, and every visit is in the audit log with their name.
Streamlit Community Cloud allows one private app at a time, with viewers invited by email who sign in with Google or a single-use emailed link (Streamlit's docs). If that covers you, it's a fine choice. Forculus is for more than one private app, sharing with a whole domain, and an audit log.
Questions
Is there a free option?
Not for Python servers. Free runs JavaScript, TypeScript and static apps only, and a container deploy on Free stops before it builds anything, with a message that says so.
What does the first request get while the container starts?
A 503 that says the app is starting. A browser gets a page with the same message, which reloads itself until the app answers. After an app's first deploy this can take several minutes. A container stops after 10 minutes with no requests and starts again on the next one.
Where does my data go between restarts?
Not on the container's disk, which goes back to what the image holds each time the container starts. Keep it in the app's database or files, or a Postgres database you connect.
How do I keep an API key out of the code?
Store it as a secret for the API's host with forculus secret set. Our outbound proxy adds it to the app's requests to that host, so the code calls the API without it. See Secrets.
Sources
- Sharing your app, Gradio's guide. Checked 16 October 2026.
- Share your app, Streamlit's docs. Checked 16 October 2026.
Next
- Containers, in the docs
- The Python SDK
- Pricing