How to Run a Gradio App in a Cloud Sandbox and Call Its API
Updated October 27, 2026 .md
Short answer: Install Gradio in a venv, launch with server_name="0.0.0.0", run it as a systemd service and host port 7860 on a private URL. To call it from gradio_client, trade the URL's _token for the proxy's cookie and pass it in headers. Our calls took about 0.6 seconds each.
What we ran#
Everything below ran on production on October 27, 2026, on a small sandbox (2 vCPU, 4 GB), with Gradio 6.30.0 on the sandbox's Python 3.12 and gradio_client 2.7.2 on a laptop. Timings are single runs.
| Step | Result |
|---|---|
pip install gradio into a fresh venv |
33 s, 347 MB |
systemctl restart gradio until port 7860 answers |
2.4 to 2.9 s |
| Gradio process memory (RSS) | 139 MiB |
| Request without the URL token | 401 from the proxy |
client.predict(...) from the laptop, with a file output |
588 to 612 ms per call |
REST call with curl (POST, then read the result stream) |
0.4 s |
sbx stop --wait, then sbx resume |
2.2 s stop, 1.3 s resume, memoryRestored: true |
| Reboot until the hosted URL answers again | 5.7 s |
A small sandbox is plenty for a CPU-only Gradio demo like this one. If your app loads a model, size for the model, not for Gradio.
1. Install Gradio and write the app#
Gradio's quickstart installs it with pip. Python 3.12 is preinstalled, so make a venv and install into it. The install takes longer than sbx exec's 30-second default, so raise the timeout:
sbx new --name gradio --type small --no-env
id=sb_r50cwqkv4c # the id that sbx new printed
sbx exec $id --timeout 600 "mkdir -p ~/app && cd ~/app && python3 -m venv .venv && .venv/bin/pip install -q gradio"The test app counts words and returns two outputs: a JSON object and a CSV file. The file output matters below, because files are where proxies usually break.
# app.py
import collections, csv, re, tempfile
import gradio as gr
def word_counts(text: str, top_n: float):
words = re.findall(r"[a-z']+", text.lower())
top = collections.Counter(words).most_common(int(top_n))
path = tempfile.NamedTemporaryFile(suffix=".csv", delete=False).name
with open(path, "w", newline="") as f:
csv.writer(f).writerows([("word", "count"), *top])
return dict(top), path
demo = gr.Interface(
word_counts,
[gr.Textbox(label="Text", lines=6), gr.Slider(1, 20, value=5, step=1, label="Top N")],
[gr.JSON(label="Counts"), gr.File(label="CSV")],
)
demo.launch(server_name="0.0.0.0", server_port=7860)sbx scp app.py $id:app/app.pyserver_name="0.0.0.0" is the line people miss. Gradio listens on 127.0.0.1 by default, and the hosting proxy then answers 502 ("Nothing is answering on port 7860"). Either bind to 0.0.0.0 as above (or set GRADIO_SERVER_NAME=0.0.0.0), or host the port with --localhost. Don't pass share=True: you don't need Gradio's share link when the sandbox already has an HTTPS URL.
2. Run it as a systemd service#
A process started from sbx exec is gone after a reboot or a resume that boots from disk. A systemd unit comes back on its own:
sbx exec $id "sudo tee /etc/systemd/system/gradio.service >/dev/null <<'EOF'
[Unit]
Description=Gradio app
After=network-online.target
[Service]
User=user
WorkingDirectory=/home/user/app
ExecStart=/home/user/app/.venv/bin/python app.py
Environment=GRADIO_ANALYTICS_ENABLED=False
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable --now gradio"Plain sbx exec doesn't forward your local stdin; sbx exec -i does. We copied the file with sbx scp and wrote the unit with a heredoc inside the remote command. After systemctl restart gradio, port 7860 answered in 2.4 to 2.9 seconds; most of that is importing Gradio.
3. Host port 7860 on a private URL#
sbx host $id 7860 --title "Word counts"https://global-vital-parrot-7860.box.sandboxer.com?_token=<token>Open that link in a browser and the Gradio UI loads. The proxy sets a 30-day __sbx_auth cookie and redirects to /, and the cookie carries the page's later requests, including the result stream. Without the token, nothing reaches Gradio, not even its API:
B=https://global-vital-parrot-7860.box.sandboxer.com
curl -s -o /dev/null -w "%{http_code}\n" $B/
curl -s -o /dev/null -w "%{http_code}\n" $B/gradio_api/info
curl -si -c jar "$B?_token=<token>" | grep -iE '^HTTP|^location'401
401
HTTP/2 302
location: /The token, the cookie and switching between public and private are covered in Give a sandbox port an HTTPS URL. This guide covers what's specific to Gradio: calling the app from code.
4. Call the app from gradio_client#
gradio_client is Gradio's Python client (pip install gradio_client). Give it the private URL as is and it fails in both forms:
base URL, no cookie -> AuthenticationError Could not load https://global-vital-parrot-7860.box.sandboxer.com/ as credentials were not provided. Please login.
URL with ?_token= -> AuthenticationError Could not load https://global-vital-parrot-7860.box.sandboxer.com?_token=<token> as credentials were not provided. Please login.The client builds its own paths from the base URL and drops the query string, so the token never reaches the proxy. Trade the token for the cookie first and pass it with headers=:
# client.py: reads the private hosted URL, with its ?_token=, from ./url
import time, httpx
from gradio_client import Client
private_url = open("url").read().strip()
# Trade the ?_token= for the proxy's __sbx_auth cookie.
r = httpx.get(private_url, follow_redirects=False)
cookie = r.headers["set-cookie"].split(";")[0]
base = private_url.split("?")[0]
client = Client(base, headers={"Cookie": cookie}, verbose=False,
analytics_enabled=False, download_files="downloads")
print(client.view_api(print_info=False, return_format="dict")["named_endpoints"].keys())
text = "the cat sat on the mat and the dog sat too"
for i in range(5):
t = time.perf_counter()
counts, csv_path = client.predict(text, 3, api_name="/word_counts")
print(f"{(time.perf_counter() - t) * 1000:.0f} ms", counts)
print(open(csv_path).read())dict_keys(['/word_counts'])
612 ms {'the': 3, 'sat': 2, 'cat': 1}
588 ms {'the': 3, 'sat': 2, 'cat': 1}
595 ms {'the': 3, 'sat': 2, 'cat': 1}
593 ms {'the': 3, 'sat': 2, 'cat': 1}
593 ms {'the': 3, 'sat': 2, 'cat': 1}
word,count
the,3
sat,2
cat,1Three things to notice:
- The endpoint name is the function name. Gradio named it
/word_countswithout anyapi_namein the code.view_api()lists the names if you aren't sure. - File outputs come back through the same URL.
predictdownloaded the CSV through the proxy with the same cookie. Each call includes that download, which is part of the roughly 0.6 seconds. - The cookie is a credential. It works for 30 days, like the token. Keep both out of logs and source code.
Don't swap in Gradio's own auth= login for this. It adds a password to the app, but the proxy's token is what keeps requests from reaching it at all. If you make the port --public instead, anyone with the URL can call your API, and Client(base) then works with no headers.
5. Call it with curl#
Gradio also has a plain HTTP API: POST the inputs, get an event_id, then read the result as a server-sent event stream. Through a private URL, send the cookie jar from step 3 with both requests:
EID=$(curl -s -b jar -H 'Content-Type: application/json' -X POST $B/gradio_api/call/word_counts \
-d '{"data": ["the cat sat on the mat and the dog sat too", 3]}' \
| python3 -c 'import json, sys; print(json.load(sys.stdin)["event_id"])')
curl -s -b jar -N $B/gradio_api/call/word_counts/$EIDevent: complete
data: [{"the": 3, "sat": 2, "cat": 1}, {"path": "/tmp/gradio/4427…/tmpp424nhik.csv", "url": "https://global-vital-parrot-7860.box.sandboxer.com/gradio_api/file=/tmp/gradio/4427…/tmpp424nhik.csv", "size": 33, "orig_name": "tmpp424nhik.csv", "mime_type": null, "is_stream": false, "meta": {"_type": "gradio.FileData"}}]Both requests together took 0.4 seconds. The file's url already uses the public box.sandboxer.com hostname, so we didn't need to set root_path or any proxy option in Gradio. Fetch it with the same cookie jar. The same POST without the cookie got 401.
This form suits agents and shell scripts that can't install gradio_client.
6. What survives a stop and resume#
sbx stop $id --wait
sbx resume $id
sbx exec $id "systemctl show gradio -p MainPID -p ActiveEnterTimestamp"MainPID=1920
ActiveEnterTimestamp=Tue 2026-10-27 10:03:10 UTCThe stop took 2.2 seconds and the resume 1.3 seconds, with memoryRestored: true. The service's start time was still from before the stop and the VM had not rebooted, so Gradio never restarted. client.py, run again with the same private URL, returned the same counts. Routes and tokens survive a stop, so links you already shared keep working.
A resume boots from disk instead when no machine with the same CPU type is free (see snapshots). To check that path, we rebooted the sandbox: the enabled unit started Gradio, and the hosted URL answered 200 5.7 seconds after the reboot command. The venv and app.py are on the sandbox disk, so nothing needs reinstalling.
An idle sandbox pauses itself after idleStopMinutes (30 on our test sandbox). Requests to the hosted URL count as activity. See Idle auto-pause and wake on use.
7. Clean up#
sbx delete $id --yesA small sandbox costs $0.018/hour while it runs and nothing while it is stopped.
FAQ#
Why does my Gradio URL show "Nothing is answering on port 7860"?#
Gradio binds to 127.0.0.1 unless you tell it otherwise. Launch with server_name="0.0.0.0" or GRADIO_SERVER_NAME=0.0.0.0, or host the port with sbx host $id 7860 --localhost.
Why does gradio_client say "credentials were not provided"?#
The URL is private and the client sent neither the token nor the cookie. It drops the ?_token= query, so fetch the URL once without following redirects, take the __sbx_auth cookie from Set-Cookie, and pass it as Client(base, headers={"Cookie": cookie}).
Do I need share=True?#
No. share=True creates a separate share link through Gradio's servers. The sandbox's hosted URL already gives you HTTPS, and it is private by default.
Do file inputs and outputs work through the hosted URL?#
File outputs did in our test: Gradio returned a box.sandboxer.com file URL, and gradio_client downloaded the CSV through the proxy. We didn't test file uploads.
Related#
More guides
For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.
For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.
The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.
If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.