Interface built the UI; launch() opens the doors — server, queue, auth, tunnel and MCP in a single call.
Every 'Running on local URL' line you have ever seen was printed by this one call — and it has 47 parameters you have never read.
demo.launch() is where your gr.Interface or gr.Blocks stops being a Python object and becomes a web service. On gradio 6.27.0 it starts a uvicorn server (default 127.0.0.1:7860, stepping to 7861, 7862… when the port is busy), wires the event queue, prints the * Running on local URL banner — and returns a (app, local_url, share_url) tuple. That tuple is the giveaway: launch() is not fire-and-forget, it hands you a live FastAPI app (gradio.routes.App) you can inspect, mount or test. In Gradio 6 the app-wide settings moved here too: theme, css, js and head belong to launch(), not the constructor. mcp_server, enable_monitoring, pwa and i18n are launch-time switches as well.
launch() is the only call in the library that touches the network, so every deployment-shaped decision lives on it. share=True tunnels your localhost to a public *.gradio.live URL — one parameter that let a GPU-locked lab machine serve a demo to a reviewer. auth=('admin','s3cret') puts a login page in front of the app. enable_monitoring=True exposes per-request analytics. mcp_server=True turns the same demo into a tool server an LLM can call. Learning launch() properly means never redeploying anything just to flip one of these switches.
import gradio as gr, json, urllib.request
def answer(question):
return "42, obviously"
demo = gr.Interface(
answer,
inputs=gr.Textbox(label="question", placeholder="Ask anything"),
outputs=gr.Textbox(label="answer"),
title="Deep Thought",
examples=[["What is the answer to everything?"]],
)
app, local_url, share_url = demo.launch(server_port=7861, prevent_thread_lock=True)
print("app class:", type(app).__module__ + "." + type(app).__name__)
print("local_url:", local_url, "| share_url:", share_url)
info = json.load(urllib.request.urlopen("http://127.0.0.1:7861/gradio_api/info"))
print("named endpoints:", list(info["named_endpoints"].keys()))* Running on local URL: http://127.0.0.1:7861 * To create a public link, set `share=True` in `launch()`. app class: gradio.routes.App local_url: http://127.0.0.1:7861/ | share_url: None named endpoints: ['/answer']
Verified on gradio 6.27.0 (Python 3.12). The unpacked return: app is a live FastAPI (gradio.routes.App), share_url is None without share=True. The browser shows the Deep Thought title, a Textbox 'question' (placeholder 'Ask anything'), Submit, a Textbox 'answer', the example row and Clear/Flag buttons. /gradio_api/info is the machine-readable API index — the endpoint is named after the function, /answer, not /predict.
import gradio as gr
def caption(img, conf):
return f"a {['dim','moody','bright'][min(int(conf*3),2)]} image"
demo = gr.Interface(
caption,
inputs=[gr.Image(label="image"), gr.Slider(0, 1, value=0.5, step=0.1, label="confidence")],
outputs=gr.Textbox(label="caption", lines=2),
)
app, local, public = demo.launch(prevent_thread_lock=True, share=True)
print("CAPTURED local:", local)
print("CAPTURED share:", public)
demo.close()* Running on local URL: http://127.0.0.1:7860 * Running on public URL: https://25f4dae8d27c9b8019.gradio.live This share link is temporary and will last for up to 1 week (best effort). For free permanent hosting and GPU upgrades, run `gradio deploy` from the terminal in the working directory to deploy to Hugging Face Spaces (https://huggingface.co/spaces) CAPTURED local: http://127.0.0.1:7860/ CAPTURED share: https://25f4dae8d27c9b8019.gradio.live Closing server running on port: 7860 Killing tunnel 127.0.0.1:7860 <> https://25f4dae8d27c9b8019.gradio.live
Ran for real on 6.27.0: share=True downloads an frpc binary, tunnels localhost to a random *.gradio.live subdomain and prints the one-week caveat. demo.close() tears the tunnel down. Rendered UI: Image input 'image' plus Slider 'confidence' on the left, Textbox 'caption' (2 lines) on the right. Your public label is the 16-hex chunk before .gradio.live — anyone holding the URL reaches your machine until the process dies.
import gradio as gr, urllib.request, urllib.parse, http.cookiejar
def answer(question):
return "42, obviously"
demo = gr.Interface(answer, gr.Textbox(label="question"), gr.Textbox(label="answer"))
demo.launch(server_port=7861, prevent_thread_lock=True, quiet=True, auth=("admin", "s3cret"))
cj = http.cookiejar.CookieJar()
op = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
try:
op.open("http://127.0.0.1:7861/gradio_api/info")
except Exception as e:
print("pre-login /gradio_api/info:", e)
r = op.open("http://127.0.0.1:7861/login",
data=urllib.parse.urlencode({"username": "admin", "password": "s3cret"}).encode())
print("POST /login:", r.status)
r2 = op.open("http://127.0.0.1:7861/gradio_api/info")
print("post-login /gradio_api/info:", r2.status)pre-login /gradio_api/info: HTTP Error 401: Unauthorized POST /login: 200 post-login /gradio_api/info: 200
Verified on 6.27.0. auth= fronts the app with a login page: API routes 401 until you POST the credentials to /login, which sets an HttpOnly session cookie. Basic-auth headers are rejected — script it by POSTing /login and reusing the cookie jar (curl -c/-b cookies.txt). A wrong password returns HTTP 400, not a retry loop. quiet=True silenced the banner; the UI is the same Textbox 'question' → Textbox 'answer' demo behind the wall.
import gradio as gr
def reverse(text):
return text[::-1]
with gr.Blocks() as demo:
t_in = gr.Textbox(label="prompt")
btn = gr.Button("Reverse")
t_out = gr.Textbox(label="reversed")
btn.click(reverse, t_in, t_out)
demo.launch(server_name="0.0.0.0", server_port=7861, prevent_thread_lock=True)* Running on local URL: http://0.0.0.0:7861 * To create a public link, set `share=True` in `launch()`.
Blocks app, verbatim run on 6.27.0: Textbox 'prompt', Button 'Reverse', Textbox 'reversed' in one column. server_name='0.0.0.0' binds every interface so colleagues on the LAN reach http://<your-ip>:7861 — the printed 0.0.0.0 is not the address you hand them. Omit server_port and launch() starts at 7860, stepping forward when occupied: in my probe a second demo auto-landed on 7861 while the first held 7860.
import gradio as gr
a = gr.Interface(lambda x: x, gr.Textbox(), gr.Textbox())
a.launch(server_port=7860, prevent_thread_lock=True, quiet=True)
b = gr.Interface(lambda t: t.upper(), gr.Textbox(label="text"), gr.Textbox(label="shout"))
app_b, url_b, _ = b.launch(prevent_thread_lock=True)
print("SECOND DEMO URL:", url_b)
c = gr.Interface(lambda t: t[::-1], gr.Textbox(label="text"), gr.Textbox(label="reversed"))
print("BEGIN-PRINT")
print(c.launch(prevent_thread_lock=True, quiet=True))
print("END-PRINT")
c.close(); a.close(); b.close()* Running on local URL: http://127.0.0.1:7861 * To create a public link, set `share=True` in `launch()`. SECOND DEMO URL: http://127.0.0.1:7861/ BEGIN-PRINT END-PRINT Closing server running on port: 7862 Closing server running on port: 7860 Closing server running on port: 7861
Three launches, one output, verbatim on 6.27.0. a quietly holds 7860; b (no server_port) auto-steps to 7861 and unpacked url_b confirms it; c then lands on 7862 — visible in the closing order (c=7862, a=7860, b=7861). And print(c.launch(...)) emits an empty line: the return tuple's class, TupleNoPrint, prints nothing by design to keep notebooks clean. Two demos ran side by side on different ports with zero configuration.
| Flag | Meaning |
|---|---|
share=True | Tunnel localhost to a temporary public *.gradio.live URL — 'up to 1 week (best effort)' per launch's own banner. Needs outbound internet. |
server_name / server_port | Bind address and port. Default 127.0.0.1:7860; '0.0.0.0' exposes the app to the LAN; the port auto-steps forward when occupied. |
prevent_thread_lock=True | Return control to your script instead of blocking until shutdown — the test/REPL switch. The server dies with the process unless something keeps it alive. |
auth / auth_message | Tuple or dict of username→password puts a login page in front of the app; auth_message customizes the login text. Cookie sessions, not Basic auth. |
mcp_server=True | Expose the app's event listeners as MCP tools (since 5.28.0) — your demo becomes callable by LLM agents. |
enable_monitoring=True | Prints a /monitoring/<key> URL with per-request analytics for the running app. |
quiet=True | Suppress the startup banner — pairs with prevent_thread_lock=True in scripts that parse the return tuple. |
launch() existed from the first PyPI release (gradio 0.1.0, uploaded 2019-02-19) — for its early years it just started a server and printed a URL. The share feature as we know it arrived with 3.13.1 (on PyPI 2022-12-15), whose release notes describe replacing ssh port-forwarding tunnels with frp — the birth of the *.gradio.live link.
gradio 3.46.0 shipped PR #5767 — 'Set share=True for all Gradio apps in Colab by default'. From then on every notebook demo carried a public link, a big reason 'Running on public URL: https://….gradio.live' became one of the most-screenshot console lines in machine learning.
Gradio 6.0 (2025-11-21) moved app-wide settings (theme, css, js, head) from the Blocks/Interface constructor into launch(), while earlier 5.x releases had already piled server features onto it: mcp_server since 5.28.0, then enable_monitoring, pwa and i18n. On 6.27.0 the signature counts 47 public parameters.
launch() constructs the FastAPI application (type gradio.routes.App — my unpacked return says so) and mounts /config (the JSON blueprint the Svelte frontend renders from), /login and /logout when auth is set, /gradio_api/info (the machine-readable API index), /monitoring when enabled, plus static asset routes — 20 routes total on a fresh 6.27.0 Interface, counted from demo.app.routes. Event calls flow through the queue over SSE; uvicorn serves it all. prevent_thread_lock=True just runs uvicorn in a background thread and hands back the tuple instead of blocking on it.