Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

opencode-web

A containerized OpenCode web server: node:trixie-slim plus a curated toolchain (Homebrew, Docker CLI, Git, OpenTofu, gh, fj, kubectl, sops, age, Go, Java, build tools), running opencode web as a non-root user.

Built for self-hosters who want a persistent, browser-accessible OpenCode environment with the tooling an agent needs to do real infrastructure work.

Quick start

git clone https://github.com/Besi97/opencode-web
cd opencode-web
cp .env.example .env   # set GITHUB_TOKEN (and anything else you use)
docker compose --profile dind up -d

Open http://127.0.0.1:4096.

Security: the OpenCode web UI has no authentication. The default compose binds it to 127.0.0.1 only. To reach it remotely, put it behind an authenticating reverse proxy (or VPN) — do not expose the port directly.

What you get

Path Purpose
/home/node/projects Your repos / working files (volume, see below)
/home/node/.config/opencode OpenCode config (volume + read-only mounts from this repo)
./opencode.jsonc Global OpenCode config, mounted read-only
./instructions/*.md Global agent instructions, mounted read-only
./agents/, ./skills/ Your custom agents and skills, mounted read-only

State defaults to named volumes (opencode-projects, -config, -share, -cache). To keep projects in a host directory instead:

mkdir -p projects && sudo chown 1000:1000 projects
echo 'OPENCODE_PROJECTS_VOLUME=./projects' >> .env

Git authentication

The entrypoint configures credentials automatically at startup:

  • GITHUB_TOKEN set → runs gh auth setup-git (git push/pull against GitHub works out of the box)
  • FORGEJO_TOKEN + FORGEJO_URL + FORGEJO_USER set → configures the fj CLI and a git credential helper for that host

Set GIT_AUTHOR_NAME/GIT_AUTHOR_EMAIL (and committer equivalents) so agent commits are attributed correctly.

Signed commits (optional)

Point GPG_SIGNING_KEY_PATH at a private key mounted into the container. At startup the entrypoint imports it (gpg --batch --import) and sets user.signingkey plus commit.gpgsign=true globally, so every agent commit is signed. Unset, or unreadable, and the container just starts unsigned.

# On the host: export the secret key. A subkey-only export (--export-secret-subkeys)
# is fine: gpg resolves the signing subkey itself, so the stubbed primary never needs
# its secret material in the container.
gpg --armor --export-secret-subkeys <key-id> > gpg_signing_key.asc
chown 1000:1000 gpg_signing_key.asc && chmod 600 gpg_signing_key.asc

echo 'GPG_SIGNING_KEY_PATH=/run/secrets/gpg_signing_key' >> .env
# and uncomment the ./gpg_signing_key.asc volume line in docker-compose.yaml

Passphrase-protected keys work too. Set GPG_SIGNING_KEY_PASSPHRASE and the entrypoint points git at gpg-loopback.sh, which hands the passphrase to gpg on a pipe (--passphrase-fd 3) rather than prompting or putting it on argv, where every process in the container could read it. The key imports fine without the passphrase - it only fails later, at signing time, with signing failed: Inappropriate ioctl for device, so that error means the variable is missing or wrong. It remains visible in docker inspect and to anything the agent runs, which is the usual argument for a dedicated signing key rather than your personal one.

Match the identity. Forges show a commit as Verified only when the key's UID email matches the commit author. GIT_AUTHOR_EMAIL/GIT_COMMITTER_EMAIL are env-driven and override git config, so set them to the same address the key was made for.

~/.gnupg is not a volume, so the key is re-imported on each start (re-importing the same key is a no-op) and signing works in every repository the agent touches.

Docker-in-Docker

The container ships the Docker CLI but no daemon. Two options:

  1. Sidecar (default): run with --profile dind — a docker:dind service is reachable at tcp://docker-in-docker:2375 (unencrypted; it stays on the compose network).
  2. Mounted socket: set DOCKER_HOST= (empty) in .env and add a volume for your host socket.

Skills and agents

This repo intentionally ships agents/ and skills/ empty. Put your own content there (any layout OpenCode accepts), or sync it from wherever you manage it — a git submodule, a CI/deploy-time fetch step, manual copies. The container reads them at startup; it does not care how they got there.

Optional: Google Stitch MCP

opencode.jsonc includes a disabled remote MCP entry for Stitch. Set STITCH_API_KEY in .env and flip "enabled": true to activate it.

Configuration reference

All variables are documented in .env.example.

Image

Published to ghcr.io/besi97/opencode-web (public, no login needed). Tags mirror the bundled OpenCode version:

Tag Meaning
1.18.27 Built with OpenCode 1.18.27 (published automatically when the pin lands on main; never moves after publication)
v1.18.27 Same, with v prefix
latest Tip of main
sha-<full-commit> Immutable per-commit build
1.18.27-rev1 Rebuild of the same OpenCode version (new toolchain, same pin). CI creates v1.18.27-rev1 git tags and matching image tags automatically when a main merge doesn't change the pin; you can also push such a tag manually

Multi-arch: linux/amd64, linux/arm64 (arm64 is best-effort via emulation).

Versions & updates

The OpenCode version is pinned in the Dockerfile (ARG OPENCODE_VERSION), so builds are reproducible and every image tag truthfully names what is inside:

docker run --rm ghcr.io/besi97/opencode-web:latest --version   # prints the pinned version
cat /etc/opencode-web-versions                                  # inside the container
# or: docker inspect → label dev.opencode-web.opencode-version

Keeping things current:

  • OpenCode — update PRs bump the pinned ARG; merging to main publishes a new :<version> tag automatically (the workflow creates the matching git release tag). Merges that don't change the pin (e.g. base image updates) publish :<version>-rev<N> instead, so every version tag keeps describing exactly one image.
  • Base image (node:trixie-slim) and CI actions — Dependabot.
  • Other brew tools (gh, kubectl, sops, ...) — resolved by Homebrew at build time; latest tracks them, version tags do not promise anything about them.

Build locally:

docker build -t opencode-web .

Repo layout

Dockerfile                      # image definition
entrypoint.sh                   # git auth + optional commit signing, then exec opencode
forgejo-credential-helper.sh    # git credential helper (env-var driven)
gpg-loopback.sh                 # git gpg.program wrapper for passphrase-protected keys
docker-compose.yaml             # reference deployment
opencode.jsonc                  # default global config (mounted read-only)
instructions/                   # global agent instructions (mounted read-only)
agents/  skills/                # your customizations (mounted read-only)
.github/dependabot.yml          # base image + CI action updates
.github/workflows/build.yml     # PR smoke build (never pushes)
.github/workflows/publish.yml   # build + push to ghcr.io, version-mirrored tags

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages