Spaces:
Running
GitHub β Hugging Face Space with Custom Domain β Setup Guide
A complete, repeatable playbook. Follow in order. Sections marked β οΈ are the common pitfall spots.
Table of Contents
- Prerequisites
- Create the Hugging Face Space
- Configure the GitHub Repository
- Set Up the Sync Workflow
- README Front Matter (Critical)
- Custom Domain Setup
- Gradio
root_pathRule - Verify End-to-End
- Troubleshooting Reference
1. Prerequisites
| What | Where to get it |
|---|---|
| Hugging Face account | https://huggingface.co |
| HF write token (fine-grained or classic) | HF β Settings β Access Tokens β New token β Role: Write |
| GitHub repo (public or private) | github.com |
| Domain registrar access (for custom domain) | Your registrar (Cloudflare, Namecheap, etc.) |
2. Create the Hugging Face Space
- Go to https://huggingface.co/new-space
- Fill in:
- Owner: your HF username or org
- Space name: e.g.
github-sync-test - SDK: Gradio (or Streamlit)
- Visibility: Public or Private
- Click Create Space β the Space is created with a default
README.md.
You do not need to push any code manually; the GitHub Action will do it.
3. Configure the GitHub Repository
3.1 Add the HF token as a GitHub Secret
- GitHub repo β Settings β Secrets and variables β Actions
- Click New repository secret
- Name:
HF_TOKEN - Value: your HF write token from step 1
- Name:
- Save.
3.2 Minimum required files
your-repo/
βββ README.md β MUST have HF front matter (see Β§5)
βββ main.py β your Gradio app entry point
βββ requirements.txt β at minimum: gradio>=5.0
βββ .github/
βββ workflows/
βββ sync.yml
4. Set Up the Sync Workflow
Create .github/workflows/sync.yml:
name: Sync to Hugging Face
on:
push:
branches: [master] # or "main" β match your default branch
workflow_dispatch: # allows manual trigger from GitHub UI
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history required by hub-sync
lfs: true
- name: Write VERSION file
run: echo "${{ github.sha }}" > VERSION
- uses: huggingface/hub-sync@main
with:
github_repo_id: YOUR_GH_USERNAME/YOUR_GH_REPO
huggingface_repo_id: YOUR_HF_USERNAME/YOUR_SPACE_NAME
repo_type: space # MUST be "space" for HF Spaces
hf_token: ${{ secrets.HF_TOKEN }}
Replace YOUR_GH_USERNAME/YOUR_GH_REPO and YOUR_HF_USERNAME/YOUR_SPACE_NAME with your actual values.
5. README Front Matter (Critical)
β οΈ The single most common cause of sync failures.
The README.md at the repo root must start with this YAML front matter block. Without it, HF rejects the push with "Missing configuration in README".
---
title: your-space-name
emoji: "π€"
colorFrom: blue
colorTo: indigo
sdk: gradio
app_file: main.py
pinned: false
---
# Your Space Title
...rest of your README...
| Field | Notes |
|---|---|
title |
Must match (or at least not conflict with) your HF Space name |
sdk |
gradio or streamlit |
app_file |
The Python file that launches your app (e.g. main.py) |
emoji, colorFrom, colorTo |
Cosmetic β any valid values work |
pinned |
true pins the Space on your profile |
6. Custom Domain Setup
6.1 Add the domain in HF Space settings
- HF Space β Settings β Custom domains
- Enter your domain: e.g.
demo.example.com - HF shows you a CNAME target (looks like
billyaungmyint-github-sync-test.hf.space) - Copy that CNAME target value.
6.2 Add the DNS record at your registrar
| Type | Host | Value | TTL |
|---|---|---|---|
CNAME |
demo (subdomain part only) |
billyaungmyint-github-sync-test.hf.space |
300 or Auto |
If you want an apex/root domain (
example.com), use a CNAME Flattening or ALIAS record β check your registrar's docs. Most registrars support this via Cloudflare's "CNAME at root" feature.
6.3 Wait for DNS propagation
- Typical: 1β5 minutes on Cloudflare, up to 48 hours on others.
- Verify with:
nslookup demo.example.comβ should resolve to HF's servers. - HF will automatically provision a TLS certificate once the CNAME resolves.
6.4 β οΈ No GRADIO_ROOT_PATH needed
When using a custom domain at the root path (e.g. https://demo.example.com):
- Do not set
GRADIO_ROOT_PATHin HF Space variables. - Leave it unset (or set to
""). - Setting it to the old
.hf.spaceURL after a domain change causes"Could not get API info. fetch failed".
Only set GRADIO_ROOT_PATH if your app is served at a subpath, e.g. https://example.com/myapp β set GRADIO_ROOT_PATH=/myapp.
7. Gradio root_path Rule
Use this pattern in your main.py β it is safe for all scenarios:
import os
if __name__ == "__main__":
# Strip trailing slash to avoid double-slash issues.
# Leave GRADIO_ROOT_PATH unset (or "") for custom domains at root.
_root_path = os.getenv("GRADIO_ROOT_PATH", "").rstrip("/")
demo.launch(server_name="0.0.0.0", root_path=_root_path)
| Deployment scenario | GRADIO_ROOT_PATH env var value |
|---|---|
Default .hf.space URL |
(unset) or "" |
Custom domain at root (myapp.com) |
(unset) or "" |
Custom domain at subpath (myapp.com/demo) |
/demo |
| Local dev | (unset) β runs fine |
8. Verify End-to-End
Run through this checklist after initial setup or after any DNS/domain change:
-
nslookup your-domain.comresolves (no NXDOMAIN) -
https://your-domain.comloads the Space (no TLS error) - Browser DevTools β Network: no failed
/infoorqueue/joinrequests - Push a trivial commit to GitHub β Action runs green β Space rebuilds β change appears
- HF Space settings β Custom domains β Status shows β (not "Pending")
9. Troubleshooting Reference
| Error | Cause | Fix |
|---|---|---|
Missing configuration in README |
README has no HF YAML front matter | Add the front matter block (see Β§5) |
Could not get API info. fetch failed |
GRADIO_ROOT_PATH set to wrong URL, or DNS not yet propagated |
Clear GRADIO_ROOT_PATH in Space variables (see Β§6.4 & Β§7) |
HF_TOKEN secret not found / 401 |
Secret missing or token lacks write scope | Re-add secret with a Write token (see Β§3.1) |
Action fails: repo_type: space error |
Wrong repo_type in workflow |
Set repo_type: space in sync.yml (see Β§4) |
| Space stuck on "Building" | Dependency error in requirements.txt |
Check Space build logs; pin a working version |
| TLS certificate "Pending" for >30 min | CNAME not propagated yet or wrong target | Verify CNAME value matches exactly what HF shows |
| Custom domain works but assets 404 | root_path set to a subpath that doesn't exist |
Set GRADIO_ROOT_PATH="" or the correct subpath |
| Space rebuilds but shows old code | fetch-depth: 0 missing in workflow |
Add fetch-depth: 0 to actions/checkout@v4 (see Β§4) |