Skip to content
learn
  1. Learn
  2. Artificial Intelligence
  3. Hugging Face, Hands On

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.

Intermediate 15 min Last reviewed

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

How a Space goes from your folder to a public app Step 1: your folder holds app.py, requirements.txt and a README.md whose YAML sets sdk: gradio. Step 2: you push it with git or hf upload to a Space repository on the Hub. Step 3: every commit triggers a build that installs the requirements and starts the app on the Space's hardware, CPU or ZeroGPU. Step 4: visitors use it in a browser or call its API with gradio_client. Step 5: secrets set in the Space settings reach the app as environment variables, so tokens never sit in the code. push each commit requests env vars your folder app.py, requirements Space repo Git on the Hub build install, start running app CPU or ZeroGPU visitors browser, API Space settings secrets, variables Figure 1. A Space is a Git repo that runs. Push code, the Hub builds and serves it; secrets come in from the settings, not the code. Step through it.
  1. Your folder app.py with the Gradio app, requirements.txt with the libraries, README.md with sdk: gradio in its YAML.
  2. Push git push or hf upload sends the files to the Space repository, like any other repo on the Hub.
  3. Build and run Each commit rebuilds the Space: requirements are installed and app.py is started on its hardware.
  4. Visitors Anyone with the link uses the web page; code can call the same function through the API.
  5. 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.

precheck.pyruns with python3 (standard library)
# 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
  1. Which of the three problems would stop the Space from starting, and which would start it but leak something?
  2. Add a check that requirements.txt mentions torch when app.py imports transformers. Why would a Space that builds fine still crash on the first request without it?
  3. 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:

Trade-offs

OptionWins whenLoses 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.py or committing a .env file 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.py and gradio_client find 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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

Get new lessons by email

Get new posts on backend and AI internals. No spam, unsubscribe anytime.

Your email is only used to send new posts. Privacy