polats Claude Opus 5 (1M context) commited on
Commit
930c3ef
Β·
1 Parent(s): e1f9a64

Run as a GitHub Codespace

Browse files

A third home for the same image, alongside Hugging Face Spaces and Railway, and
the only one that needs no account the user does not already have.

A dev container starts with its entrypoint overridden, so the image's ENTRYPOINT
never runs and the server has to be started explicitly. That happens in
postStartCommand rather than postCreateCommand because codespaces stop after 30
minutes idle: only postStartCommand runs again on resume, which is what makes this
behave like a server instead of a dev box to be restarted by hand.

The port is declared public in portsAttributes because there is no REST endpoint
for port visibility β€” a client that provisions codespaces cannot otherwise expose
anything without a human toggling it in the UI. Public is only safe here because
entrypoint.sh refuses to start without OPENCODE_SERVER_PASSWORD; every route on
this server runs shell commands, so a missing password has to fail loudly rather
than publish an open shell.

State goes under /workspaces rather than /data, which does not exist in a
codespace, so sessions and code survive an idle stop and are lost only when the
codespace itself is deleted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Files changed (2) hide show
  1. .devcontainer/devcontainer.json +58 -0
  2. README.md +30 -1
.devcontainer/devcontainer.json ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // opencode-cloud as a GitHub Codespace β€” a third home alongside Hugging Face Spaces and
2
+ // Railway.
3
+ //
4
+ // The same Dockerfile serves all three. What differs here is that a dev container starts with
5
+ // its entrypoint overridden, so the image's ENTRYPOINT never runs: the server has to be
6
+ // started explicitly, and started again after every idle stop.
7
+ //
8
+ // Codespaces stop after 30 minutes idle by default. That is fine for this use β€” a stopped
9
+ // codespace keeps its disk and restarts under the same name and URL β€” but it does mean
10
+ // postStartCommand, not postCreateCommand, is what boots the server, because only the former
11
+ // runs on every resume.
12
+ {
13
+ "name": "opencode-cloud",
14
+ "build": {
15
+ "dockerfile": "../Dockerfile"
16
+ },
17
+
18
+ // uid 1000, matching the image. The Dockerfile creates nothing else.
19
+ "remoteUser": "node",
20
+
21
+ // Hugging Face has no $PORT and falls back to 7860; keep the same here so one number
22
+ // describes the server everywhere.
23
+ "containerEnv": {
24
+ "PORT": "7860",
25
+ // entrypoint.sh persists sessions, logins and code under this root when it is writable.
26
+ // /data does not exist in a codespace, but /workspaces survives stop/start, so state
27
+ // outlives an idle timeout and is lost only when the codespace itself is deleted.
28
+ "OPENCODE_STATE_ROOT": "/workspaces/.opencode-state"
29
+ },
30
+
31
+ "forwardPorts": [7860],
32
+ "portsAttributes": {
33
+ "7860": {
34
+ "label": "opencode",
35
+ // The only way to expose a port without a human toggling it: there is no REST endpoint
36
+ // for port visibility, so a client that provisions codespaces has to declare it here.
37
+ //
38
+ // Public is safe *because* entrypoint.sh refuses to start without
39
+ // OPENCODE_SERVER_PASSWORD. Every route on this server runs shell commands, so basic
40
+ // auth is the only thing in front of it; a missing password fails loudly rather than
41
+ // publishing an open shell.
42
+ "visibility": "public",
43
+ "onAutoForward": "silent"
44
+ }
45
+ },
46
+
47
+ // Runs on create *and* on every resume from an idle stop, which is what makes the codespace
48
+ // behave like a server rather than a dev box you have to log into and restart by hand.
49
+ // Backgrounded so the codespace can finish starting; the log is where a refused boot
50
+ // (usually a missing password) explains itself.
51
+ "postStartCommand": "nohup /home/node/entrypoint.sh > /tmp/opencode.log 2>&1 & echo started",
52
+
53
+ "customizations": {
54
+ "vscode": {
55
+ "extensions": []
56
+ }
57
+ }
58
+ }
README.md CHANGED
@@ -74,6 +74,35 @@ with mount path `/data` for persistence. Details and caveats: [docs/RAILWAY.md](
74
 
75
  ---
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  ## Keeping the Space in sync with GitHub
78
 
79
  `.github/workflows/sync-to-hf-space.yml` builds the image, boots it, checks that
@@ -150,7 +179,7 @@ removed and nothing advertises the capability.
150
  | `OPENCODE_SERVER_PASSWORD` | β€” | **Required.** Basic auth password. |
151
  | `OPENCODE_SERVER_USERNAME` | `opencode` | Basic auth username. |
152
  | `PORT` | `7860` | Listen port. Railway sets this; HF must match `app_port`. |
153
- | `OPENCODE_STATE_ROOT` | `/data` | Where to look for a writable volume. |
154
  | `OPENCODE_WORKSPACE` | `$STATE_ROOT/workspace` or `$HOME/workspace` | Directory to serve. |
155
  | `OPENCODE_CORS_ORIGINS` | β€” | Comma-separated extra CORS origins. |
156
  | `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. |
 
74
 
75
  ---
76
 
77
+ ## Run as a GitHub Codespace
78
+
79
+ ```
80
+ Code -> Codespaces -> Create codespace on main
81
+ ```
82
+
83
+ Set `OPENCODE_SERVER_PASSWORD` first, as a **Codespaces** secret β€” repository secrets
84
+ and Actions secrets are separate and are not visible here:
85
+
86
+ ```bash
87
+ gh secret set OPENCODE_SERVER_PASSWORD --app codespaces --repo OWNER/REPO
88
+ ```
89
+
90
+ `.devcontainer/devcontainer.json` builds the same Dockerfile, forwards 7860 publicly and
91
+ starts the server on `postStartCommand`. Public is safe only because `entrypoint.sh` refuses
92
+ to start without a password; without the secret the codespace comes up with no server and the
93
+ reason is in `/tmp/opencode.log`.
94
+
95
+ Unlike a Space or a Railway service, a codespace **stops after 30 minutes idle** (240 maximum).
96
+ It keeps its disk and restarts under the same name and URL, and `postStartCommand` boots the
97
+ server again β€” but it is not always-on, and GitHub deletes a stopped codespace after the
98
+ retention period, up to 30 days. State lives under `/workspaces/.opencode-state`, so it
99
+ survives a stop and is lost only with the codespace itself.
100
+
101
+ A personal Free account includes 120 core-hours a month, which is roughly 60 hours of wall
102
+ clock on a 2-core machine; the idle timeout is what keeps that from draining.
103
+
104
+ ---
105
+
106
  ## Keeping the Space in sync with GitHub
107
 
108
  `.github/workflows/sync-to-hf-space.yml` builds the image, boots it, checks that
 
179
  | `OPENCODE_SERVER_PASSWORD` | β€” | **Required.** Basic auth password. |
180
  | `OPENCODE_SERVER_USERNAME` | `opencode` | Basic auth username. |
181
  | `PORT` | `7860` | Listen port. Railway sets this; HF must match `app_port`. |
182
+ | `OPENCODE_STATE_ROOT` | `/data` | Where to look for a writable volume. Codespaces sets `/workspaces/.opencode-state`. |
183
  | `OPENCODE_WORKSPACE` | `$STATE_ROOT/workspace` or `$HOME/workspace` | Directory to serve. |
184
  | `OPENCODE_CORS_ORIGINS` | β€” | Comma-separated extra CORS origins. |
185
  | `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. |