From 2d74deb12e046751ffe1fe0ea19c096e011a9c9a Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Mon, 22 Jun 2026 01:53:26 +0000 Subject: [PATCH 1/9] feat: add Homebrew as built-in package manager - Install Homebrew to /home/linuxbrew/.linuxbrew (outside the persistent volume so it survives container restarts) - Add Homebrew bin/sbin dirs to PATH for opencode user - Include Homebrew paths in /etc/profile.d/node-path.sh so login shells also pick them up - Update README with Homebrew entry in the tools table and a runtime note about ephemeral brew packages --- Dockerfile | 9 +++++++-- README.md | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 20bbaab..2eae80f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -74,12 +74,17 @@ RUN echo 'opencode ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/opencode \ && chmod 0440 /etc/sudoers.d/opencode \ && visudo -cf /etc/sudoers.d/opencode +# Homebrew package manager for Linux — installed system-wide, not under the +# persistent home volume, so it survives container restarts. +RUN NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" \ + && chown -R opencode:opencode /home/linuxbrew + # `n` CLI for runtime Node version switches. RUN curl -fsSL -o /usr/local/bin/n https://raw.githubusercontent.com/tj/n/master/bin/n \ && chmod 0755 /usr/local/bin/n ARG NODE_PREFIX ENV N_PREFIX=${NODE_PREFIX} -ENV PATH=${N_PREFIX}/bin:/home/opencode/.local/bin:${PATH} +ENV PATH=${N_PREFIX}/bin:/home/opencode/.local/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:${PATH} # Toolchains from builder (no installer residue). COPY --from=builder --chown=opencode:opencode ${NODE_PREFIX} ${NODE_PREFIX} @@ -87,7 +92,7 @@ COPY --from=builder /opt/opencode /usr/local/bin/opencode RUN node --version && npm --version && opencode --version # Ensure login shells pick up NODE_PREFIX on PATH. -RUN printf 'export N_PREFIX=%s\nfor d in "$N_PREFIX/bin" "$HOME/.local/bin"; do case ":$PATH:" in *":$d:"*) ;; *) PATH="$d:$PATH";; esac; done\nexport PATH\n' "${N_PREFIX}" > /etc/profile.d/node-path.sh \ +RUN printf 'export N_PREFIX=%s\nfor d in "$N_PREFIX/bin" "$HOME/.local/bin" "/home/linuxbrew/.linuxbrew/bin" "/home/linuxbrew/.linuxbrew/sbin"; do case ":$PATH:" in *":$d:"*) ;; *) PATH="$d:$PATH";; esac; done\nexport PATH\n' "${N_PREFIX}" > /etc/profile.d/node-path.sh \ && chmod 0644 /etc/profile.d/node-path.sh COPY entrypoint.sh /usr/local/bin/entrypoint.sh diff --git a/README.md b/README.md index 3cebd5e..545b8e8 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ A general-purpose Ubuntu Docker image for running [opencode](https://opencode.ai | **Node.js** | Current LTS via `n`, installed to `/opt/n` (outside home) | | **Python 3** | pip, venv | | **Build tools** | `build-essential`, `pkg-config` (for native npm addons, pip source builds) | +| **Homebrew** | Linux-native Homebrew (`/home/linuxbrew/.linuxbrew`) — `brew` on PATH for all users | | **CLI utilities** | git, curl, wget, jq, ripgrep, fd-find, vim, nano, less, unzip, ssh client | | **Init** | tini as PID 1 (zombie reaping, clean shutdown) | @@ -82,3 +83,4 @@ Fetches the latest release from [anomalyco/opencode](https://github.com/anomalyc - The root filesystem is ephemeral; mount `/home/opencode` as the persistent volume for all user data (dotfiles, config, projects). The `~/workspace` subdirectory is the default workdir. - `~/.local/bin` is on PATH and user-writable, useful for dropping custom tools at runtime. - Node version can be switched at runtime with `n ` (e.g. `n lts`). +- Homebrew is installed under `/home/linuxbrew/.linuxbrew` (outside the persistent volume) and is usable immediately by the `opencode` user. Packages installed with `brew` are also ephemeral — they persist only for the container's lifetime. From d4dfb6811d8f34c7a4b6ad81743d73ddd0b6e630 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:13:37 +0000 Subject: [PATCH 2/9] fix: run Homebrew install as opencode user to avoid root bailout --- Dockerfile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2eae80f..659f0dc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -76,8 +76,11 @@ RUN echo 'opencode ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/opencode \ # Homebrew package manager for Linux — installed system-wide, not under the # persistent home volume, so it survives container restarts. -RUN NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" \ - && chown -R opencode:opencode /home/linuxbrew +# Run as the opencode user because the installer aborts when run as root +# (and during BuildKit builds it cannot detect it is inside a container). +RUN mkdir -p /home/linuxbrew \ + && chown opencode:opencode /home/linuxbrew \ + && su opencode -c 'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' # `n` CLI for runtime Node version switches. RUN curl -fsSL -o /usr/local/bin/n https://raw.githubusercontent.com/tj/n/master/bin/n \ From b170293a20499dad28b0dd64ba1ed7b3f98a7159 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:19:10 +0000 Subject: [PATCH 3/9] fix: strip homebrew-core tap, cache, and docs to reduce image size --- Dockerfile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 659f0dc..64a8957 100644 --- a/Dockerfile +++ b/Dockerfile @@ -80,7 +80,14 @@ RUN echo 'opencode ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/opencode \ # (and during BuildKit builds it cannot detect it is inside a container). RUN mkdir -p /home/linuxbrew \ && chown opencode:opencode /home/linuxbrew \ - && su opencode -c 'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' + && su opencode -c 'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' \ + && su opencode -c 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" \ + && brew cleanup --prune=all \ + && rm -rf "$(brew --cache)" \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Taps/homebrew/homebrew-core \ + && rm -rf /home/linuxbrew/.linuxbrew/share/man \ + && rm -rf /home/linuxbrew/.linuxbrew/share/doc \ + && rm -rf /home/linuxbrew/.linuxbrew/share/zsh' # `n` CLI for runtime Node version switches. RUN curl -fsSL -o /usr/local/bin/n https://raw.githubusercontent.com/tj/n/master/bin/n \ From 6133b9022497771c79c0fb861562f084685f932a Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:38:24 +0000 Subject: [PATCH 4/9] fix: install system Ruby to avoid Homebrew's vendored portable Ruby, more aggressive cleanup --- Dockerfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 64a8957..e0b4f68 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,7 +54,7 @@ ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates curl wget git openssh-client unzip xz-utils \ build-essential pkg-config \ - python3 python3-pip python3-venv \ + python3 python3-pip python3-venv ruby \ ripgrep fd-find jq less nano vim-tiny \ sudo \ tini \ @@ -85,6 +85,10 @@ RUN mkdir -p /home/linuxbrew \ && brew cleanup --prune=all \ && rm -rf "$(brew --cache)" \ && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Taps/homebrew/homebrew-core \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/test \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/bundle/ruby/*/cache \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/bundle/ruby/*/doc \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby \ && rm -rf /home/linuxbrew/.linuxbrew/share/man \ && rm -rf /home/linuxbrew/.linuxbrew/share/doc \ && rm -rf /home/linuxbrew/.linuxbrew/share/zsh' From 9485c3dd9f348ab49a73b375e5ba29bbe91a1c2a Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:45:28 +0000 Subject: [PATCH 5/9] refactor: move Homebrew install to builder stage, strip cask code --- Dockerfile | 47 +++++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/Dockerfile b/Dockerfile index e0b4f68..dd7b055 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,8 +8,9 @@ # shadowed when a volume mounts over the home directory. # # Multi-stage: the `builder` stage fetches relocatable toolchains (Node via `n`, -# the opencode binary) so installers and caches never land in the final image. -# The final stage carries only the runtime: apt dev packages + copied-in Node + opencode. +# the opencode binary, Homebrew) so installers and caches never land in the +# final image. The final stage carries only the runtime: apt dev packages + +# copied-in Node + opencode + Homebrew. ARG OPENCODE_VERSION=0.0.0 # Node lives OUTSIDE /home/opencode so it's never shadowed by a volume mount @@ -17,12 +18,12 @@ ARG OPENCODE_VERSION=0.0.0 ARG NODE_PREFIX=/opt/n # --------------------------------------------------------------------------- -# builder: fetch Node (via n) + the opencode binary +# builder: fetch Node (via n) + the opencode binary + Homebrew # --------------------------------------------------------------------------- FROM ubuntu:26.04 AS builder ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates curl xz-utils bash libatomic1 \ + ca-certificates curl xz-utils bash libatomic1 ruby \ && rm -rf /var/lib/apt/lists/* ARG NODE_PREFIX @@ -41,6 +42,24 @@ RUN curl -fsSL https://opencode.ai/install | VERSION="${OPENCODE_VERSION}" bash && chmod 0755 /opt/opencode \ && /opt/opencode --version +# Homebrew installed here so build-time tools never land in the final image +RUN useradd --create-home --shell /bin/bash opencode \ + && mkdir -p /home/linuxbrew \ + && chown opencode:opencode /home/linuxbrew \ + && su opencode -c 'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' \ + && su opencode -c 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" \ + && brew cleanup --prune=all \ + && rm -rf "$(brew --cache)" \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Taps/homebrew/homebrew-core \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/test \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/cask \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/bundle/ruby/*/cache \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/bundle/ruby/*/doc \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby \ + && rm -rf /home/linuxbrew/.linuxbrew/share/man \ + && rm -rf /home/linuxbrew/.linuxbrew/share/doc \ + && rm -rf /home/linuxbrew/.linuxbrew/share/zsh' + # --------------------------------------------------------------------------- # final: the sandbox runtime # --------------------------------------------------------------------------- @@ -74,24 +93,8 @@ RUN echo 'opencode ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/opencode \ && chmod 0440 /etc/sudoers.d/opencode \ && visudo -cf /etc/sudoers.d/opencode -# Homebrew package manager for Linux — installed system-wide, not under the -# persistent home volume, so it survives container restarts. -# Run as the opencode user because the installer aborts when run as root -# (and during BuildKit builds it cannot detect it is inside a container). -RUN mkdir -p /home/linuxbrew \ - && chown opencode:opencode /home/linuxbrew \ - && su opencode -c 'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' \ - && su opencode -c 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" \ - && brew cleanup --prune=all \ - && rm -rf "$(brew --cache)" \ - && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Taps/homebrew/homebrew-core \ - && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/test \ - && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/bundle/ruby/*/cache \ - && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/bundle/ruby/*/doc \ - && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby \ - && rm -rf /home/linuxbrew/.linuxbrew/share/man \ - && rm -rf /home/linuxbrew/.linuxbrew/share/doc \ - && rm -rf /home/linuxbrew/.linuxbrew/share/zsh' +# Homebrew from builder (no installer residue or macOS-only code). +COPY --from=builder --chown=opencode:opencode /home/linuxbrew /home/linuxbrew # `n` CLI for runtime Node version switches. RUN curl -fsSL -o /usr/local/bin/n https://raw.githubusercontent.com/tj/n/master/bin/n \ From e943212a0435b417904844746563aefe8cdabdbe Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:47:11 +0000 Subject: [PATCH 6/9] fix: add sudo to builder stage for Homebrew install --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index dd7b055..463cbf0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,7 +43,10 @@ RUN curl -fsSL https://opencode.ai/install | VERSION="${OPENCODE_VERSION}" bash && /opt/opencode --version # Homebrew installed here so build-time tools never land in the final image -RUN useradd --create-home --shell /bin/bash opencode \ +RUN apt-get update && apt-get install -y --no-install-recommends sudo \ + && useradd --create-home --shell /bin/bash opencode \ + && echo 'opencode ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/opencode \ + && chmod 0440 /etc/sudoers.d/opencode \ && mkdir -p /home/linuxbrew \ && chown opencode:opencode /home/linuxbrew \ && su opencode -c 'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' \ From c97c43cb2e2a2e4a902910c555edd2557602e3f3 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:52:08 +0000 Subject: [PATCH 7/9] fix: use sudo -u instead of su for Homebrew install in builder --- Dockerfile | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index 463cbf0..70ac90e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,19 +49,18 @@ RUN apt-get update && apt-get install -y --no-install-recommends sudo \ && chmod 0440 /etc/sudoers.d/opencode \ && mkdir -p /home/linuxbrew \ && chown opencode:opencode /home/linuxbrew \ - && su opencode -c 'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' \ - && su opencode -c 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" \ - && brew cleanup --prune=all \ - && rm -rf "$(brew --cache)" \ - && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Taps/homebrew/homebrew-core \ - && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/test \ - && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/cask \ - && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/bundle/ruby/*/cache \ - && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/bundle/ruby/*/doc \ - && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby \ - && rm -rf /home/linuxbrew/.linuxbrew/share/man \ - && rm -rf /home/linuxbrew/.linuxbrew/share/doc \ - && rm -rf /home/linuxbrew/.linuxbrew/share/zsh' + && sudo -u opencode NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" \ + && sudo -u opencode /home/linuxbrew/.linuxbrew/bin/brew cleanup --prune=all \ + && sudo -u opencode rm -rf "$(sudo -u opencode /home/linuxbrew/.linuxbrew/bin/brew --cache)" \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Taps/homebrew/homebrew-core \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/test \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/cask \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/bundle/ruby/*/cache \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/bundle/ruby/*/doc \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/vendor/portable-ruby \ + && rm -rf /home/linuxbrew/.linuxbrew/share/man \ + && rm -rf /home/linuxbrew/.linuxbrew/share/doc \ + && rm -rf /home/linuxbrew/.linuxbrew/share/zsh # --------------------------------------------------------------------------- # final: the sandbox runtime From b955bbaeec1b35ec4bee26f40ca521eef21dc660 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:56:22 +0000 Subject: [PATCH 8/9] refactor: three-stage build with shared base stage for builder and final --- Dockerfile | 92 +++++++++++++++++++++--------------------------------- 1 file changed, 35 insertions(+), 57 deletions(-) diff --git a/Dockerfile b/Dockerfile index 70ac90e..160e8b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,25 +7,46 @@ # lives under ~/workspace. Node lives in /opt/n (outside home) so it's never # shadowed when a volume mounts over the home directory. # -# Multi-stage: the `builder` stage fetches relocatable toolchains (Node via `n`, -# the opencode binary, Homebrew) so installers and caches never land in the -# final image. The final stage carries only the runtime: apt dev packages + -# copied-in Node + opencode + Homebrew. +# Three-stage build: +# base — apt packages, user, sudo, init — shared by builder and final +# builder — fetches relocatable toolchains (Node, opencode, Homebrew) +# final — copies in runtimes from builder; carries only runtime layers ARG OPENCODE_VERSION=0.0.0 -# Node lives OUTSIDE /home/opencode so it's never shadowed by a volume mount -# over the entire home directory. /opt/n stays on the ephemeral image rootfs. ARG NODE_PREFIX=/opt/n # --------------------------------------------------------------------------- -# builder: fetch Node (via n) + the opencode binary + Homebrew +# base: common runtime layer (apt, user, sudo, init) # --------------------------------------------------------------------------- -FROM ubuntu:26.04 AS builder +FROM ubuntu:26.04 AS base + ENV DEBIAN_FRONTEND=noninteractive + RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates curl xz-utils bash libatomic1 ruby \ + ca-certificates curl wget git openssh-client unzip xz-utils \ + build-essential pkg-config \ + python3 python3-pip python3-venv ruby \ + ripgrep fd-find jq less nano vim-tiny \ + sudo tini open-iscsi tzdata locales \ && rm -rf /var/lib/apt/lists/* +RUN userdel --remove ubuntu 2>/dev/null || true; \ + groupdel ubuntu 2>/dev/null || true; \ + groupadd --gid 1000 opencode \ + && useradd --uid 1000 --gid 1000 --create-home --shell /bin/bash opencode + +RUN echo 'opencode ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/opencode \ + && chmod 0440 /etc/sudoers.d/opencode \ + && visudo -cf /etc/sudoers.d/opencode + +COPY entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod 0755 /usr/local/bin/entrypoint.sh + +# --------------------------------------------------------------------------- +# builder: fetch Node, opencode, and Homebrew +# --------------------------------------------------------------------------- +FROM base AS builder + ARG NODE_PREFIX ENV N_PREFIX=${NODE_PREFIX} ENV PATH=${NODE_PREFIX}/bin:${PATH} @@ -42,12 +63,7 @@ RUN curl -fsSL https://opencode.ai/install | VERSION="${OPENCODE_VERSION}" bash && chmod 0755 /opt/opencode \ && /opt/opencode --version -# Homebrew installed here so build-time tools never land in the final image -RUN apt-get update && apt-get install -y --no-install-recommends sudo \ - && useradd --create-home --shell /bin/bash opencode \ - && echo 'opencode ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/opencode \ - && chmod 0440 /etc/sudoers.d/opencode \ - && mkdir -p /home/linuxbrew \ +RUN mkdir -p /home/linuxbrew \ && chown opencode:opencode /home/linuxbrew \ && sudo -u opencode NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" \ && sudo -u opencode /home/linuxbrew/.linuxbrew/bin/brew cleanup --prune=all \ @@ -63,65 +79,27 @@ RUN apt-get update && apt-get install -y --no-install-recommends sudo \ && rm -rf /home/linuxbrew/.linuxbrew/share/zsh # --------------------------------------------------------------------------- -# final: the sandbox runtime +# final: runtime image # --------------------------------------------------------------------------- -FROM ubuntu:26.04 - -ENV DEBIAN_FRONTEND=noninteractive +FROM base -# General-purpose toolchain: VCS, build tools, common languages + CLI utilities. -# `sudo` lets the sandbox user install system packages at runtime. -# `build-essential`/`pkg-config` support native npm addons and pip source builds. -RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates curl wget git openssh-client unzip xz-utils \ - build-essential pkg-config \ - python3 python3-pip python3-venv ruby \ - ripgrep fd-find jq less nano vim-tiny \ - sudo \ - tini \ - open-iscsi \ - tzdata locales \ - && rm -rf /var/lib/apt/lists/* - -# Unprivileged runtime user; uid/gid 1000. -RUN userdel --remove ubuntu 2>/dev/null || true; \ - groupdel ubuntu 2>/dev/null || true; \ - groupadd --gid 1000 opencode \ - && useradd --uid 1000 --gid 1000 --create-home --shell /bin/bash opencode - -# Passwordless sudo. The root filesystem is ephemeral — apt-installed packages -# are lost on container restart; only /home/opencode (the persistent mount) keeps data. -RUN echo 'opencode ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/opencode \ - && chmod 0440 /etc/sudoers.d/opencode \ - && visudo -cf /etc/sudoers.d/opencode - -# Homebrew from builder (no installer residue or macOS-only code). -COPY --from=builder --chown=opencode:opencode /home/linuxbrew /home/linuxbrew - -# `n` CLI for runtime Node version switches. -RUN curl -fsSL -o /usr/local/bin/n https://raw.githubusercontent.com/tj/n/master/bin/n \ - && chmod 0755 /usr/local/bin/n ARG NODE_PREFIX ENV N_PREFIX=${NODE_PREFIX} ENV PATH=${N_PREFIX}/bin:/home/opencode/.local/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:${PATH} -# Toolchains from builder (no installer residue). COPY --from=builder --chown=opencode:opencode ${NODE_PREFIX} ${NODE_PREFIX} COPY --from=builder /opt/opencode /usr/local/bin/opencode +COPY --from=builder --chown=opencode:opencode /home/linuxbrew /home/linuxbrew + RUN node --version && npm --version && opencode --version -# Ensure login shells pick up NODE_PREFIX on PATH. RUN printf 'export N_PREFIX=%s\nfor d in "$N_PREFIX/bin" "$HOME/.local/bin" "/home/linuxbrew/.linuxbrew/bin" "/home/linuxbrew/.linuxbrew/sbin"; do case ":$PATH:" in *":$d:"*) ;; *) PATH="$d:$PATH";; esac; done\nexport PATH\n' "${N_PREFIX}" > /etc/profile.d/node-path.sh \ && chmod 0644 /etc/profile.d/node-path.sh -COPY entrypoint.sh /usr/local/bin/entrypoint.sh -RUN chmod 0755 /usr/local/bin/entrypoint.sh - USER opencode ENV HOME=/home/opencode WORKDIR /home/opencode/workspace EXPOSE 4096 -# tini as PID 1 for zombie reaping and clean signal forwarding. ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"] From fb7389d46b953240fd289ca413461fad3ce5300b Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 18:57:28 +0000 Subject: [PATCH 9/9] docs: remove brew ephemeral note --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 545b8e8..b2cef5b 100644 --- a/README.md +++ b/README.md @@ -83,4 +83,4 @@ Fetches the latest release from [anomalyco/opencode](https://github.com/anomalyc - The root filesystem is ephemeral; mount `/home/opencode` as the persistent volume for all user data (dotfiles, config, projects). The `~/workspace` subdirectory is the default workdir. - `~/.local/bin` is on PATH and user-writable, useful for dropping custom tools at runtime. - Node version can be switched at runtime with `n ` (e.g. `n lts`). -- Homebrew is installed under `/home/linuxbrew/.linuxbrew` (outside the persistent volume) and is usable immediately by the `opencode` user. Packages installed with `brew` are also ephemeral — they persist only for the container's lifetime. +- Homebrew is installed under `/home/linuxbrew/.linuxbrew` (outside the persistent volume) and is usable immediately by the `opencode` user.