{"course":{"slug":"hugging-face","title":"Hugging Face, End to End","url":"https://addaly.com/learn/hugging-face","level":"intermediate","track":"create"},"lesson":{"slug":"beyond-gradio","title":"Streamlit, Docker and a Space with no server at all","url":"https://addaly.com/learn/hugging-face/beyond-gradio","position":35,"of":75,"minutes":8,"answer":"Gradio, Streamlit, Docker and static are four different deployment shapes, and the static Space running transformers.js is the one people overlook: no server, no sleep, no cost, and the user's data never leaves their device.","keyIdea":"Gradio, Streamlit, Docker and static are four different deployment shapes, and the static Space running transformers.js is the one people overlook: no server, no sleep, no cost, and the user's data never leaves their device."},"body":{"format":"markdown","text":"## Four SDKs, four different jobs\n\n**Gradio** is the default and the best-supported. Its components map onto machine-learning inputs and outputs, it has ZeroGPU support, it exposes an API automatically, and most examples you find are written in it.\n\n**Streamlit** re-runs your whole script top to bottom on every interaction, with `st.session_state` holding what must survive. That model is excellent for dashboards and data exploration and awkward for a chat interface, because a long-running generation restarts the script's reasoning about state. Choose it when the Space is mostly charts and tables over a dataframe.\n\n**Docker** is the escape hatch. You supply a `Dockerfile`, the Hub builds and runs it, and anything that listens on port 7860 works: FastAPI, Flask, Node, a Rust binary, a multi-process stack.\n\n**Static** serves files. No server process, no sleep, no cold start.\n\n## The static Space is underrated\n\nWith transformers.js from module three, a static Space is a complete AI application that costs nothing and never sleeps:\n\n```yaml\n---\ntitle: Local Sentiment\nsdk: static\napp_file: index.html\n---\n```\n\n`index.html` loads the library from a CDN, downloads an ONNX model into the browser, and runs everything on the visitor's device. No queue, no container, no per-request compute, and the user's text never leaves their machine. For classifiers, embeddings, background removal, OCR and short-clip speech recognition, this is frequently the best engineering answer as well as the cheapest.\n\nIts limits are exactly the browser's: the model must be small enough to download once, and heavy generation is impractical.\n\n## Docker Spaces, and the one gotcha\n\n```dockerfile\nFROM python:3.11-slim\n\nRUN useradd -m -u 1000 user\nUSER user\nENV PATH=\"/home/user/.local/bin:$PATH\" \\\n    HF_HOME=/home/user/.cache/huggingface\n\nWORKDIR /app\nCOPY --chown=user requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\nCOPY --chown=user . .\n\nEXPOSE 7860\nCMD [\"uvicorn\", \"main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"7860\"]\n```\n\nThree things this gets right, and each of them is a build that fails otherwise:\n\n1. **Port 7860.** The platform routes to it. Listening on 8000 produces a Space that builds, starts and shows a blank page forever.\n2. **`--host 0.0.0.0`.** Binding to localhost means nothing outside the container can reach it. Same blank page.\n3. **A non-root user with a writable home.** Containers run unprivileged, and libraries that try to write a cache into a root-owned directory raise permission errors at import: usually the first confusing failure people meet here.\n\nReach for Docker when you need a database alongside the app, a system library that pip cannot install, a non-Python runtime, or a custom web front end. Do not reach for it to avoid learning Gradio; you will rebuild a queue, a file upload widget and a streaming response by hand.\n\n## Spaces are also a way to read code\n\nEvery public Space is a public repository. When you find one that does what you want, click Files and read it. That is a working configuration (the exact dependency versions, the model revision, the hardware, the prompt) which is worth more than documentation because it is running.\n\nThis is the fastest way to learn a new technique on the Hub, and it is also why the licence of a Space's code matters: reading it is always fine, copying it is governed by whatever licence its readme declares, and many declare none. Ask, or write your own from what you understood.\n\n## The build is a cache, and caches go stale\n\nAll four SDKs share one build system, and it caches layers. Change `requirements.txt` and the dependency layer rebuilds; change only `app.py` and it does not. That is why a Space usually restarts in seconds and occasionally takes ten minutes.\n\nIt is also why a Space can fail in a way the current code cannot explain: a layer built against a package version that has since been yanked, or a half-downloaded model in the image cache. The factory reboot in the debugging lesson discards all of it and starts clean.\n\n## Choosing in one line\n\n- Model demo with inputs and outputs → **Gradio**.\n- Dashboard over a dataset → **Streamlit**.\n- Small model, privacy, zero cost, no sleep → **static** plus transformers.js.\n- Anything else → **Docker**.\n\n## Do this now\n\nFind a public Space that does something close to what you want and read its `app.py` and `requirements.txt` end to end. Note the pinned versions and the hardware in the readme. You have just been handed a working configuration for free, which is the single most useful thing the Spaces ecosystem provides."},"check":{"question":"A Docker Space builds successfully, reports itself as running, and shows a blank page. What should be checked first?","options":[{"text":"Whether the container binds to 0.0.0.0 on port 7860, since the platform routes only to that address and port.","correct":true},{"text":"Whether the SDK field in the readme is set to docker, because a mismatch makes the platform serve static files instead.","correct":false,"tempting":"A wrong SDK would change the build entirely rather than produce a running container with no reachable page."},{"text":"Whether the image runs as a non-root user, since privileged containers are rejected by the runtime after start.","correct":false,"tempting":"Permissions matter for cache writes and surface as import errors, not as a silent blank page."},{"text":"Whether the build exceeded the free tier's image size limit, which truncates the final layer and serves an empty response.","correct":false,"tempting":"Invents a truncation behaviour; an oversized image fails the build outright."}]},"prev":{"slug":"storage-in-a-space","title":"The filesystem disappears, and what to do about it","url":"https://addaly.com/learn/hugging-face/storage-in-a-space"},"next":{"slug":"calling-a-space-as-an-api","title":"Every Space is an API you can call","url":"https://addaly.com/learn/hugging-face/calling-a-space-as-an-api"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Streamlit, Docker and a Space with no server at all', in Hugging Face, End to End. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/hugging-face/beyond-gradio (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Streamlit, Docker and a Space with no server at all","courseTitle":"Hugging Face, End to End","module":{"index":4,"slug":"spaces-and-shipping-something","title":"Spaces: putting something in front of people"},"answer":null,"keyIdea":"Gradio, Streamlit, Docker and static are four different deployment shapes, and the static Space running transformers.js is the one people overlook: no server, no sleep, no cost, and the user’s data never leaves their device.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"four-sdks-four-different-jobs","runs":[{"text":"Four SDKs, four different jobs"}]},{"t":"p","runs":[{"text":"Gradio","b":true},{"text":" is the default and the best-supported. Its components map onto machine-learning inputs and outputs, it has ZeroGPU support, it exposes an API automatically, and most examples you find are written in it."}]},{"t":"p","runs":[{"text":"Streamlit","b":true},{"text":" re-runs your whole script top to bottom on every interaction, with "},{"text":"st.session_state","code":true},{"text":" holding what must survive. That model is excellent for dashboards and data exploration and awkward for a chat interface, because a long-running generation restarts the script’s reasoning about state. Choose it when the Space is mostly charts and tables over a dataframe."}]},{"t":"p","runs":[{"text":"Docker","b":true},{"text":" is the escape hatch. You supply a "},{"text":"Dockerfile","code":true},{"text":", the Hub builds and runs it, and anything that listens on port 7860 works: FastAPI, Flask, Node, a Rust binary, a multi-process stack."}]},{"t":"p","runs":[{"text":"Static","b":true},{"text":" serves files. No server process, no sleep, no cold start."}]},{"t":"h2","id":"the-static-space-is-underrated","runs":[{"text":"The static Space is underrated"}]},{"t":"p","runs":[{"text":"With transformers.js from module three, a static Space is a complete AI application that costs nothing and never sleeps:"}]},{"t":"code","lang":"yaml","label":"YAML","text":"---\ntitle: Local Sentiment\nsdk: static\napp_file: index.html\n---"},{"t":"p","runs":[{"text":"index.html","code":true},{"text":" loads the library from a CDN, downloads an ONNX model into the browser, and runs everything on the visitor’s device. No queue, no container, no per-request compute, and the user’s text never leaves their machine. For classifiers, embeddings, background removal, OCR and short-clip speech recognition, this is frequently the best engineering answer as well as the cheapest."}]},{"t":"p","runs":[{"text":"Its limits are exactly the browser’s: the model must be small enough to download once, and heavy generation is impractical."}]},{"t":"h2","id":"docker-spaces-and-the-one-gotcha","runs":[{"text":"Docker Spaces, and the one gotcha"}]},{"t":"code","lang":"dockerfile","label":"Dockerfile","text":"FROM python:3.11-slim\n\nRUN useradd -m -u 1000 user\nUSER user\nENV PATH=\"/home/user/.local/bin:$PATH\" \\\n    HF_HOME=/home/user/.cache/huggingface\n\nWORKDIR /app\nCOPY --chown=user requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\nCOPY --chown=user . .\n\nEXPOSE 7860\nCMD [\"uvicorn\", \"main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"7860\"]"},{"t":"p","runs":[{"text":"Three things this gets right, and each of them is a build that fails otherwise:"}]},{"t":"ol","start":1,"items":[[{"text":"Port 7860.","b":true},{"text":" The platform routes to it. Listening on 8000 produces a Space that builds, starts and shows a blank page forever."}],[{"text":"--host 0.0.0.0","b":true,"code":true},{"text":".","b":true},{"text":" Binding to localhost means nothing outside the container can reach it. Same blank page."}],[{"text":"A non-root user with a writable home.","b":true},{"text":" Containers run unprivileged, and libraries that try to write a cache into a root-owned directory raise permission errors at import: usually the first confusing failure people meet here."}]]},{"t":"p","runs":[{"text":"Reach for Docker when you need a database alongside the app, a system library that pip cannot install, a non-Python runtime, or a custom web front end. Do not reach for it to avoid learning Gradio; you will rebuild a queue, a file upload widget and a streaming response by hand."}]},{"t":"h2","id":"spaces-are-also-a-way-to-read-code","runs":[{"text":"Spaces are also a way to read code"}]},{"t":"p","runs":[{"text":"Every public Space is a public repository. When you find one that does what you want, click Files and read it. That is a working configuration (the exact dependency versions, the model revision, the hardware, the prompt) which is worth more than documentation because it is running."}]},{"t":"p","runs":[{"text":"This is the fastest way to learn a new technique on the Hub, and it is also why the licence of a Space’s code matters: reading it is always fine, copying it is governed by whatever licence its readme declares, and many declare none. Ask, or write your own from what you understood."}]},{"t":"h2","id":"the-build-is-a-cache-and-caches-go-stale","runs":[{"text":"The build is a cache, and caches go stale"}]},{"t":"p","runs":[{"text":"All four SDKs share one build system, and it caches layers. Change "},{"text":"requirements.txt","code":true},{"text":" and the dependency layer rebuilds; change only "},{"text":"app.py","code":true},{"text":" and it does not. That is why a Space usually restarts in seconds and occasionally takes ten minutes."}]},{"t":"p","runs":[{"text":"It is also why a Space can fail in a way the current code cannot explain: a layer built against a package version that has since been yanked, or a half-downloaded model in the image cache. The factory reboot in the debugging lesson discards all of it and starts clean."}]},{"t":"h2","id":"choosing-in-one-line","runs":[{"text":"Choosing in one line"}]},{"t":"ul","items":[[{"text":"Model demo with inputs and outputs → "},{"text":"Gradio","b":true},{"text":"."}],[{"text":"Dashboard over a dataset → "},{"text":"Streamlit","b":true},{"text":"."}],[{"text":"Small model, privacy, zero cost, no sleep → "},{"text":"static","b":true},{"text":" plus transformers.js."}],[{"text":"Anything else → "},{"text":"Docker","b":true},{"text":"."}]]},{"t":"h2","id":"do-this-now","runs":[{"text":"Do this now"}],"role":"try"},{"t":"p","runs":[{"text":"Find a public Space that does something close to what you want and read its "},{"text":"app.py","code":true},{"text":" and "},{"text":"requirements.txt","code":true},{"text":" end to end. Note the pinned versions and the hardware in the readme. You have just been handed a working configuration for free, which is the single most useful thing the Spaces ecosystem provides."}]}],"check":{"question":[{"text":"A Docker Space builds successfully, reports itself as running, and shows a blank page. What should be checked first?"}],"options":[{"text":[{"text":"Whether the container binds to 0.0.0.0 on port 7860, since the platform routes only to that address and port."}],"correct":true,"tempting":null},{"text":[{"text":"Whether the SDK field in the readme is set to docker, because a mismatch makes the platform serve static files instead."}],"correct":false,"tempting":[{"text":"A wrong SDK would change the build entirely rather than produce a running container with no reachable page."}]},{"text":[{"text":"Whether the image runs as a non-root user, since privileged containers are rejected by the runtime after start."}],"correct":false,"tempting":[{"text":"Permissions matter for cache writes and surface as import errors, not as a silent blank page."}]},{"text":[{"text":"Whether the build exceeded the free tier’s image size limit, which truncates the final layer and serves an empty response."}],"correct":false,"tempting":[{"text":"Invents a truncation behaviour; an oversized image fails the build outright."}]}],"order":[3,1,2,0],"why":null},"prev":{"slug":"storage-in-a-space","title":"The filesystem disappears, and what to do about it","minutes":8},"next":{"slug":"calling-a-space-as-an-api","title":"Every Space is an API you can call","minutes":8}}}