Spaces:
Sleeping
Add deployable opencode server image for HF Spaces and Railway
Browse filesOne Dockerfile serving both hosts. entrypoint.sh resolves the port at runtime
($PORT on Railway, 7860 to match app_port on HF Spaces), detects a writable
/data mount and points the XDG dirs and workspace at it so sessions, provider
logins and code survive restarts, and refuses to start without
OPENCODE_SERVER_PASSWORD — every route on this server allows shell execution,
so basic auth is not optional on a public URL.
opencode is installed at build time rather than on first boot, since a free
Space's filesystem is ephemeral and would otherwise re-download the release on
every cold start. The installer hardcodes $HOME/.opencode/bin and ignores
OPENCODE_INSTALL_DIR, so the binary is moved onto the shared PATH afterwards.
The node images already ship a uid-1000 user, which is what HF Spaces requires,
so `node` is reused instead of adding a colliding one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- .gitignore +5 -0
- Dockerfile +45 -0
- LICENSE +21 -0
- README.md +119 -0
- docs/RAILWAY.md +46 -0
- entrypoint.sh +78 -0
- railway.json +12 -0
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.env
|
| 2 |
+
.env.*
|
| 3 |
+
!.env.example
|
| 4 |
+
node_modules/
|
| 5 |
+
.DS_Store
|
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# opencode-cloud — one image, two homes (Hugging Face Spaces + Railway).
|
| 2 |
+
#
|
| 3 |
+
# The port is decided at runtime by entrypoint.sh:
|
| 4 |
+
# * Railway injects $PORT
|
| 5 |
+
# * HF Spaces does not, so we fall back to 7860 (matches app_port in README.md)
|
| 6 |
+
FROM node:22-bookworm-slim
|
| 7 |
+
|
| 8 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 9 |
+
ca-certificates \
|
| 10 |
+
curl \
|
| 11 |
+
git \
|
| 12 |
+
jq \
|
| 13 |
+
less \
|
| 14 |
+
openssh-client \
|
| 15 |
+
ripgrep \
|
| 16 |
+
tar \
|
| 17 |
+
unzip \
|
| 18 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 19 |
+
|
| 20 |
+
# Install opencode at BUILD time so cold starts don't re-download the release.
|
| 21 |
+
# The official installer hardcodes $HOME/.opencode/bin (it ignores
|
| 22 |
+
# OPENCODE_INSTALL_DIR), so move the binary onto the shared PATH afterwards.
|
| 23 |
+
# Pin a release with: --build-arg OPENCODE_VERSION=1.18.18
|
| 24 |
+
ARG OPENCODE_VERSION=""
|
| 25 |
+
RUN VERSION="${OPENCODE_VERSION}" bash -c 'curl -fsSL https://opencode.ai/install | bash' \
|
| 26 |
+
&& mv "$HOME/.opencode/bin/opencode" /usr/local/bin/opencode \
|
| 27 |
+
&& chmod 755 /usr/local/bin/opencode \
|
| 28 |
+
&& rm -rf "$HOME/.opencode" \
|
| 29 |
+
&& opencode --version
|
| 30 |
+
|
| 31 |
+
# Hugging Face Spaces runs containers as uid 1000. The node images already ship
|
| 32 |
+
# a `node` user at uid 1000, so reuse it rather than useradd'ing a colliding one.
|
| 33 |
+
ENV HOME=/home/node
|
| 34 |
+
ENV PATH=/usr/local/bin:${PATH}
|
| 35 |
+
|
| 36 |
+
COPY --chown=node:node entrypoint.sh /home/node/entrypoint.sh
|
| 37 |
+
RUN chmod +x /home/node/entrypoint.sh \
|
| 38 |
+
&& mkdir -p /home/node/workspace \
|
| 39 |
+
&& chown -R node:node /home/node
|
| 40 |
+
|
| 41 |
+
USER node
|
| 42 |
+
WORKDIR /home/node/workspace
|
| 43 |
+
|
| 44 |
+
EXPOSE 7860
|
| 45 |
+
ENTRYPOINT ["/home/node/entrypoint.sh"]
|
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 polats
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: OpenCode Cloud
|
| 3 |
+
emoji: 🤖
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
pinned: false
|
| 9 |
+
license: mit
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# opencode-cloud
|
| 13 |
+
|
| 14 |
+
Run [opencode](https://opencode.ai) — an open source AI coding agent — as a hosted
|
| 15 |
+
server you reach from a browser, from the desktop app, or from a local build of the
|
| 16 |
+
web app. One Dockerfile, deployable to **Hugging Face Spaces** or **Railway**.
|
| 17 |
+
|
| 18 |
+
`opencode serve` is not just an API: the server also serves the web UI on its
|
| 19 |
+
catch-all route, so opening the deployment URL gives you the full app, same-origin.
|
| 20 |
+
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
## Deploy on Hugging Face Spaces
|
| 24 |
+
|
| 25 |
+
1. **Create a Space** → SDK **Docker**, blank template. Push this repo to it
|
| 26 |
+
(or duplicate an existing Space built from it).
|
| 27 |
+
2. **Settings → Variables and secrets** and add:
|
| 28 |
+
|
| 29 |
+
| Secret | Required | Notes |
|
| 30 |
+
|---|---|---|
|
| 31 |
+
| `OPENCODE_SERVER_PASSWORD` | **yes** | The container refuses to start without it. This is the only thing protecting a shell. |
|
| 32 |
+
| `OPENCODE_SERVER_USERNAME` | no | Defaults to `opencode`. |
|
| 33 |
+
| `OPENCODE_API_KEY` | pick ≥1 | [opencode zen](https://opencode.ai/zen) curated models |
|
| 34 |
+
| `ANTHROPIC_API_KEY` | pick ≥1 | https://console.anthropic.com |
|
| 35 |
+
| `OPENAI_API_KEY` | pick ≥1 | https://platform.openai.com/api-keys |
|
| 36 |
+
| `GEMINI_API_KEY` | pick ≥1 | https://aistudio.google.com/apikey |
|
| 37 |
+
|
| 38 |
+
3. Open the Space URL and log in with `opencode` + your password.
|
| 39 |
+
|
| 40 |
+
Provider keys work as plain env vars — opencode registers any provider whose
|
| 41 |
+
models.dev env var is present, no login step needed.
|
| 42 |
+
|
| 43 |
+
### Persistence
|
| 44 |
+
|
| 45 |
+
Free Spaces are **ephemeral** and are stopped after ~48h idle. Sessions, provider
|
| 46 |
+
logins and any uncommitted code are lost on restart. Two options:
|
| 47 |
+
|
| 48 |
+
- **Free:** `git clone` your repo into the workspace and push before you walk away.
|
| 49 |
+
- **Paid:** enable persistent storage (Settings → Storage). It mounts at `/data`,
|
| 50 |
+
which `entrypoint.sh` detects and uses for the XDG dirs *and* the workspace, so
|
| 51 |
+
state survives restarts. Watch the boot log — it prints which mode it picked.
|
| 52 |
+
|
| 53 |
+
### Public or private Space?
|
| 54 |
+
|
| 55 |
+
A **private** Space adds your HF login in front of everything, which is the safer
|
| 56 |
+
default for browser use. The catch: HF authenticates private Spaces with an
|
| 57 |
+
`Authorization: Bearer hf_…` header, and opencode wants `Authorization: Basic …` —
|
| 58 |
+
one header, two claimants. So a private Space works in a browser (HF session
|
| 59 |
+
cookie) but a **desktop-app or local-app connection to a private Space will fight
|
| 60 |
+
over that header**. Keep the Space public with a long password if you want those.
|
| 61 |
+
|
| 62 |
+
---
|
| 63 |
+
|
| 64 |
+
## Deploy on Railway
|
| 65 |
+
|
| 66 |
+
```bash
|
| 67 |
+
railway init # or point a service at this GitHub repo
|
| 68 |
+
railway variables --set OPENCODE_SERVER_PASSWORD=...
|
| 69 |
+
railway up
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
Railway injects `$PORT`, which `entrypoint.sh` uses automatically. Attach a volume
|
| 73 |
+
with mount path `/data` for persistence. Details and caveats: [docs/RAILWAY.md](docs/RAILWAY.md).
|
| 74 |
+
|
| 75 |
+
---
|
| 76 |
+
|
| 77 |
+
## Connecting the desktop app or a local web app
|
| 78 |
+
|
| 79 |
+
You don't have to use the in-browser UI. In the app, **Settings → Servers → Add**
|
| 80 |
+
and enter the deployment URL plus the username/password. The server's CORS
|
| 81 |
+
allowlist already covers `localhost`, `*.opencode.ai` and the desktop app's
|
| 82 |
+
`oc://renderer` origin, so no extra flags are needed. Only a frontend you host on
|
| 83 |
+
your own domain needs `OPENCODE_CORS_ORIGINS=https://your.domain`.
|
| 84 |
+
|
| 85 |
+
---
|
| 86 |
+
|
| 87 |
+
## Read this before you deploy
|
| 88 |
+
|
| 89 |
+
- **This is a remote shell.** Every route allows command execution and file
|
| 90 |
+
read/write in the container. Basic auth over HTTPS is the entire security model,
|
| 91 |
+
so use a long random password, and think twice about what credentials you put in
|
| 92 |
+
the container alongside it.
|
| 93 |
+
- **Provider OAuth "login with browser" flows don't work on a remote server.**
|
| 94 |
+
They bind a `http://localhost:<port>/auth/callback` listener *inside* the
|
| 95 |
+
container, so your browser is redirected to your own machine instead. Use API
|
| 96 |
+
keys.
|
| 97 |
+
- **CPU only, and modest.** Fine for the agent; you can't run local models.
|
| 98 |
+
- **Check the host's terms.** A general-purpose remote shell is not the ML-demo
|
| 99 |
+
use case Spaces are described for; a public one with a weak password is the way
|
| 100 |
+
to get flagged.
|
| 101 |
+
|
| 102 |
+
---
|
| 103 |
+
|
| 104 |
+
## Environment variables
|
| 105 |
+
|
| 106 |
+
| Variable | Default | Purpose |
|
| 107 |
+
|---|---|---|
|
| 108 |
+
| `OPENCODE_SERVER_PASSWORD` | — | **Required.** Basic auth password. |
|
| 109 |
+
| `OPENCODE_SERVER_USERNAME` | `opencode` | Basic auth username. |
|
| 110 |
+
| `PORT` | `7860` | Listen port. Railway sets this; HF must match `app_port`. |
|
| 111 |
+
| `OPENCODE_STATE_ROOT` | `/data` | Where to look for a writable volume. |
|
| 112 |
+
| `OPENCODE_WORKSPACE` | `$STATE_ROOT/workspace` or `$HOME/workspace` | Directory to serve. |
|
| 113 |
+
| `OPENCODE_CORS_ORIGINS` | — | Comma-separated extra CORS origins. |
|
| 114 |
+
|
| 115 |
+
Build arg `OPENCODE_VERSION` pins a release (default: latest).
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
MIT. opencode itself is © Anomaly Innovations; this repo is deployment wrapper code.
|
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Deploying on Railway
|
| 2 |
+
|
| 3 |
+
Railway is the sturdier of the two targets: no port/`app_port` coupling, cheap
|
| 4 |
+
volumes, and no always-on/idle-GC surprises. There is no free always-on tier.
|
| 5 |
+
|
| 6 |
+
## Steps
|
| 7 |
+
|
| 8 |
+
1. Create a project and point a service at this repo (or `railway init` + `railway up`).
|
| 9 |
+
`railway.json` selects the Dockerfile builder.
|
| 10 |
+
2. Set variables:
|
| 11 |
+
|
| 12 |
+
```bash
|
| 13 |
+
railway variables --set OPENCODE_SERVER_PASSWORD="$(openssl rand -base64 24)"
|
| 14 |
+
railway variables --set OPENCODE_API_KEY=... # and/or ANTHROPIC_API_KEY, etc.
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
3. **Attach a volume** with mount path `/data`. Without it, every redeploy wipes
|
| 18 |
+
sessions, provider logins and uncommitted code. `entrypoint.sh` picks `/data`
|
| 19 |
+
up automatically and logs which mode it chose.
|
| 20 |
+
4. Generate a domain (Settings → Networking → Generate Domain) and open it.
|
| 21 |
+
|
| 22 |
+
`$PORT` is injected by Railway and used automatically — do not hardcode a port.
|
| 23 |
+
|
| 24 |
+
## Do not set a healthcheck path
|
| 25 |
+
|
| 26 |
+
Every route requires basic auth, so `/api/health` returns `401` to an
|
| 27 |
+
unauthenticated prober and Railway would mark the deploy failed. Leave
|
| 28 |
+
`healthcheckPath` unset and let Railway consider the service live once the
|
| 29 |
+
process is listening.
|
| 30 |
+
|
| 31 |
+
## Getting your code in
|
| 32 |
+
|
| 33 |
+
There is no repo in the container. From the app's terminal:
|
| 34 |
+
|
| 35 |
+
```bash
|
| 36 |
+
git clone https://github.com/you/your-repo.git
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
For pushes, add a deploy key or use a PAT. Anything outside `/data` is lost on
|
| 40 |
+
redeploy.
|
| 41 |
+
|
| 42 |
+
## Restarts kill in-flight runs
|
| 43 |
+
|
| 44 |
+
A redeploy or crash takes the container with it, including any agent run in
|
| 45 |
+
progress. Session state survives if it lives on the volume; the interrupted run
|
| 46 |
+
does not.
|
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Boot opencode's headless server for a PaaS (Hugging Face Spaces / Railway).
|
| 3 |
+
set -euo pipefail
|
| 4 |
+
|
| 5 |
+
# Allow `docker run <image> bash` etc. to override the server for debugging.
|
| 6 |
+
# PaaS hosts pass no arguments, so the normal path is unaffected.
|
| 7 |
+
if [ "$#" -gt 0 ]; then
|
| 8 |
+
exec "$@"
|
| 9 |
+
fi
|
| 10 |
+
|
| 11 |
+
# --- port -------------------------------------------------------------------
|
| 12 |
+
# Railway injects $PORT. HF Spaces does not; 7860 must match app_port in README.md.
|
| 13 |
+
PORT="${PORT:-7860}"
|
| 14 |
+
|
| 15 |
+
# --- refuse to run unauthenticated -----------------------------------------
|
| 16 |
+
# opencode itself only warns. On a public URL an unauthenticated server means
|
| 17 |
+
# anyone can run shell commands and read/write files in this container, so fail
|
| 18 |
+
# loudly instead.
|
| 19 |
+
if [ -z "${OPENCODE_SERVER_PASSWORD:-}" ]; then
|
| 20 |
+
cat >&2 <<'MSG'
|
| 21 |
+
FATAL: OPENCODE_SERVER_PASSWORD is not set.
|
| 22 |
+
|
| 23 |
+
Every route on this server allows shell execution and file access, and basic
|
| 24 |
+
auth is the only thing standing in front of it. Set the secret and redeploy:
|
| 25 |
+
|
| 26 |
+
Hugging Face Settings -> Variables and secrets -> New secret
|
| 27 |
+
Railway Variables -> New Variable
|
| 28 |
+
|
| 29 |
+
Log in as $OPENCODE_SERVER_USERNAME (default "opencode") with that password.
|
| 30 |
+
MSG
|
| 31 |
+
exit 1
|
| 32 |
+
fi
|
| 33 |
+
|
| 34 |
+
# --- persistence -----------------------------------------------------------
|
| 35 |
+
# opencode keeps sessions, provider credentials and its SQLite DB under the XDG
|
| 36 |
+
# dirs (packages/core/src/global.ts), so pointing those at a mounted volume is
|
| 37 |
+
# what makes state survive a restart.
|
| 38 |
+
# HF Spaces persistent storage is mounted at /data (paid add-on)
|
| 39 |
+
# Railway attach a volume with mount path /data
|
| 40 |
+
STATE_ROOT="${OPENCODE_STATE_ROOT:-/data}"
|
| 41 |
+
|
| 42 |
+
if [ -d "$STATE_ROOT" ] && [ -w "$STATE_ROOT" ]; then
|
| 43 |
+
export XDG_DATA_HOME="$STATE_ROOT/share"
|
| 44 |
+
export XDG_STATE_HOME="$STATE_ROOT/state"
|
| 45 |
+
export XDG_CACHE_HOME="$STATE_ROOT/cache"
|
| 46 |
+
export XDG_CONFIG_HOME="$STATE_ROOT/config"
|
| 47 |
+
WORKSPACE="${OPENCODE_WORKSPACE:-$STATE_ROOT/workspace}"
|
| 48 |
+
mkdir -p "$XDG_DATA_HOME" "$XDG_STATE_HOME" "$XDG_CACHE_HOME" "$XDG_CONFIG_HOME"
|
| 49 |
+
echo "persistence: $STATE_ROOT (sessions, logins and code survive restarts)"
|
| 50 |
+
else
|
| 51 |
+
WORKSPACE="${OPENCODE_WORKSPACE:-$HOME/workspace}"
|
| 52 |
+
echo "persistence: NONE — $STATE_ROOT is not a writable mount."
|
| 53 |
+
echo " Sessions, provider logins and uncommitted code are lost on restart."
|
| 54 |
+
echo " Push to git before you walk away, or attach a volume at $STATE_ROOT."
|
| 55 |
+
fi
|
| 56 |
+
|
| 57 |
+
mkdir -p "$WORKSPACE"
|
| 58 |
+
cd "$WORKSPACE"
|
| 59 |
+
|
| 60 |
+
# Volumes and bind mounts routinely carry a different owner than uid 1000,
|
| 61 |
+
# which makes git refuse to touch the repo. Trust what we already control.
|
| 62 |
+
git config --global --add safe.directory '*' 2>/dev/null || true
|
| 63 |
+
|
| 64 |
+
# --- extra CORS origins ----------------------------------------------------
|
| 65 |
+
# localhost, *.opencode.ai and the desktop app's oc://renderer are allowed out
|
| 66 |
+
# of the box (packages/server/src/cors.ts). Only a frontend you host yourself
|
| 67 |
+
# on some other domain needs this.
|
| 68 |
+
CORS_ARGS=()
|
| 69 |
+
if [ -n "${OPENCODE_CORS_ORIGINS:-}" ]; then
|
| 70 |
+
IFS=',' read -ra origins <<<"$OPENCODE_CORS_ORIGINS"
|
| 71 |
+
for origin in "${origins[@]}"; do
|
| 72 |
+
origin="$(echo "$origin" | xargs)"
|
| 73 |
+
[ -n "$origin" ] && CORS_ARGS+=(--cors "$origin")
|
| 74 |
+
done
|
| 75 |
+
fi
|
| 76 |
+
|
| 77 |
+
echo "opencode $(opencode --version) starting on 0.0.0.0:$PORT (workspace: $WORKSPACE)"
|
| 78 |
+
exec opencode serve --hostname 0.0.0.0 --port "$PORT" "${CORS_ARGS[@]}"
|
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://railway.com/railway.schema.json",
|
| 3 |
+
"build": {
|
| 4 |
+
"builder": "DOCKERFILE",
|
| 5 |
+
"dockerfilePath": "Dockerfile"
|
| 6 |
+
},
|
| 7 |
+
"deploy": {
|
| 8 |
+
"restartPolicyType": "ON_FAILURE",
|
| 9 |
+
"restartPolicyMaxRetries": 10,
|
| 10 |
+
"numReplicas": 1
|
| 11 |
+
}
|
| 12 |
+
}
|