Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
# VCS
.git
.gitignore
.dockerignore
Dockerfile

# Docs / meta (not needed in the image)
README.md
node_modules
**/.DS_Store
LICENSE

# Local-only env files (never bake into image)
.env
**/.env
**/.env.*

# Editor / OS noise
.idea
.vscode
*.swp
*.swo
**/.DS_Store
Thumbs.db

# Build artifacts / logs
node_modules
*.log
17 changes: 9 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ RUN install -d -m 0755 /out/etc/apt/keyrings \
FROM debian:bookworm-slim AS runtime

ARG NVM_VERSION=v0.40.3
ARG NODE_VERSION=22.11.0
ARG NODE_VERSION=22.20.0
ARG USER_UID=1000
ARG USER_GID=1000

Expand Down Expand Up @@ -110,14 +110,14 @@ RUN groupadd --gid ${USER_GID} developer \
&& echo 'developer ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/developer \
&& chmod 0440 /etc/sudoers.d/developer \
&& install -d -m 0755 -o developer -g developer \
/workspace \
/home/developer/dev \
/home/developer/.opencode \
/home/developer/.opencode/bin \
/home/developer/.local \
/home/developer/.local/share \
/home/developer/.config \
/home/developer/.config/opencode \
&& ln -s /workspace/.opencode /home/developer/.local/share/opencode \
&& ln -s /home/developer/dev/.opencode /home/developer/.local/share/opencode \
&& chown -h developer:developer /home/developer/.local/share/opencode

COPY --from=downloader --chown=developer:developer \
Expand Down Expand Up @@ -146,15 +146,16 @@ COPY --chown=developer:developer \
opencode-user-config.json \
/home/developer/.config/opencode/opencode.json

# Tiny entrypoint that mkdir's /workspace/.opencode at runtime so a single
# Railway Volume mounted at /workspace persists both projects and OpenCode
# session/auth data (~/.local/share/opencode is symlinked into it).
# Tiny entrypoint that mkdir's ~/dev/.opencode at runtime so a single
# Railway Volume mounted at ~/dev persists projects + OpenCode session/auth
# data together (~/.local/share/opencode is symlinked into it).
COPY --chmod=0755 docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh

# No VOLUME directive — Railway rejects them. Attach a Railway Volume at
# /workspace via the dashboard for persistence.
# /home/developer/dev (~/dev) via the dashboard for persistence; both
# projects you clone there and OpenCode session/auth data live in it.
EXPOSE 4096
WORKDIR /workspace
WORKDIR /home/developer/dev

# PORT lets PaaS platforms (Railway/Fly/Render) assign a port; falls back
# to 4096 locally.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ Self-hosted [OpenCode](https://opencode.ai) web UI in a Docker image, ready to d
- [Context7](https://context7.com) → `CONTEXT7_API_KEY`
- [GitHub MCP](https://github.com/github/github-mcp-server) → `GITHUB_MCP_TOKEN`
- [Sentry MCP](https://mcp.sentry.dev) → no env var
- Non-root `developer` user. Mount a single persistent volume at `/workspace` to keep your projects **and** OpenCode session/auth data across redeploys — `~/.local/share/opencode` is symlinked into `/workspace/.opencode`.
- Non-root `developer` user. OpenCode starts in `~/dev`. Mount a single persistent volume at `~/dev` (= `/home/developer/dev`) to keep your projects **and** OpenCode session/auth data across redeploys — `~/.local/share/opencode` is symlinked into `~/dev/.opencode`.

## Deploy on Railway

1. Push this repo to GitHub.
2. Railway: **New Project → Deploy from GitHub repo**.
3. **Variables** tab: set `OPENCODE_SERVER_PASSWORD` and at least one LLM provider key.
4. (Optional) Add a **Volume** mounted at `/workspace` so projects and OpenCode session history survive redeploys (sessions live at `/workspace/.opencode` via a symlink one volume covers both).
4. (Optional) Add a **Volume** mounted at `/home/developer/dev` so projects you clone and OpenCode session history both survive redeploys (sessions live at `~/dev/.opencode` via a symlink, so one volume covers both).
5. **Settings → Networking → Generate Domain**, open it, sign in as `opencode` with the password from step 3.

## Environment variables
Expand Down Expand Up @@ -46,5 +46,5 @@ Open <http://localhost:4096>.

## Notes

- Override Node at build time: `docker build --build-arg NODE_VERSION=20.18.0 -t my-opencode .`
- Override Node at build time: `docker build --build-arg NODE_VERSION=22.20.0 -t my-opencode .`
- Python isn't installed. If an npm package needs `node-gyp`, install on the fly inside an OpenCode bash session: `sudo apt-get install -y python3`.
39 changes: 20 additions & 19 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,37 +1,38 @@
#!/bin/sh
set -e

# A fresh Railway Volume mounted at /workspace lands owned by root, so
# fix it up to belong to the running user before we try to write to it.
DEV_DIR="${HOME:-/home/developer}/dev"

# A fresh Railway Volume mounted at ~/dev lands owned by root, so fix it
# up to belong to the running user before we try to write to it.
# Passwordless sudo is configured for the developer user specifically
# so this single chown can succeed without further setup.
if [ ! -w /workspace ]; then
sudo chown "$(id -u):$(id -g)" /workspace || {
echo "ERROR: /workspace is not writable and chown failed." >&2
if [ ! -w "$DEV_DIR" ]; then
sudo chown "$(id -u):$(id -g)" "$DEV_DIR" || {
echo "ERROR: $DEV_DIR is not writable and chown failed." >&2
echo "Check the volume's mount permissions in your platform's dashboard." >&2
exit 1
}
fi

# Ensure OpenCode's session/auth dir exists in the workspace. The image
# symlinks ~/.local/share/opencode -> /workspace/.opencode so a single
# Railway Volume on /workspace persists projects + session history.
mkdir -p /workspace/.opencode
# Ensure OpenCode's session/auth dir exists in the dev volume. The image
# symlinks ~/.local/share/opencode -> ~/dev/.opencode so a single Railway
# Volume mounted at ~/dev persists projects + session history together.
mkdir -p "$DEV_DIR/.opencode"

# OpenCode picks the "worktree" (project root) by walking up from cwd
# looking for a .git directory. With no .git ancestor it falls back to /
# and refuses to write there ("the default working directory (/) doesn't
# allow writing"). Init /workspace as a git repo so opencode anchors on
# /workspace as the worktree. Skipped if a previous deploy already set
# this up.
if [ ! -d /workspace/.git ]; then
git init -q /workspace
git -C /workspace config user.email "developer@my-opencode.local"
git -C /workspace config user.name "Developer"
# allow writing"). Init ~/dev as a git repo so opencode anchors there as
# the worktree. Skipped if a previous deploy already set this up.
if [ ! -d "$DEV_DIR/.git" ]; then
git init -q "$DEV_DIR"
git -C "$DEV_DIR" config user.email "developer@my-opencode.local"
git -C "$DEV_DIR" config user.name "Developer"
fi

# Pin cwd to /workspace too — Railway can start the container from /
# regardless of the Dockerfile's WORKDIR.
cd /workspace
# Pin cwd to ~/dev — Railway can start the container from / regardless
# of the Dockerfile's WORKDIR.
cd "$DEV_DIR"

exec "$@"
2 changes: 1 addition & 1 deletion opencode-user-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"Authorization": "Bearer {env:GITHUB_MCP_TOKEN}"
}
},
"Sentry": {
"sentry": {
"type": "remote",
"url": "https://mcp.sentry.dev/mcp",
"headers": {}
Expand Down