AI AI-16.06
Build and share your first Space
Turn a model into a web app anyone can use: write a tiny Gradio app, test it locally, push it to a Hugging Face Space, add ZeroGPU and secrets, keep learning.
The big idea
A model on the Hub is a recipe. Useful, but only to people who can cook: they need Python, the right libraries, and enough memory. A SpaceAn app hosted on the Hugging Face Hub. Its code lives in a Git repository; every push rebuilds and restarts it, and anyone with the link can use it in a browser. is a food truck. You hand over the recipe and the shopping list, Hugging Face runs the kitchen, and anyone with the link walks up and tastes it in their browser.
Under the hood a Space is just another Git repository, like a model or a dataset, with one difference: it runs. Push app.py and requirements.txt, and the Hub installs the requirements, starts your app and gives it a public address. Push again, and it rebuilds.
The quickest way to write that app is GradioAn open-source Python library that turns a function into a web interface with inputs, outputs and an API, in a few lines.: you write an ordinary Python function, say what goes in and what comes out, and Gradio draws the page and exposes an API for free. In this lesson you build a 23-line "mood meter", run it on your machine, call it from code, and see exactly what it takes to put it online.
How it works
- Your folder app.py with the Gradio app, requirements.txt with the libraries, README.md with sdk: gradio in its YAML.
- Push git push or hf upload sends the files to the Space repository, like any other repo on the Hub.
- Build and run Each commit rebuilds the Space: requirements are installed and app.py is started on its hardware.
- Visitors Anyone with the link uses the web page; code can call the same function through the API.
- Secrets Tokens and keys are set in the Space settings and read with os.getenv. They never appear in the repo.
Step 1: three kinds of Space
When you create a Space you pick its SDKSoftware development kit. For a Space it decides how the app runs: gradio (a Python app), docker (any container) or static (plain HTML, CSS and JavaScript).:
- Gradio: a Python app. The Hub installs your requirements and runs
app.py. This lesson uses it. - Docker: any app in a container, in any language. You write the Dockerfile; the app listens on port 7860 by default.
- Static: HTML, CSS and JavaScript only, no server code. Good for a page that runs a model in the browser with Transformers.js.
Who can host what is the first thing to check, because it changed recently:
Step 2: write the app
Two files. First app.py:
import gradio as gr
from transformers import pipeline
# Load the model once, when the app starts, not on every request.
classifier = pipeline(
"sentiment-analysis",
model="distilbert/distilbert-base-uncased-finetuned-sst-2-english",
)
def mood(text):
result = classifier(text)[0] # {'label': 'POSITIVE', 'score': 0.99}
return {result["label"]: result["score"]}
demo = gr.Interface(
fn=mood,
inputs=gr.Textbox(label="Write a sentence"),
outputs=gr.Label(label="Mood"),
title="Mood meter",
examples=["I love this little app.", "The bus was late again."],
)
if __name__ == "__main__":
demo.launch()
Then requirements.txt, the shopping list the Space installs before it starts:
transformers
torch
Three things to notice. The model loads once at the top, so each visitor waits for one prediction, not for a model load. mood is a plain function: Gradio does not care what is inside. And gr.Interface maps it to a page: a textbox in, a label with a confidence bar out, plus two example sentences a visitor can click. Gradio itself is not in requirements.txt, because a Gradio Space installs the Gradio version named in its README.
Step 3: run it on your machine
pip install gradio transformers torch
python app.py
We ran it in Docker, with GRADIO_SERVER_NAME=0.0.0.0 so the browser outside the container could reach it, hence the 0.0.0.0 in the output:
* Running on local URL: http://0.0.0.0:7860
* To create a public link, set `share=True` in `launch()`.
Open the address in a browser (on your own machine, http://127.0.0.1:7860) and you have the app: type a sentence, press Submit, watch the label. The share=True hint gives a public link to the app while it runs on your machine; we did not use it here. A Space keeps the app online without your laptop.
Step 4: call it from code
Every Gradio app is also an API. gradio_client calls it like a function. Our first attempt used the endpoint name from older tutorials and failed:
ValueError: Cannot find a function with `api_name`: /predict.
So ask the app what it offers. view_api() lists the endpoints:
from gradio_client import Client
client = Client("http://127.0.0.1:7860/")
client.view_api()
Named API endpoints: 1
- predict(text, api_name="/mood") -> mood
Parameters:
- [Textbox] text: str (required)
In this Gradio version the endpoint is named after the function: /mood. With that name:
print(client.predict("Building my first Space was easier than I expected.",
api_name="/mood"))
{'label': 'POSITIVE', 'confidences': [{'label': 'POSITIVE', 'confidence': 0.9754320383071899}]}
No Python on the other side? Plain HTTP works too, in two steps: post the input and get an event id, then read the result stream. (Our container mapped the app to another port on the host; the commands use the app's own port.)
$ curl -X POST http://127.0.0.1:7860/gradio_api/call/mood \
-H "Content-Type: application/json" -d '{"data": ["The bus was late again."]}'
{"event_id":"c1526e88d40b4190a72255a2582467bb"}
$ curl -N http://127.0.0.1:7860/gradio_api/call/mood/<event_id>
event: heartbeat
data: null
event: complete
data: [{"label": "NEGATIVE", "confidences": [{"label": "NEGATIVE", "confidence": 0.9972857236862183}]}]
Once the app is a Space, you point the same client and the same calls at its public address instead of 127.0.0.1 (not tested here, since we had no Space).
Step 5: create the Space and push
These steps need an account, so they were not run here. They are the documented ones. On the website, open huggingface.co/new-space, choose a name, a license and the visibility, and pick Gradio as the SDK. Or from a terminal, logged in with a write token ( Accounts, tokens, downloads and datasets ):
hf repos create my-gradio-space --repo-type space --sdk gradio
hf upload <your-username>/my-gradio-space . . --repo-type=space
hf upload sends the current folder to the root of the Space. If you prefer Git, clone the Space and push like any repo; when Git asks for a password, give it your token:
git clone https://huggingface.co/spaces/<your-username>/my-gradio-space
cd my-gradio-space
# copy app.py and requirements.txt in, then:
git add .
git commit -m "First version"
git push
The Space's settings live in a YAML block at the top of its README.md. Creating the Space as Gradio writes sdk: gradio for you; this is what ours would say, with the Gradio version we tested and the model linked so it shows on the Space page:
---
title: Mood meter
sdk: gradio
sdk_version: 6.30.0
app_file: app.py
models:
- distilbert/distilbert-base-uncased-finetuned-sst-2-english
---
Each push triggers a rebuild and restart. On free hardware, a Space that nobody uses goes to sleep after a while; you can also pause it from Settings.
Step 6: hardware and ZeroGPU
By default a Space gets 2 CPU cores and 16 GB of RAM, plenty for our small model. Bigger models need a GPU, rented by the hour, or ZeroGPUShared GPUs on Hugging Face Spaces that are attached to a Gradio Space only while a function decorated with @spaces.GPU runs, then released. Free to use within daily quotas.: a pool of shared GPUs that your Space borrows only while a request is being served.
ZeroGPU works only with the Gradio SDK. To use it, select ZeroGPU hardware in the Space settings, add spaces to the imports and decorate the function that needs a GPU:
import gradio as gr
import spaces
@spaces.GPU # borrow a GPU only while this function runs
def mood(text):
...
The decorator requests a GPU when the function is called and releases it when it returns, with a default limit of 60 seconds per call (@spaces.GPU(duration=120) raises it, and shorter durations get better queue priority). The docs say to move the model to cuda at the top of the file, not inside the function, because outside decorated functions ZeroGPU emulates CUDA and real transfers are fastest at startup.
The decorator is designed to do nothing anywhere else, which we checked: with spaces installed and @spaces.GPU on mood, our CPU-only container gave the identical answer, 0.9754320383071899. So one app.py runs on your laptop and on ZeroGPU.
Step 7: secrets, not code
Your app may need a token, for example to load a gated model or call a paid API. Do not write it in app.py: a public Space's code is visible to everyone, and Spaces scan for hard-coded secrets and warn their owners. Instead, open the Space's Settings and add it there:
- Secrets hold tokens, API keys and passwords. Their value cannot be read back from the settings page, and they are not copied when someone duplicates your Space.
- Variables hold non-sensitive settings, such as a model name. They are public and are copied into duplicates.
In a Gradio Space both arrive as environment variables:
import os
token = os.getenv("HF_TOKEN") # set as a Space secret, never in the code
A secret named HF_TOKEN is also picked up automatically by the Hugging Face libraries. It is the same habit as keeping provider keys in one gateway in a production app ( Anatomy of an LLM application ): the code says where to find a key, never what it is.
Try it
A pre-push check for a Space folder. It builds a broken folder in a temporary directory, lists the problems, fixes them and checks again. The token in the first version is fake, on purpose.
# A pre-push check for a Gradio Space folder. Builds a sample folder in a temp dir,
# finds the problems, fixes them and checks again. Standard library only, no network.
import pathlib, re, tempfile
def front_matter(text):
m = re.match(r"---\n(.*?)\n---", text, re.S)
return dict(l.split(":", 1) for l in m.group(1).splitlines()) if m else {}
def check(folder):
problems = []
readme = folder / "README.md"
meta = {k.strip(): v.strip() for k, v in front_matter(readme.read_text()).items()} if readme.exists() else {}
if meta.get("sdk") not in {"gradio", "docker", "static"}:
problems.append("README.md YAML needs sdk: gradio, docker or static")
app = folder / meta.get("app_file", "app.py")
if not app.exists():
problems.append(f"app_file {app.name} is missing")
elif re.search(r"hf_[A-Za-z0-9]{8,}", app.read_text()):
problems.append(f"{app.name} contains something that looks like a token: use a Space secret")
if not (folder / "requirements.txt").exists():
problems.append("requirements.txt is missing, so transformers and torch will not be installed")
return problems or ["ready to push"]
with tempfile.TemporaryDirectory() as d:
space = pathlib.Path(d)
(space / "README.md").write_text("---\ntitle: Mood meter\napp_file: app.py\n---\n")
(space / "app.py").write_text('TOKEN = "hf_FAKEexample123456"\n') # a fake token, on purpose
print("first check:")
for p in check(space):
print(" -", p)
(space / "README.md").write_text("---\ntitle: Mood meter\nsdk: gradio\napp_file: app.py\n---\n")
(space / "app.py").write_text('import os\nTOKEN = os.getenv("HF_TOKEN")\n')
(space / "requirements.txt").write_text("transformers\ntorch\n")
print("after the fixes:")
for p in check(space):
print(" -", p)Output
first check:
- README.md YAML needs sdk: gradio, docker or static
- app.py contains something that looks like a token: use a Space secret
- requirements.txt is missing, so transformers and torch will not be installed
after the fixes:
- ready to push - Which of the three problems would stop the Space from starting, and which would start it but leak something?
- Add a check that
requirements.txtmentionstorchwhenapp.pyimportstransformers. Why would a Space that builds fine still crash on the first request without it? - The token check is a simple pattern. What would it miss? (Think of a token split across two strings, or read from a file committed to the repo.)
Going deeper
Visibility
A Space can be public (code and app open to everyone, and anyone can duplicate it), private (only you and collaborators; others get a 404), or protected on paid plans (code private, but the running app reachable at its .hf.space address). Start private while you test, then switch to public in Settings.
Duplicating and embedding
The three-dots menu on any Space has Duplicate this Space: it copies the repo to your account, carries over public variables and asks you to set the secrets. It is the fastest way to start from someone else's working app. Duplicates are private by default and follow the same plan rules as creating a Space. A Gradio Space can also be embedded in another web page with an iframe or Gradio's web component.
Limits worth knowing
Disk on a Space is not persistent, so anything the app writes can be lost on restart: keep what matters somewhere else, such as a dataset repo. Outgoing requests are allowed only on ports 80, 443 and 8080. A paused Space is not billed. And because every push rebuilds, test locally first (Steps 3 and 4): a local run takes seconds, a failed build on the Hub takes minutes to discover.
Where to keep learning
Hugging Face publishes free courses at huggingface.co/learn. Good next steps after this module:
- LLM Course: large language models with the Hugging Face libraries, from pipelines to fine-tuning.
- Agents Course: build and deploy your own AI agents.
- a smol course: post-training small models.
- Audio Course, Community Computer Vision Course and Diffusion Course: transformers for sound, vision models, and image generation with diffusers.
- Deep RL Course: deep reinforcement learning.
- Open-Source AI Cookbook: notebooks for specific tasks.
- Also listed: the Robotics Course (LeRobot), ML for Games, ML for 3D and the Context Course on context engineering for code agents.
Trade-offs
| Option | Wins when | Loses when |
|---|---|---|
| Gradio Space | A Python model demo in minutes, with a web page and an API for free | Needs PRO unless it runs on ZeroGPU; Python only |
| Docker Space | Any language or server, full control of the environment | Needs a paid plan; you write and debug the Dockerfile |
| Static Space | Free for everyone; models that run in the browser | No server code; the model runs on the visitor's device |
| ZeroGPU | Big models on a strong GPU with no hourly bill | Gradio only, daily quotas, queues when busy, a time limit per call |
| Rented GPU hardware | No queue, no quota, a dedicated card | Billed every hour it runs, used or not |
Common mistakes
- Loading the model inside the request function, so every visitor waits for a full model load.
- Writing a token in
app.pyor committing a.envfile to the Space, instead of using Space secrets. - Forgetting a library in
requirements.txt: it works locally because you installed it, and the Space crashes on start. - Guessing the API name. Call
view_api()and use the name it lists; in current Gradio it is the function's name. - Debugging by pushing to the Space over and over, when
python app.pyandgradio_clientfind the same bug in seconds.
In the wild
Hugging Face
Spaces overview (opens in a new tab)The official overview explains that Spaces store code in a Git repository like models and datasets, with Gradio, Docker and static HTML as the three SDKs, and that every pushed commit rebuilds and restarts the Space. It states the plan rules this lesson uses (static free, Gradio and Docker on a paid plan, up to 2 ZeroGPU Spaces for free accounts in good standing), lists the hardware and hourly prices, and recommends secrets for tokens and keys, exposed to the app as environment variables, instead of hard-coding them.
Check yourself
-
You push a fix to app.py in your Space. What happens?
Show answer
B. The Space rebuilds and restarts with the new code A Space is a Git repo that runs. Every pushed commit triggers a rebuild and restart, which is why testing locally before pushing saves time.
-
A free account, older than 30 days with a verified email, wants to host a Gradio demo at no cost. What works?
Show answer
B. A Gradio Space on ZeroGPU, up to 2 of them Gradio and Docker Spaces need a paid plan, except that free accounts in good standing can host up to 2 Gradio Spaces on ZeroGPU. Static Spaces are free too, but run no Python.
-
Your app needs an API key for a paid service. Where does it go?
Show answer
C. A Space secret, read with os.getenv Secrets are private, cannot be read back from the settings page and are not copied into duplicates. Variables are public, and code in a public Space is visible to anyone.
-
gradio_client fails with "Cannot find a function with api_name: /predict". What do you do first?
Show answer
B. Call client.view_api() and use the endpoint name it lists view_api() shows the real endpoints. In current Gradio an Interface's endpoint is named after the function, here /mood.
-
Why load the pipeline at the top of app.py instead of inside mood()?
Show answer
B. So the model loads once at startup instead of on every request Loading a model takes far longer than one prediction. Loading at startup means each visitor waits only for their own prediction. On ZeroGPU the docs also place the model on cuda at module level.
Recap
- A Space is a Git repository that runs an app: Gradio, Docker or static. Every push rebuilds and restarts it.
- Static Spaces are free; Gradio and Docker need a paid plan, except up to 2 ZeroGPU Gradio Spaces for free accounts in good standing.
- A Gradio Space is app.py (load the model once, wrap a function in gr.Interface), requirements.txt and a README with sdk: gradio. Test it locally with python app.py and gradio_client.
- ZeroGPU lends a shared GPU only while a @spaces.GPU function runs, within daily quotas; the decorator does nothing elsewhere.
- Tokens and keys go in Space secrets and are read with os.getenv, never written in the code.