File size: 8,474 Bytes
5bd2d41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
930c3ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e85a4d4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5bd2d41
 
 
 
 
 
 
 
 
 
e1f9a64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5bd2d41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
930c3ef
5bd2d41
 
e1f9a64
5bd2d41
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
---
title: OpenCode Cloud
emoji: πŸ€–
colorFrom: gray
colorTo: indigo
sdk: docker
app_port: 7860
pinned: false
license: mit
---

# opencode-cloud

Run [opencode](https://opencode.ai) β€” 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

1. **Create a Space** β†’ SDK **Docker**, blank template. Push this repo to it
   (or duplicate an existing Space built from it).
2. **Settings β†’ Variables and secrets** and add:

   | Secret | Required | Notes |
   |---|---|---|
   | `OPENCODE_SERVER_PASSWORD` | **yes** | The container refuses to start without it. This is the only thing protecting a shell. |
   | `OPENCODE_SERVER_USERNAME` | no | Defaults to `opencode`. |
   | `OPENCODE_API_KEY` | pick β‰₯1 | [opencode zen](https://opencode.ai/zen) curated models |
   | `ANTHROPIC_API_KEY` | pick β‰₯1 | https://console.anthropic.com |
   | `OPENAI_API_KEY` | pick β‰₯1 | https://platform.openai.com/api-keys |
   | `GEMINI_API_KEY` | pick β‰₯1 | https://aistudio.google.com/apikey |

3. 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 clone` your repo into the workspace and push before you walk away.
- **Paid:** enable persistent storage (Settings β†’ Storage). It mounts at `/data`,
  which `entrypoint.sh` detects 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

```bash
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](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:

```bash
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`](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/callback` listener *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.