# opencode-devbox — variant image # # FROMs a base- image produced by Dockerfile.base and adds only # the variant-specific tools (opencode, pi, oh-my-opencode-slim, Go). # # The four published variants are produced from THIS Dockerfile by # varying build args: # # variant INSTALL_OPENCODE INSTALL_OMOS INSTALL_PI # ───────────────── ──────────────── ──────────── ────────── # base true false false # omos true true false # with-pi true false true # omos-with-pi true true true # # Pass `--build-arg BASE_IMAGE=:base-` to select the base. # The CI workflow computes the base hash from Dockerfile.base + rootfs/ # + entrypoint*.sh and feeds it in. # # IMPORTANT: the base image sets NPM_CONFIG_PREFIX to # /home/developer/.pi/npm-global so runtime `pi install npm:...` and # `npm install -g` by the developer user lands on the named volume. # At BUILD time we want the baked binaries on /usr so they survive the # volume mount. Each `npm install -g` below therefore prefixes the # command with `NPM_CONFIG_PREFIX=/usr`. ARG BASE_IMAGE FROM ${BASE_IMAGE} ARG TARGETARCH ARG USER_NAME=developer # ── Install opencode via npm ───────────────────────────────────────── # OPENCODE_VERSION is intentionally pinned in this Dockerfile (not # 'latest'). It drives the release tag and gets bumped via a source # edit, so the cache-hit class of bug that bit pi-devbox v0.74.0.. # v0.75.5 cannot apply here. ARG INSTALL_OPENCODE=true ARG OPENCODE_VERSION=1.15.12 RUN if [ "${INSTALL_OPENCODE}" = "true" ]; then \ NPM_CONFIG_PREFIX=/usr npm install -g opencode-ai@${OPENCODE_VERSION} && \ opencode --version ; \ fi # ── Optional: pi coding-agent ──────────────────────────────────────── # pi-toolkit and pi-extensions are cloned into /opt/. entrypoint-user.sh # runs each repo's install.sh on container start so symlinks land under # ~/.pi/agent/ on the named volume. # PI_VERSION should be passed explicitly by CI as a concrete version # (resolved from `npm view @earendil-works/pi-coding-agent version`, # see .gitea/workflows/docker-publish-split.yml § resolve-versions). # The default `latest` is for local dev convenience only — it has a # known cache-hit footgun when used in registry-cached CI builds: the # resulting build-arg string is byte-identical across builds, the # layer-hash is identical, and the registry buildcache silently reuses # the layer from whatever pi version was current when the cache was # first populated. Currently masked here because OPENCODE_VERSION (a # parent layer) bumps every release; will manifest the moment a # vN.N.Nb opencode-version-unchanged release ships. See pi-devbox # v0.75.5b 2026-05-23 for the discovery + canonical fix. ARG INSTALL_PI=false ARG PI_VERSION=latest ARG PI_TOOLKIT_REF=main ARG PI_EXTENSIONS_REF=main RUN if [ "${INSTALL_PI}" = "true" ]; then \ set -e && \ git_clone_retry() { \ url="$1"; ref="$2"; dest="$3"; \ for i in 1 2 3 4 5; do \ if git clone --depth 1 --branch "$ref" "$url" "$dest"; then return 0; fi; \ rm -rf "$dest"; \ echo "git clone $url failed (attempt $i/5), retrying in $((i*5))s..."; \ sleep $((i*5)); \ done; \ return 1; \ } && \ if [ "${PI_VERSION}" = "latest" ]; then \ NPM_CONFIG_PREFIX=/usr npm install -g @earendil-works/pi-coding-agent ; \ else \ NPM_CONFIG_PREFIX=/usr npm install -g @earendil-works/pi-coding-agent@${PI_VERSION} ; \ fi && \ pi --version && \ git_clone_retry https://gitea.jordbo.se/joakimp/pi-toolkit.git "${PI_TOOLKIT_REF}" /opt/pi-toolkit && \ git_clone_retry https://gitea.jordbo.se/joakimp/pi-extensions.git "${PI_EXTENSIONS_REF}" /opt/pi-extensions && \ echo "pi-toolkit at $(cd /opt/pi-toolkit && git rev-parse --short HEAD)" && \ echo "pi-extensions at $(cd /opt/pi-extensions && git rev-parse --short HEAD)" ; \ fi # ── Optional: Go ───────────────────────────────────────────────────── ARG INSTALL_GO=false ARG GO_VERSION=latest RUN if [ "${INSTALL_GO}" = "true" ]; then \ GOARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;; *) echo "amd64" ;; esac) && \ V="${GO_VERSION}" && \ if [ "$V" = "latest" ]; then \ V=$(curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://go.dev/dl/?mode=json" | \ awk -F'"' '/"version":/ { sub(/^go/,"",$4); print $4; exit }'); \ fi && \ [ -n "$V" ] && \ echo "Installing Go ${V}" && \ curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://go.dev/dl/go${V}.linux-${GOARCH}.tar.gz" | tar -C /usr/local -xz && \ ln -s /usr/local/go/bin/go /usr/local/bin/go && \ ln -s /usr/local/go/bin/gofmt /usr/local/bin/gofmt; \ fi # ── Optional: oh-my-opencode-slim (multi-agent orchestration) ──────── # Installs Bun runtime and the oh-my-opencode-slim npm package. # OMOS_VERSION shares the same cache-hit footgun as PI_VERSION when # left at the `latest` default in registry-cached CI builds. CI # resolves it via `npm view oh-my-opencode-slim version` and passes # the concrete value as a build-arg. See PI_VERSION block above. ARG INSTALL_OMOS=false ARG OMOS_VERSION=latest RUN if [ "${INSTALL_OMOS}" = "true" ]; then \ ARCH=$(uname -m) && \ if [ "$ARCH" = "x86_64" ]; then \ BUN_ARCH="x64-baseline"; \ elif [ "$ARCH" = "aarch64" ]; then \ BUN_ARCH="aarch64"; \ fi && \ curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/oven-sh/bun/releases/latest/download/bun-linux-${BUN_ARCH}.zip" -o /tmp/bun.zip && \ unzip -o /tmp/bun.zip -d /tmp/bun && \ mv /tmp/bun/bun-linux-${BUN_ARCH}/bun /usr/local/bin/bun && \ chmod +x /usr/local/bin/bun && \ ln -sf bun /usr/local/bin/bunx && \ rm -rf /tmp/bun /tmp/bun.zip && \ bun --version && \ test -L /usr/local/bin/bunx && \ NPM_CONFIG_PREFIX=/usr npm install -g oh-my-opencode-slim@${OMOS_VERSION}; \ fi # WORKDIR / ENTRYPOINT / CMD inherited from base.