Spaces:
Sleeping
title: OpenCode Cloud
emoji: π€
colorFrom: gray
colorTo: indigo
sdk: docker
app_port: 7860
pinned: false
license: mit
opencode-cloud
Run opencode β an open source AI coding agent β as a hosted server you reach from a browser, from the desktop app, or from a local build of the web app. One Dockerfile, deployable to Hugging Face Spaces or Railway.
opencode serve is not just an API: the server also serves the web UI on its
catch-all route, so opening the deployment URL gives you the full app, same-origin.
Deploy on Hugging Face Spaces
Create a Space β SDK Docker, blank template. Push this repo to it (or duplicate an existing Space built from it).
Settings β Variables and secrets and add:
Secret Required Notes OPENCODE_SERVER_PASSWORDyes The container refuses to start without it. This is the only thing protecting a shell. OPENCODE_SERVER_USERNAMEno Defaults to opencode.OPENCODE_API_KEYpick β₯1 opencode zen curated models ANTHROPIC_API_KEYpick β₯1 https://console.anthropic.com OPENAI_API_KEYpick β₯1 https://platform.openai.com/api-keys GEMINI_API_KEYpick β₯1 https://aistudio.google.com/apikey Open the Space URL and log in with
opencode+ your password.
Provider keys work as plain env vars β opencode registers any provider whose models.dev env var is present, no login step needed.
Persistence
Free Spaces are ephemeral and are stopped after ~48h idle. Sessions, provider logins and any uncommitted code are lost on restart. Two options:
- Free:
git cloneyour repo into the workspace and push before you walk away. - Paid: enable persistent storage (Settings β Storage). It mounts at
/data, whichentrypoint.shdetects and uses for the XDG dirs and the workspace, so state survives restarts. Watch the boot log β it prints which mode it picked.
Public or private Space?
A private Space adds your HF login in front of everything, which is the safer
default for browser use. The catch: HF authenticates private Spaces with an
Authorization: Bearer hf_β¦ header, and opencode wants Authorization: Basic β¦ β
one header, two claimants. So a private Space works in a browser (HF session
cookie) but a desktop-app or local-app connection to a private Space will fight
over that header. Keep the Space public with a long password if you want those.
Deploy on Railway
railway init # or point a service at this GitHub repo
railway variables --set OPENCODE_SERVER_PASSWORD=...
railway up
Railway injects $PORT, which entrypoint.sh uses automatically. Attach a volume
with mount path /data for persistence. Details and caveats: docs/RAILWAY.md.
Run as a GitHub Codespace
Code -> Codespaces -> Create codespace on main
Set OPENCODE_SERVER_PASSWORD first, as a Codespaces secret β repository secrets
and Actions secrets are separate and are not visible here:
gh secret set OPENCODE_SERVER_PASSWORD --app codespaces --repo OWNER/REPO
.devcontainer/devcontainer.json builds the same Dockerfile, forwards 7860 publicly and
starts the server on postStartCommand. Public is safe only because entrypoint.sh refuses
to start without a password; without the secret the codespace comes up with no server and the
reason is in /tmp/opencode.log.
Unlike a Space or a Railway service, a codespace stops after 30 minutes idle (240 maximum).
It keeps its disk and restarts under the same name and URL, and postStartCommand boots the
server again β but it is not always-on, and GitHub deletes a stopped codespace after the
retention period, up to 30 days. State lives under /workspaces/.opencode-state, so it
survives a stop and is lost only with the codespace itself.
A personal Free account includes 120 core-hours a month, which is roughly 60 hours of wall clock on a 2-core machine; the idle timeout is what keeps that from draining.
Keeping the Space in sync with GitHub
.github/workflows/sync-to-hf-space.yml builds the image, boots it, checks that
it refuses to start unauthenticated and that it serves an authenticated API and
the web UI β and only then force-pushes main to the Space and waits for it to
report RUNNING. A broken Dockerfile fails on the runner instead of leaving the
Space stuck in BUILD_ERROR.
Configure it under Settings β Secrets and variables β Actions:
| Name | Kind | Notes |
|---|---|---|
HF_TOKEN |
secret | A Hugging Face write token. The HF username is derived from it, so it is the only secret needed. |
HF_SPACE |
variable | owner/space-name. Optional β defaults to this GitHub repo's owner/name. A public Space id isn't sensitive, so a variable keeps it readable in logs; a secret of the same name also works. |
GitHub is the source of truth: the sync force-pushes, so a commit made only in the Space's web UI will be discarded. Edit here, not there.
Connecting the desktop app or a local web app
You don't have to use the in-browser UI. In the app, Settings β Servers β Add
and enter the deployment URL plus the username/password. The server's CORS
allowlist already covers localhost, *.opencode.ai and the desktop app's
oc://renderer origin, so no extra flags are needed. Only a frontend you host on
your own domain needs OPENCODE_CORS_ORIGINS=https://your.domain.
Giving the agent Hugging Face access
Set HF_TOKEN as a Space secret (or Railway variable) and the agent can create
and manage Hugging Face repos: the official hf CLI is installed in the image,
it picks the token up from the environment with no login step, and entrypoint.sh
installs agents/hf-spaces.md as opencode's global
AGENTS.md so the agent knows the recipes. With no HF_TOKEN set, that file is
removed and nothing advertises the capability.
This hands your HF account to whoever can reach the server. A standard HF write token covers every repo the account can write to β including org repos β so the blast radius is much larger than "a shell in a container". Two things make it meaningfully safer:
- Use a fine-grained token scoped to just the permissions you need (write access to Spaces, say) rather than an account-wide write token.
- Remember the agent reads untrusted content β repos, web pages, issue text. Anything it reads can try to talk it into using the token. The instructions tell it never to print the token, but that is a speed bump, not a boundary.
Read this before you deploy
- This is a remote shell. Every route allows command execution and file read/write in the container. Basic auth over HTTPS is the entire security model, so use a long random password, and think twice about what credentials you put in the container alongside it.
- Provider OAuth "login with browser" flows don't work on a remote server.
They bind a
http://localhost:<port>/auth/callbacklistener inside the container, so your browser is redirected to your own machine instead. Use API keys. - CPU only, and modest. Fine for the agent; you can't run local models.
- Check the host's terms. A general-purpose remote shell is not the ML-demo use case Spaces are described for; a public one with a weak password is the way to get flagged.
Environment variables
| Variable | Default | Purpose |
|---|---|---|
OPENCODE_SERVER_PASSWORD |
β | Required. Basic auth password. |
OPENCODE_SERVER_USERNAME |
opencode |
Basic auth username. |
PORT |
7860 |
Listen port. Railway sets this; HF must match app_port. |
OPENCODE_STATE_ROOT |
/data |
Where to look for a writable volume. Codespaces sets /workspaces/.opencode-state. |
OPENCODE_WORKSPACE |
$STATE_ROOT/workspace or $HOME/workspace |
Directory to serve. |
OPENCODE_CORS_ORIGINS |
β | Comma-separated extra CORS origins. |
HF_TOKEN |
β | Optional. A Hugging Face token, which lets the agent create and manage HF repos with the bundled hf CLI. See the warning below. |
Build arg OPENCODE_VERSION pins a release (default: latest).
MIT. opencode itself is Β© Anomaly Innovations; this repo is deployment wrapper code.