From c78500fa86fbb3e85022b672053a87b04d0cdd87 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 19:45:20 +0000 Subject: [PATCH 01/24] feat: replace custom auto-install shim with mise for on-demand tools - Add mise config (mise-config.toml) with pre-approved tools via brew backend - Install mise in builder stage, pre-install tools, copy to final - Set MISE_DATA_DIR=/opt/mise (outside home, not shadowed by volume) - Enable always_install setting for auto-install on first use - Add mise activation to .bashrc - Remove old brew-auto-install.conf and brew-install-handler.sh - Remove gh from apt install (now managed by mise via brew) --- Dockerfile | 33 +++++++++++++++++++++++---------- README.md | 6 ++++-- mise-config.toml | 5 +++++ 3 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 mise-config.toml diff --git a/Dockerfile b/Dockerfile index a52afad..39a0b8e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,19 +23,12 @@ FROM ubuntu:26.04 AS base ENV DEBIAN_FRONTEND=noninteractive # General dev toolchain: VCS, build tools, languages, CLI utilities. -# Also installs GitHub CLI via its official apt repo. 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 \ - && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ - | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \ - && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ - > /etc/apt/sources.list.d/github-cli.list \ - && apt-get update \ - && apt-get install -y --no-install-recommends gh \ && rm -rf /var/lib/apt/lists/* \ && userdel --remove ubuntu 2>/dev/null || true; \ groupdel ubuntu 2>/dev/null || true; \ @@ -68,7 +61,7 @@ RUN mkdir -p /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 \ && 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/Taps/homebrew/homebrew-core/.git \ && 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 \ @@ -78,6 +71,17 @@ RUN mkdir -p /home/linuxbrew \ && rm -rf /home/linuxbrew/.linuxbrew/share/doc \ && rm -rf /home/linuxbrew/.linuxbrew/share/zsh +# 1.5. mise — dev tool manager for pre-approved tools (gh, glab, n, node) +# Tools install via Homebrew backend so they live in the brew prefix. +RUN curl -fsSL https://mise.run | MISE_INSTALL_DIR=/usr/local/bin sh \ + && mkdir -p /opt/mise /etc/mise +COPY mise-config.toml /etc/mise/config.toml +RUN MISE_DATA_DIR=/opt/mise MISE_GLOBAL_CONFIG_FILE=/etc/mise/config.toml \ + mise settings set always_install true \ + && MISE_DATA_DIR=/opt/mise MISE_GLOBAL_CONFIG_FILE=/etc/mise/config.toml \ + mise install \ + && rm -rf /tmp/mise* + # 2. Node.js via `n` — changes when the upstream LTS version bumps RUN curl -fsSL -o /usr/local/bin/n https://raw.githubusercontent.com/tj/n/master/bin/n \ && chmod 0755 /usr/local/bin/n \ @@ -102,6 +106,9 @@ FROM base 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} +ENV HOMEBREW_NO_AUTO_UPDATE=1 +ENV MISE_DATA_DIR=/opt/mise +ENV MISE_GLOBAL_CONFIG_FILE=/etc/mise/config.toml # Runtimes copied from builder (most-stable first so frequent version # bumps don't invalidate cache for the other layers). @@ -109,10 +116,16 @@ COPY --from=builder --chown=opencode:opencode /home/linuxbrew /home/linuxbrew COPY --from=builder --chown=opencode:opencode ${NODE_PREFIX} ${NODE_PREFIX} COPY --from=builder /opt/opencode /usr/local/bin/opencode -# Verify runtimes and set up login-shell PATH +# Mise — dev tool manager; auto-installs tools defined in the global config. +COPY --from=builder /usr/local/bin/mise /usr/local/bin/mise +COPY --from=builder --chown=opencode:opencode /opt/mise /opt/mise +COPY --from=builder /etc/mise/config.toml /etc/mise/config.toml + +# Verify runtimes and set up login-shell PATH and auto-install handler RUN node --version && npm --version && opencode --version \ && 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 + && chmod 0644 /etc/profile.d/node-path.sh \ + && printf '\neval "$(mise activate bash)"\n' >> /home/opencode/.bashrc USER opencode ENV HOME=/home/opencode diff --git a/README.md b/README.md index 71707f3..9bc0664 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,9 @@ 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, gh (GitHub CLI), jq, ripgrep, fd-find, vim, nano, less, unzip, ssh client | +| **Homebrew** | Linux-native Homebrew (`/home/linuxbrew/.linuxbrew`) — `brew` on PATH | +| **mise** | Dev tool manager (`mise activate bash`); pre-approved tools (gh, glab, n, node) auto-install via `brew` backend on first use | +| **CLI utilities** | git, curl, wget, jq, ripgrep, fd-find, vim, nano, less, unzip, ssh client | | **Init** | tini as PID 1 (zombie reaping, clean shutdown) | ## Usage @@ -84,3 +85,4 @@ Fetches the latest release from [anomalyco/opencode](https://github.com/anomalyc - `~/.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. +- **mise auto-install**: [mise](https://mise.jdx.dev) is activated on shell start. Pre-approved tools (gh, glab, n, node) defined in `/etc/mise/config.toml` auto-install via the `brew` backend on first invocation. Add more tools to the config to extend the list. diff --git a/mise-config.toml b/mise-config.toml new file mode 100644 index 0000000..ce74bb9 --- /dev/null +++ b/mise-config.toml @@ -0,0 +1,5 @@ +[tools] +"brew:gh" = "latest" +"brew:glab" = "latest" +"brew:n" = "latest" +"brew:node" = "latest" From f8336f96531326f69b25b48d5406692a3d8f872b Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 19:56:08 +0000 Subject: [PATCH 02/24] fix: use correct MISE_INSTALL_PATH env var, pre-install gh/glab via brew as opencode user - MISE_INSTALL_PATH (not MISE_INSTALL_DIR) is the correct env var - Pre-install gh/glab via sudo -u opencode brew instead of mise install (builder runs as root, brew is owned by opencode) - Remove mise settings set; use MISE_ALWAYS_INSTALL=1 env var instead - Add HOMEBREW_NO_AUTO_UPDATE=1 to brew install in builder --- Dockerfile | 16 ++++++++-------- README.md | 17 +++++++++++++++-- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 39a0b8e..1f7811c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -71,16 +71,15 @@ RUN mkdir -p /home/linuxbrew \ && rm -rf /home/linuxbrew/.linuxbrew/share/doc \ && rm -rf /home/linuxbrew/.linuxbrew/share/zsh -# 1.5. mise — dev tool manager for pre-approved tools (gh, glab, n, node) -# Tools install via Homebrew backend so they live in the brew prefix. -RUN curl -fsSL https://mise.run | MISE_INSTALL_DIR=/usr/local/bin sh \ +# 1.5. mise — dev tool manager; pre-approved tools defined in the global config +# auto-install via Homebrew backend on first use at runtime. +RUN curl -fsSL https://mise.run | MISE_INSTALL_PATH=/usr/local/bin/mise sh \ && mkdir -p /opt/mise /etc/mise COPY mise-config.toml /etc/mise/config.toml -RUN MISE_DATA_DIR=/opt/mise MISE_GLOBAL_CONFIG_FILE=/etc/mise/config.toml \ - mise settings set always_install true \ - && MISE_DATA_DIR=/opt/mise MISE_GLOBAL_CONFIG_FILE=/etc/mise/config.toml \ - mise install \ - && rm -rf /tmp/mise* +# Pre-install tools via Homebrew (as opencode user) so mise finds them at +# runtime. n and node are already installed by step 2 below. +RUN sudo -u opencode HOMEBREW_NO_AUTO_UPDATE=1 \ + /home/linuxbrew/.linuxbrew/bin/brew install gh glab # 2. Node.js via `n` — changes when the upstream LTS version bumps RUN curl -fsSL -o /usr/local/bin/n https://raw.githubusercontent.com/tj/n/master/bin/n \ @@ -109,6 +108,7 @@ ENV PATH=${N_PREFIX}/bin:/home/opencode/.local/bin:/home/linuxbrew/.linuxbrew/bi ENV HOMEBREW_NO_AUTO_UPDATE=1 ENV MISE_DATA_DIR=/opt/mise ENV MISE_GLOBAL_CONFIG_FILE=/etc/mise/config.toml +ENV MISE_ALWAYS_INSTALL=1 # Runtimes copied from builder (most-stable first so frequent version # bumps don't invalidate cache for the other layers). diff --git a/README.md b/README.md index 9bc0664..2e58fe1 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,23 @@ A general-purpose Ubuntu Docker image for running [opencode](https://opencode.ai | **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 | -| **mise** | Dev tool manager (`mise activate bash`); pre-approved tools (gh, glab, n, node) auto-install via `brew` backend on first use | +| **mise** | Dev tool manager — tools listed below install on first use via `brew` backend | | **CLI utilities** | git, curl, wget, jq, ripgrep, fd-find, vim, nano, less, unzip, ssh client | | **Init** | tini as PID 1 (zombie reaping, clean shutdown) | +### Lazy-installed tools + +These tools install on first use (via mise → Homebrew): + +| Tool | Command | Backend | +|---|---|---| +| GitHub CLI | `gh` | brew | +| GitLab CLI | `glab` | brew | + +Add more tools to `/etc/mise/config.toml` to extend the list. + +> **Note:** `n` and `node` are also listed in the mise config but are pre-installed in the image — they're included so mise can manage versions if you switch them at runtime. + ## Usage ### Quick start @@ -85,4 +98,4 @@ Fetches the latest release from [anomalyco/opencode](https://github.com/anomalyc - `~/.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. -- **mise auto-install**: [mise](https://mise.jdx.dev) is activated on shell start. Pre-approved tools (gh, glab, n, node) defined in `/etc/mise/config.toml` auto-install via the `brew` backend on first invocation. Add more tools to the config to extend the list. +- **Lazy-installed tools** (see table above): run `gh`, `glab`, `n`, or `node` and they install on first use via mise → Homebrew. Edit `/etc/mise/config.toml` to add more auto-install tools. From 3c9f88f9b99b0b3c9060514a0d0a2c5ffec4a889 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 19:59:30 +0000 Subject: [PATCH 03/24] fix: use HOMEBREW_INSTALL_FROM_API=1, strip entire core tap - HOMEBREW_INSTALL_FROM_API=1 lets brew install any formula via JSON API without needing a local tap, so we can strip the entire homebrew-core (saves ~20MB) - brew install at runtime works for any formula, including ones added to homebrew-core after the image was built - No git operations needed, so .git removal is no longer relevant --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1f7811c..71803e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,7 +61,7 @@ RUN mkdir -p /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 \ && 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/.git \ + && 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 \ @@ -78,7 +78,7 @@ RUN curl -fsSL https://mise.run | MISE_INSTALL_PATH=/usr/local/bin/mise sh \ COPY mise-config.toml /etc/mise/config.toml # Pre-install tools via Homebrew (as opencode user) so mise finds them at # runtime. n and node are already installed by step 2 below. -RUN sudo -u opencode HOMEBREW_NO_AUTO_UPDATE=1 \ +RUN sudo -u opencode HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_INSTALL_FROM_API=1 \ /home/linuxbrew/.linuxbrew/bin/brew install gh glab # 2. Node.js via `n` — changes when the upstream LTS version bumps @@ -106,6 +106,7 @@ 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} ENV HOMEBREW_NO_AUTO_UPDATE=1 +ENV HOMEBREW_INSTALL_FROM_API=1 ENV MISE_DATA_DIR=/opt/mise ENV MISE_GLOBAL_CONFIG_FILE=/etc/mise/config.toml ENV MISE_ALWAYS_INSTALL=1 From 1aec935f5211817b66f29f303735d24b229252ee Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 20:00:52 +0000 Subject: [PATCH 04/24] fix: restore full homebrew-core tap so brew update works at runtime - Keep homebrew-core with .git intact (brew update requires it) - Remove HOMEBREW_INSTALL_FROM_API=1 (no longer needed with local tap) - Keep other size optimizations (test, cask, portable-ruby, gem cache) --- Dockerfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 71803e8..e29a65e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,7 +61,6 @@ RUN mkdir -p /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 \ && 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 \ @@ -78,7 +77,7 @@ RUN curl -fsSL https://mise.run | MISE_INSTALL_PATH=/usr/local/bin/mise sh \ COPY mise-config.toml /etc/mise/config.toml # Pre-install tools via Homebrew (as opencode user) so mise finds them at # runtime. n and node are already installed by step 2 below. -RUN sudo -u opencode HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_INSTALL_FROM_API=1 \ +RUN sudo -u opencode HOMEBREW_NO_AUTO_UPDATE=1 \ /home/linuxbrew/.linuxbrew/bin/brew install gh glab # 2. Node.js via `n` — changes when the upstream LTS version bumps @@ -106,7 +105,6 @@ 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} ENV HOMEBREW_NO_AUTO_UPDATE=1 -ENV HOMEBREW_INSTALL_FROM_API=1 ENV MISE_DATA_DIR=/opt/mise ENV MISE_GLOBAL_CONFIG_FILE=/etc/mise/config.toml ENV MISE_ALWAYS_INSTALL=1 From 493299fc62054712b0312153b598d968cec786bc Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 20:01:58 +0000 Subject: [PATCH 05/24] fix: re-enable HOMEBREW_INSTALL_FROM_API=1, strip core tap With HOMEBREW_INSTALL_FROM_API=1: - brew update downloads API JSON (fast, no git needed) - brew install fetches formula metadata from API cache - brew install works for any formula immediately - Homebrew-core tap can be stripped entirely (saves ~100MB) This is actually better than keeping the local tap. --- Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index e29a65e..01d4645 100644 --- a/Dockerfile +++ b/Dockerfile @@ -68,7 +68,8 @@ RUN mkdir -p /home/linuxbrew \ && 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 + && rm -rf /home/linuxbrew/.linuxbrew/share/zsh \ + && rm -rf /home/linuxbrew/.linuxbrew/Homebrew/Library/Taps/homebrew/homebrew-core # 1.5. mise — dev tool manager; pre-approved tools defined in the global config # auto-install via Homebrew backend on first use at runtime. @@ -77,7 +78,7 @@ RUN curl -fsSL https://mise.run | MISE_INSTALL_PATH=/usr/local/bin/mise sh \ COPY mise-config.toml /etc/mise/config.toml # Pre-install tools via Homebrew (as opencode user) so mise finds them at # runtime. n and node are already installed by step 2 below. -RUN sudo -u opencode HOMEBREW_NO_AUTO_UPDATE=1 \ +RUN sudo -u opencode HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_INSTALL_FROM_API=1 \ /home/linuxbrew/.linuxbrew/bin/brew install gh glab # 2. Node.js via `n` — changes when the upstream LTS version bumps @@ -105,6 +106,7 @@ 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} ENV HOMEBREW_NO_AUTO_UPDATE=1 +ENV HOMEBREW_INSTALL_FROM_API=1 ENV MISE_DATA_DIR=/opt/mise ENV MISE_GLOBAL_CONFIG_FILE=/etc/mise/config.toml ENV MISE_ALWAYS_INSTALL=1 From d2df34c8f0c320f05a1e5c1e5ec84e092b5f0cd1 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 20:11:13 +0000 Subject: [PATCH 06/24] feat: move ruby, jq, ripgrep, fd, wget, vim, nano to lazy install via mise - Remove ruby, wget, jq, ripgrep, fd-find, vim-tiny, nano from apt - Keep portable-ruby (brew needs it with system ruby removed) - Move brew install gh glab before stripping homebrew-core tap - Add all moved tools to mise-config.toml - Update README tables and notes --- Dockerfile | 14 +++++--------- README.md | 17 ++++++++++++----- mise-config.toml | 7 +++++++ 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index 01d4645..b399575 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,11 +24,10 @@ ENV DEBIAN_FRONTEND=noninteractive # General dev toolchain: VCS, build tools, languages, CLI utilities. RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates curl wget git openssh-client unzip xz-utils \ + ca-certificates curl 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 \ + python3 python3-pip python3-venv \ + less sudo tini open-iscsi tzdata locales \ && rm -rf /var/lib/apt/lists/* \ && userdel --remove ubuntu 2>/dev/null || true; \ groupdel ubuntu 2>/dev/null || true; \ @@ -61,11 +60,12 @@ RUN mkdir -p /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 \ && sudo -u opencode rm -rf "$(sudo -u opencode /home/linuxbrew/.linuxbrew/bin/brew --cache)" \ + && sudo -u opencode HOMEBREW_NO_AUTO_UPDATE=1 \ + /home/linuxbrew/.linuxbrew/bin/brew install gh glab \ && 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 \ @@ -76,10 +76,6 @@ RUN mkdir -p /home/linuxbrew \ RUN curl -fsSL https://mise.run | MISE_INSTALL_PATH=/usr/local/bin/mise sh \ && mkdir -p /opt/mise /etc/mise COPY mise-config.toml /etc/mise/config.toml -# Pre-install tools via Homebrew (as opencode user) so mise finds them at -# runtime. n and node are already installed by step 2 below. -RUN sudo -u opencode HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_INSTALL_FROM_API=1 \ - /home/linuxbrew/.linuxbrew/bin/brew install gh glab # 2. Node.js via `n` — changes when the upstream LTS version bumps RUN curl -fsSL -o /usr/local/bin/n https://raw.githubusercontent.com/tj/n/master/bin/n \ diff --git a/README.md b/README.md index 2e58fe1..1c42692 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ A general-purpose Ubuntu Docker image for running [opencode](https://opencode.ai | **Build tools** | `build-essential`, `pkg-config` (for native npm addons, pip source builds) | | **Homebrew** | Linux-native Homebrew (`/home/linuxbrew/.linuxbrew`) — `brew` on PATH | | **mise** | Dev tool manager — tools listed below install on first use via `brew` backend | -| **CLI utilities** | git, curl, wget, jq, ripgrep, fd-find, vim, nano, less, unzip, ssh client | +| **CLI utilities** | git, curl, less, unzip, ssh client | | **Init** | tini as PID 1 (zombie reaping, clean shutdown) | ### Lazy-installed tools @@ -25,10 +25,17 @@ These tools install on first use (via mise → Homebrew): |---|---|---| | GitHub CLI | `gh` | brew | | GitLab CLI | `glab` | brew | +| Ruby | `ruby` | brew | +| jq | `jq` | brew | +| ripgrep | `rg` | brew | +| fd | `fd` | brew | +| Wget | `wget` | brew | +| Vim | `vim` | brew | +| Nano | `nano` | brew | -Add more tools to `/etc/mise/config.toml` to extend the list. +`n` and `node` are also in the config but pre-installed — included so mise can manage version switches at runtime. -> **Note:** `n` and `node` are also listed in the mise config but are pre-installed in the image — they're included so mise can manage versions if you switch them at runtime. +Add more tools to `/etc/mise/config.toml` to extend the list. ## Usage @@ -97,5 +104,5 @@ 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. -- **Lazy-installed tools** (see table above): run `gh`, `glab`, `n`, or `node` and they install on first use via mise → Homebrew. Edit `/etc/mise/config.toml` to add more auto-install tools. +- Homebrew is installed under `/home/linuxbrew/.linuxbrew` (outside the persistent volume). It uses its bundled portable Ruby — no system Ruby needed. +- **Lazy-installed tools** (see table above): run any listed tool and mise auto-installs it via Homebrew on first use. Edit `/etc/mise/config.toml` to add more. diff --git a/mise-config.toml b/mise-config.toml index ce74bb9..e2ec244 100644 --- a/mise-config.toml +++ b/mise-config.toml @@ -3,3 +3,10 @@ "brew:glab" = "latest" "brew:n" = "latest" "brew:node" = "latest" +"brew:ruby" = "latest" +"brew:jq" = "latest" +"brew:ripgrep" = "latest" +"brew:fd" = "latest" +"brew:wget" = "latest" +"brew:vim" = "latest" +"brew:nano" = "latest" From 934b7335af5ddd9d0d8771dac04765b231932b4b Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 20:11:21 +0000 Subject: [PATCH 07/24] chore: remove pre-install of gh/glab, all tools now lazy via mise --- Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index b399575..d2fb98a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -60,8 +60,6 @@ RUN mkdir -p /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 \ && sudo -u opencode rm -rf "$(sudo -u opencode /home/linuxbrew/.linuxbrew/bin/brew --cache)" \ - && sudo -u opencode HOMEBREW_NO_AUTO_UPDATE=1 \ - /home/linuxbrew/.linuxbrew/bin/brew install gh glab \ && 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 \ From 9d383f29d2f058eec5680a84fcd01dd477c0fb37 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 20:12:19 +0000 Subject: [PATCH 08/24] fix: move mise config to user home dir --- Dockerfile | 7 +++---- README.md | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index d2fb98a..38578ad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -72,8 +72,7 @@ RUN mkdir -p /home/linuxbrew \ # 1.5. mise — dev tool manager; pre-approved tools defined in the global config # auto-install via Homebrew backend on first use at runtime. RUN curl -fsSL https://mise.run | MISE_INSTALL_PATH=/usr/local/bin/mise sh \ - && mkdir -p /opt/mise /etc/mise -COPY mise-config.toml /etc/mise/config.toml + && mkdir -p /opt/mise # 2. Node.js via `n` — changes when the upstream LTS version bumps RUN curl -fsSL -o /usr/local/bin/n https://raw.githubusercontent.com/tj/n/master/bin/n \ @@ -102,7 +101,7 @@ ENV PATH=${N_PREFIX}/bin:/home/opencode/.local/bin:/home/linuxbrew/.linuxbrew/bi ENV HOMEBREW_NO_AUTO_UPDATE=1 ENV HOMEBREW_INSTALL_FROM_API=1 ENV MISE_DATA_DIR=/opt/mise -ENV MISE_GLOBAL_CONFIG_FILE=/etc/mise/config.toml +ENV MISE_GLOBAL_CONFIG_FILE=/home/opencode/.config/mise/config.toml ENV MISE_ALWAYS_INSTALL=1 # Runtimes copied from builder (most-stable first so frequent version @@ -114,7 +113,7 @@ COPY --from=builder /opt/opencode /usr/local/bin/opencode # Mise — dev tool manager; auto-installs tools defined in the global config. COPY --from=builder /usr/local/bin/mise /usr/local/bin/mise COPY --from=builder --chown=opencode:opencode /opt/mise /opt/mise -COPY --from=builder /etc/mise/config.toml /etc/mise/config.toml +COPY --chown=opencode:opencode mise-config.toml /home/opencode/.config/mise/config.toml # Verify runtimes and set up login-shell PATH and auto-install handler RUN node --version && npm --version && opencode --version \ diff --git a/README.md b/README.md index 1c42692..b5df149 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ These tools install on first use (via mise → Homebrew): `n` and `node` are also in the config but pre-installed — included so mise can manage version switches at runtime. -Add more tools to `/etc/mise/config.toml` to extend the list. +Add more tools to `~/.config/mise/config.toml` to extend the list. ## Usage From feaf738dc86edc70afe4f80247cd3c20aaa69e6b Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 20:14:04 +0000 Subject: [PATCH 09/24] feat: move python3 to lazy install via mise --- Dockerfile | 1 - README.md | 3 ++- mise-config.toml | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 38578ad..724a109 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,6 @@ ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates curl git openssh-client unzip xz-utils \ build-essential pkg-config \ - python3 python3-pip python3-venv \ less sudo tini open-iscsi tzdata locales \ && rm -rf /var/lib/apt/lists/* \ && userdel --remove ubuntu 2>/dev/null || true; \ diff --git a/README.md b/README.md index b5df149..a0f4c17 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ A general-purpose Ubuntu Docker image for running [opencode](https://opencode.ai | **User** | `opencode` (uid/gid 1000), passwordless sudo | | **opencode** | Pinned in `version.txt` as `OPENCODE_VERSION` build arg | | **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) | +| **Python 3** | Lazy-installed via mise (see table below) | | **Homebrew** | Linux-native Homebrew (`/home/linuxbrew/.linuxbrew`) — `brew` on PATH | | **mise** | Dev tool manager — tools listed below install on first use via `brew` backend | | **CLI utilities** | git, curl, less, unzip, ssh client | @@ -32,6 +32,7 @@ These tools install on first use (via mise → Homebrew): | Wget | `wget` | brew | | Vim | `vim` | brew | | Nano | `nano` | brew | +| Python 3 | `python3` | brew | `n` and `node` are also in the config but pre-installed — included so mise can manage version switches at runtime. diff --git a/mise-config.toml b/mise-config.toml index e2ec244..0cacb17 100644 --- a/mise-config.toml +++ b/mise-config.toml @@ -10,3 +10,4 @@ "brew:wget" = "latest" "brew:vim" = "latest" "brew:nano" = "latest" +"brew:python" = "latest" From 4e8cf0942d1432c2cd55e1458e0b12376c416fa8 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 20:14:48 +0000 Subject: [PATCH 10/24] chore: remove n and node from mise config (already pre-installed) --- README.md | 2 -- mise-config.toml | 2 -- 2 files changed, 4 deletions(-) diff --git a/README.md b/README.md index a0f4c17..37e0d45 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,6 @@ These tools install on first use (via mise → Homebrew): | Nano | `nano` | brew | | Python 3 | `python3` | brew | -`n` and `node` are also in the config but pre-installed — included so mise can manage version switches at runtime. - Add more tools to `~/.config/mise/config.toml` to extend the list. ## Usage diff --git a/mise-config.toml b/mise-config.toml index 0cacb17..2c23098 100644 --- a/mise-config.toml +++ b/mise-config.toml @@ -1,8 +1,6 @@ [tools] "brew:gh" = "latest" "brew:glab" = "latest" -"brew:n" = "latest" -"brew:node" = "latest" "brew:ruby" = "latest" "brew:jq" = "latest" "brew:ripgrep" = "latest" From 5f087434c7661388fe3b4e52f702acbc286c4ddd Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:16:03 +0000 Subject: [PATCH 11/24] Replace pre-installed Node.js (n) with lazy mise install; remove open-iscsi and .deb cache --- Dockerfile | 36 ++++++++++-------------------------- README.md | 5 +++-- mise-config.toml | 2 ++ 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/Dockerfile b/Dockerfile index 724a109..dbacf4c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,16 +4,14 @@ # Ubuntu base with a broad, familiar dev toolchain. All configuration is env-driven # (OPENCODE_SERVER_PASSWORD, OPENCODE_CORS_ORIGIN, OPENCODE_PORT). The entire home # directory (/home/opencode) is the persistent mount point; the active project -# lives under ~/workspace. Node lives in /opt/n (outside home) so it's never -# shadowed when a volume mounts over the home directory. +# lives under ~/workspace. # # Three-stage build: # base — apt packages, user, sudo, init — shared by builder and final -# builder — fetches relocatable toolchains (Node, opencode, Homebrew) +# builder — fetches relocatable toolchains (opencode, Homebrew, mise) # final — copies in runtimes from builder; carries only runtime layers ARG OPENCODE_VERSION=0.0.0 -ARG NODE_PREFIX=/opt/n # --------------------------------------------------------------------------- # base: common runtime layer (apt, user, sudo, init) @@ -26,8 +24,8 @@ ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates curl git openssh-client unzip xz-utils \ build-essential pkg-config \ - less sudo tini open-iscsi tzdata locales \ - && rm -rf /var/lib/apt/lists/* \ + less sudo tini tzdata locales \ + && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*.deb \ && userdel --remove ubuntu 2>/dev/null || true; \ groupdel ubuntu 2>/dev/null || true; \ groupadd --gid 1000 opencode \ @@ -48,10 +46,6 @@ RUN chmod 0755 /usr/local/bin/entrypoint.sh # --------------------------------------------------------------------------- FROM base AS builder -ARG NODE_PREFIX -ENV N_PREFIX=${NODE_PREFIX} -ENV PATH=${NODE_PREFIX}/bin:${PATH} - # 1. Homebrew — the install script URL is stable; brew releases rarely # invalidate the layer once installed. RUN mkdir -p /home/linuxbrew \ @@ -73,16 +67,9 @@ RUN mkdir -p /home/linuxbrew \ RUN curl -fsSL https://mise.run | MISE_INSTALL_PATH=/usr/local/bin/mise sh \ && mkdir -p /opt/mise -# 2. Node.js via `n` — changes when the upstream LTS version bumps -RUN curl -fsSL -o /usr/local/bin/n https://raw.githubusercontent.com/tj/n/master/bin/n \ - && chmod 0755 /usr/local/bin/n \ - && mkdir -p "${N_PREFIX}" \ - && n install --cleanup current \ - && node --version && npm --version - ARG OPENCODE_VERSION -# 3. opencode server binary — changes on every version bump (most frequent) +# 2. opencode server binary — changes on every version bump (most frequent) RUN curl -fsSL https://opencode.ai/install | VERSION="${OPENCODE_VERSION}" bash \ && (cp /root/.opencode/bin/opencode /opt/opencode 2>/dev/null \ || cp "$HOME/.opencode/bin/opencode" /opt/opencode) \ @@ -94,9 +81,7 @@ RUN curl -fsSL https://opencode.ai/install | VERSION="${OPENCODE_VERSION}" bash # --------------------------------------------------------------------------- FROM base -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} +ENV PATH=/home/opencode/.local/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:${PATH} ENV HOMEBREW_NO_AUTO_UPDATE=1 ENV HOMEBREW_INSTALL_FROM_API=1 ENV MISE_DATA_DIR=/opt/mise @@ -106,7 +91,6 @@ ENV MISE_ALWAYS_INSTALL=1 # Runtimes copied from builder (most-stable first so frequent version # bumps don't invalidate cache for the other layers). COPY --from=builder --chown=opencode:opencode /home/linuxbrew /home/linuxbrew -COPY --from=builder --chown=opencode:opencode ${NODE_PREFIX} ${NODE_PREFIX} COPY --from=builder /opt/opencode /usr/local/bin/opencode # Mise — dev tool manager; auto-installs tools defined in the global config. @@ -114,10 +98,10 @@ COPY --from=builder /usr/local/bin/mise /usr/local/bin/mise COPY --from=builder --chown=opencode:opencode /opt/mise /opt/mise COPY --chown=opencode:opencode mise-config.toml /home/opencode/.config/mise/config.toml -# Verify runtimes and set up login-shell PATH and auto-install handler -RUN node --version && npm --version && opencode --version \ - && 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 \ +# Verify runtime and set up login-shell PATH and auto-install handler +RUN opencode --version \ + && printf 'for d in "$HOME/.local/bin" "/home/linuxbrew/.linuxbrew/bin" "/home/linuxbrew/.linuxbrew/sbin"; do case ":$PATH:" in *":$d:"*) ;; *) PATH="$d:$PATH";; esac; done\nexport PATH\n' > /etc/profile.d/brew-path.sh \ + && chmod 0644 /etc/profile.d/brew-path.sh \ && printf '\neval "$(mise activate bash)"\n' >> /home/opencode/.bashrc USER opencode diff --git a/README.md b/README.md index 37e0d45..01c0865 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ A general-purpose Ubuntu Docker image for running [opencode](https://opencode.ai | **Base OS** | ubuntu:26.04 | | **User** | `opencode` (uid/gid 1000), passwordless sudo | | **opencode** | Pinned in `version.txt` as `OPENCODE_VERSION` build arg | -| **Node.js** | Current LTS via `n`, installed to `/opt/n` (outside home) | | **Build tools** | `build-essential`, `pkg-config` (for native npm addons, pip source builds) | | **Python 3** | Lazy-installed via mise (see table below) | | **Homebrew** | Linux-native Homebrew (`/home/linuxbrew/.linuxbrew`) — `brew` on PATH | @@ -33,6 +32,8 @@ These tools install on first use (via mise → Homebrew): | Vim | `vim` | brew | | Nano | `nano` | brew | | Python 3 | `python3` | brew | +| n | `n` | brew | +| Node.js | `node` | brew | Add more tools to `~/.config/mise/config.toml` to extend the list. @@ -104,4 +105,4 @@ Fetches the latest release from [anomalyco/opencode](https://github.com/anomalyc - `~/.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). It uses its bundled portable Ruby — no system Ruby needed. -- **Lazy-installed tools** (see table above): run any listed tool and mise auto-installs it via Homebrew on first use. Edit `/etc/mise/config.toml` to add more. +- **Lazy-installed tools** (see table above): run any listed tool and mise auto-installs it via Homebrew on first use. Edit `~/.config/mise/config.toml` to add more. diff --git a/mise-config.toml b/mise-config.toml index 2c23098..4eb3313 100644 --- a/mise-config.toml +++ b/mise-config.toml @@ -9,3 +9,5 @@ "brew:vim" = "latest" "brew:nano" = "latest" "brew:python" = "latest" +"brew:n" = "latest" +"brew:node" = "latest" From 5330397a771b381d749dc09062c3530f5c7a33e0 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:25:15 +0000 Subject: [PATCH 12/24] Fix negative image diff: pass abs value to numfmt, handle sign separately --- .github/workflows/pr-build.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index 83de44c..efa89ef 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -92,8 +92,14 @@ jobs: BASELINE_HUMAN="${{ steps.baseline-size.outputs.human }}" BASELINE_BYTES=${{ steps.baseline-size.outputs.bytes }} DIFF_BYTES=$(( PR_BYTES - BASELINE_BYTES )) - if [ "$DIFF_BYTES" -ge 0 ]; then SIGN="+"; else SIGN=""; fi - DIFF_HUMAN=$(numfmt --to=iec --suffix=B "$DIFF_BYTES" 2>/dev/null || echo "${DIFF_BYTES} B") + if [ "$DIFF_BYTES" -gt 0 ]; then + SIGN="+" + ABS_DIFF=$DIFF_BYTES + else + SIGN="" + ABS_DIFF=$(( -DIFF_BYTES )) + fi + DIFF_HUMAN=$(numfmt --to=iec --suffix=B "$ABS_DIFF" 2>/dev/null || echo "${ABS_DIFF} B") fi { @@ -121,11 +127,7 @@ jobs: echo "pr: $PR_HUMAN ($PR_BYTES bytes)" echo "pr-human=${PR_HUMAN}" >> "$GITHUB_OUTPUT" - if [ "$DIFF_BYTES" -ge 0 ]; then - echo "diff-human=+${DIFF_HUMAN}" >> "$GITHUB_OUTPUT" - else - echo "diff-human=${DIFF_HUMAN}" >> "$GITHUB_OUTPUT" - fi + echo "diff-human=${SIGN}${DIFF_HUMAN}" >> "$GITHUB_OUTPUT" - name: Comment on PR if: github.event_name == 'pull_request' From 02a7ba4fc47899f28e17bf581fd673d4618625aa Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:37:56 +0000 Subject: [PATCH 13/24] Move mise config to /etc/mise/config.toml (system config, outside PVC) --- Dockerfile | 3 +-- README.md | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index dbacf4c..b10b5d9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -85,7 +85,6 @@ ENV PATH=/home/opencode/.local/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbre ENV HOMEBREW_NO_AUTO_UPDATE=1 ENV HOMEBREW_INSTALL_FROM_API=1 ENV MISE_DATA_DIR=/opt/mise -ENV MISE_GLOBAL_CONFIG_FILE=/home/opencode/.config/mise/config.toml ENV MISE_ALWAYS_INSTALL=1 # Runtimes copied from builder (most-stable first so frequent version @@ -96,7 +95,7 @@ COPY --from=builder /opt/opencode /usr/local/bin/opencode # Mise — dev tool manager; auto-installs tools defined in the global config. COPY --from=builder /usr/local/bin/mise /usr/local/bin/mise COPY --from=builder --chown=opencode:opencode /opt/mise /opt/mise -COPY --chown=opencode:opencode mise-config.toml /home/opencode/.config/mise/config.toml +COPY mise-config.toml /etc/mise/config.toml # Verify runtime and set up login-shell PATH and auto-install handler RUN opencode --version \ diff --git a/README.md b/README.md index 01c0865..2dc411c 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ These tools install on first use (via mise → Homebrew): | n | `n` | brew | | Node.js | `node` | brew | -Add more tools to `~/.config/mise/config.toml` to extend the list. +The image ships with a system config at `/etc/mise/config.toml` with these pre-approved tools. Users can add or override tools by creating `~/.config/mise/config.toml` — mise merges both. ## Usage @@ -105,4 +105,4 @@ Fetches the latest release from [anomalyco/opencode](https://github.com/anomalyc - `~/.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). It uses its bundled portable Ruby — no system Ruby needed. -- **Lazy-installed tools** (see table above): run any listed tool and mise auto-installs it via Homebrew on first use. Edit `~/.config/mise/config.toml` to add more. +- **Lazy-installed tools** (see table above): run any listed tool and mise auto-installs it via Homebrew on first use. The image ships defaults in `/etc/mise/config.toml`; create `~/.config/mise/config.toml` to add your own — mise merges both. From 81d68cddf22ae8f1613b1d138ad38b4223e39c83 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:40:14 +0000 Subject: [PATCH 14/24] Install mise brew backend plugin (brew:tool prefix requires it) --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b10b5d9..0ba03cf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,7 +65,8 @@ RUN mkdir -p /home/linuxbrew \ # 1.5. mise — dev tool manager; pre-approved tools defined in the global config # auto-install via Homebrew backend on first use at runtime. RUN curl -fsSL https://mise.run | MISE_INSTALL_PATH=/usr/local/bin/mise sh \ - && mkdir -p /opt/mise + && mkdir -p /opt/mise \ + && MISE_DATA_DIR=/opt/mise mise plugins install brew https://github.com/woutermont/mise-brew ARG OPENCODE_VERSION From 085cf5a154d85d69742aaad6f6adabd7b3fdb7b9 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:42:53 +0000 Subject: [PATCH 15/24] Pre-install jq (brew plugin dependency); restore Homebrew cask (brew requires it at startup) --- Dockerfile | 3 +-- README.md | 3 +-- mise-config.toml | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0ba03cf..3d71624 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ ENV DEBIAN_FRONTEND=noninteractive # General dev toolchain: VCS, build tools, languages, CLI utilities. RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates curl git openssh-client unzip xz-utils \ - build-essential pkg-config \ + build-essential jq pkg-config \ less sudo tini tzdata locales \ && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*.deb \ && userdel --remove ubuntu 2>/dev/null || true; \ @@ -54,7 +54,6 @@ RUN mkdir -p /home/linuxbrew \ && 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/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/share/man \ diff --git a/README.md b/README.md index 2dc411c..af07799 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A general-purpose Ubuntu Docker image for running [opencode](https://opencode.ai | **Python 3** | Lazy-installed via mise (see table below) | | **Homebrew** | Linux-native Homebrew (`/home/linuxbrew/.linuxbrew`) — `brew` on PATH | | **mise** | Dev tool manager — tools listed below install on first use via `brew` backend | -| **CLI utilities** | git, curl, less, unzip, ssh client | +| **CLI utilities** | git, curl, jq, less, unzip, ssh client | | **Init** | tini as PID 1 (zombie reaping, clean shutdown) | ### Lazy-installed tools @@ -25,7 +25,6 @@ These tools install on first use (via mise → Homebrew): | GitHub CLI | `gh` | brew | | GitLab CLI | `glab` | brew | | Ruby | `ruby` | brew | -| jq | `jq` | brew | | ripgrep | `rg` | brew | | fd | `fd` | brew | | Wget | `wget` | brew | diff --git a/mise-config.toml b/mise-config.toml index 4eb3313..97b738d 100644 --- a/mise-config.toml +++ b/mise-config.toml @@ -2,7 +2,6 @@ "brew:gh" = "latest" "brew:glab" = "latest" "brew:ruby" = "latest" -"brew:jq" = "latest" "brew:ripgrep" = "latest" "brew:fd" = "latest" "brew:wget" = "latest" From 9ac1a58580070b8f371ea3dc70525fb41363040b Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:55:36 +0000 Subject: [PATCH 16/24] Switch mise activation to shims mode (hook-not-found doesn't match prefixed tools like brew:gh) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3d71624..8adac0c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -101,7 +101,7 @@ COPY mise-config.toml /etc/mise/config.toml RUN opencode --version \ && printf 'for d in "$HOME/.local/bin" "/home/linuxbrew/.linuxbrew/bin" "/home/linuxbrew/.linuxbrew/sbin"; do case ":$PATH:" in *":$d:"*) ;; *) PATH="$d:$PATH";; esac; done\nexport PATH\n' > /etc/profile.d/brew-path.sh \ && chmod 0644 /etc/profile.d/brew-path.sh \ - && printf '\neval "$(mise activate bash)"\n' >> /home/opencode/.bashrc + && printf '\neval "$(mise activate bash --shims)"\n' >> /home/opencode/.bashrc USER opencode ENV HOME=/home/opencode From e428612da329760fffa22c9a33064f12873016fb Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:57:05 +0000 Subject: [PATCH 17/24] Add mise activation for zsh, fish, and sh shells --- Dockerfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 8adac0c..6f9d92d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -101,7 +101,11 @@ COPY mise-config.toml /etc/mise/config.toml RUN opencode --version \ && printf 'for d in "$HOME/.local/bin" "/home/linuxbrew/.linuxbrew/bin" "/home/linuxbrew/.linuxbrew/sbin"; do case ":$PATH:" in *":$d:"*) ;; *) PATH="$d:$PATH";; esac; done\nexport PATH\n' > /etc/profile.d/brew-path.sh \ && chmod 0644 /etc/profile.d/brew-path.sh \ - && printf '\neval "$(mise activate bash --shims)"\n' >> /home/opencode/.bashrc + && printf '\neval "$(mise activate bash --shims)"\n' >> /home/opencode/.bashrc \ + && printf '\neval "$(mise activate zsh --shims)"\n' >> /home/opencode/.zshrc \ + && mkdir -p /home/opencode/.config/fish \ + && printf '\nmise activate fish --shims | source\n' >> /home/opencode/.config/fish/config.fish \ + && printf '\neval "$(mise activate sh --shims)"\n' >> /home/opencode/.profile USER opencode ENV HOME=/home/opencode From 36424da812a7fdea9a55d05c708300d183910d51 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 22:19:48 +0000 Subject: [PATCH 18/24] Create mise shims for each configured tool (mise exec handles auto-install with MISE_ALWAYS_INSTALL=1) --- Dockerfile | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6f9d92d..8f611af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -101,11 +101,19 @@ COPY mise-config.toml /etc/mise/config.toml RUN opencode --version \ && printf 'for d in "$HOME/.local/bin" "/home/linuxbrew/.linuxbrew/bin" "/home/linuxbrew/.linuxbrew/sbin"; do case ":$PATH:" in *":$d:"*) ;; *) PATH="$d:$PATH";; esac; done\nexport PATH\n' > /etc/profile.d/brew-path.sh \ && chmod 0644 /etc/profile.d/brew-path.sh \ - && printf '\neval "$(mise activate bash --shims)"\n' >> /home/opencode/.bashrc \ - && printf '\neval "$(mise activate zsh --shims)"\n' >> /home/opencode/.zshrc \ + && printf '\neval "$(mise activate bash)"\n' >> /home/opencode/.bashrc \ + && printf '\neval "$(mise activate zsh)"\n' >> /home/opencode/.zshrc \ && mkdir -p /home/opencode/.config/fish \ - && printf '\nmise activate fish --shims | source\n' >> /home/opencode/.config/fish/config.fish \ - && printf '\neval "$(mise activate sh --shims)"\n' >> /home/opencode/.profile + && printf '\nmise activate fish | source\n' >> /home/opencode/.config/fish/config.fish \ + && printf '\neval "$(mise activate sh)"\n' >> /home/opencode/.profile \ + && mkdir -p /opt/mise/shims \ + && grep -E '^\s*"' /etc/mise/config.toml | while IFS='=' read -r key value; do \ + key="$(echo "$key" | tr -d ' "')" \ + && shim="${key#*:}" \ + && printf '#!/usr/bin/env bash\nexec /usr/local/bin/mise exec "%s" -- "$@"\n' "$key" > "/opt/mise/shims/$shim" \ + && chmod 0755 "/opt/mise/shims/$shim"; \ + done \ + && chown -R opencode:opencode /opt/mise/shims USER opencode ENV HOME=/home/opencode From 47378b4b876725b0362989e88992fd4021de8667 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 22:27:01 +0000 Subject: [PATCH 19/24] Use --shims mode so mise hook-env adds /opt/mise/shims to PATH (shim files now exist) --- Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8f611af..514dd7a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -101,11 +101,11 @@ COPY mise-config.toml /etc/mise/config.toml RUN opencode --version \ && printf 'for d in "$HOME/.local/bin" "/home/linuxbrew/.linuxbrew/bin" "/home/linuxbrew/.linuxbrew/sbin"; do case ":$PATH:" in *":$d:"*) ;; *) PATH="$d:$PATH";; esac; done\nexport PATH\n' > /etc/profile.d/brew-path.sh \ && chmod 0644 /etc/profile.d/brew-path.sh \ - && printf '\neval "$(mise activate bash)"\n' >> /home/opencode/.bashrc \ - && printf '\neval "$(mise activate zsh)"\n' >> /home/opencode/.zshrc \ + && printf '\neval "$(mise activate bash --shims)"\n' >> /home/opencode/.bashrc \ + && printf '\neval "$(mise activate zsh --shims)"\n' >> /home/opencode/.zshrc \ && mkdir -p /home/opencode/.config/fish \ - && printf '\nmise activate fish | source\n' >> /home/opencode/.config/fish/config.fish \ - && printf '\neval "$(mise activate sh)"\n' >> /home/opencode/.profile \ + && printf '\nmise activate fish --shims | source\n' >> /home/opencode/.config/fish/config.fish \ + && printf '\neval "$(mise activate sh --shims)"\n' >> /home/opencode/.profile \ && mkdir -p /opt/mise/shims \ && grep -E '^\s*"' /etc/mise/config.toml | while IFS='=' read -r key value; do \ key="$(echo "$key" | tr -d ' "')" \ From 51ae32e1f185deb0d404b9590e22ff80a6b053ec Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 22:30:01 +0000 Subject: [PATCH 20/24] Fix shim format: add tool name after -- so args pass correctly; add shims dir to PATH via ENV --- Dockerfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 514dd7a..e34c9b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -81,7 +81,7 @@ RUN curl -fsSL https://opencode.ai/install | VERSION="${OPENCODE_VERSION}" bash # --------------------------------------------------------------------------- FROM base -ENV PATH=/home/opencode/.local/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:${PATH} +ENV PATH=/opt/mise/shims:/home/opencode/.local/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:${PATH} ENV HOMEBREW_NO_AUTO_UPDATE=1 ENV HOMEBREW_INSTALL_FROM_API=1 ENV MISE_DATA_DIR=/opt/mise @@ -101,16 +101,16 @@ COPY mise-config.toml /etc/mise/config.toml RUN opencode --version \ && printf 'for d in "$HOME/.local/bin" "/home/linuxbrew/.linuxbrew/bin" "/home/linuxbrew/.linuxbrew/sbin"; do case ":$PATH:" in *":$d:"*) ;; *) PATH="$d:$PATH";; esac; done\nexport PATH\n' > /etc/profile.d/brew-path.sh \ && chmod 0644 /etc/profile.d/brew-path.sh \ - && printf '\neval "$(mise activate bash --shims)"\n' >> /home/opencode/.bashrc \ - && printf '\neval "$(mise activate zsh --shims)"\n' >> /home/opencode/.zshrc \ + && printf '\neval "$(mise activate bash)"\n' >> /home/opencode/.bashrc \ + && printf '\neval "$(mise activate zsh)"\n' >> /home/opencode/.zshrc \ && mkdir -p /home/opencode/.config/fish \ - && printf '\nmise activate fish --shims | source\n' >> /home/opencode/.config/fish/config.fish \ - && printf '\neval "$(mise activate sh --shims)"\n' >> /home/opencode/.profile \ + && printf '\nmise activate fish | source\n' >> /home/opencode/.config/fish/config.fish \ + && printf '\neval "$(mise activate sh)"\n' >> /home/opencode/.profile \ && mkdir -p /opt/mise/shims \ && grep -E '^\s*"' /etc/mise/config.toml | while IFS='=' read -r key value; do \ key="$(echo "$key" | tr -d ' "')" \ && shim="${key#*:}" \ - && printf '#!/usr/bin/env bash\nexec /usr/local/bin/mise exec "%s" -- "$@"\n' "$key" > "/opt/mise/shims/$shim" \ + && printf '#!/usr/bin/env bash\nexec /usr/local/bin/mise exec "%s" -- %s "$@"\n' "$key" "$shim" > "/opt/mise/shims/$shim" \ && chmod 0755 "/opt/mise/shims/$shim"; \ done \ && chown -R opencode:opencode /opt/mise/shims From 6306cea98dd61f61753be509753a237247b0e0a1 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 22:39:04 +0000 Subject: [PATCH 21/24] Move shims after brew bins in PATH; use plain mise activate (no --shims) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e34c9b3..c476110 100644 --- a/Dockerfile +++ b/Dockerfile @@ -81,7 +81,7 @@ RUN curl -fsSL https://opencode.ai/install | VERSION="${OPENCODE_VERSION}" bash # --------------------------------------------------------------------------- FROM base -ENV PATH=/opt/mise/shims:/home/opencode/.local/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:${PATH} +ENV PATH=/home/opencode/.local/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/opt/mise/shims:${PATH} ENV HOMEBREW_NO_AUTO_UPDATE=1 ENV HOMEBREW_INSTALL_FROM_API=1 ENV MISE_DATA_DIR=/opt/mise From 5181c8e154a51be3471389433a65aabc340d962c Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 22:45:33 +0000 Subject: [PATCH 22/24] Move auto-install shims outside MISE_DATA_DIR so mise hook-env doesn't manage/delete them --- Dockerfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index c476110..108d86b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -81,7 +81,7 @@ RUN curl -fsSL https://opencode.ai/install | VERSION="${OPENCODE_VERSION}" bash # --------------------------------------------------------------------------- FROM base -ENV PATH=/home/opencode/.local/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/opt/mise/shims:${PATH} +ENV PATH=/home/opencode/.local/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/opt/auto-install-shims:${PATH} ENV HOMEBREW_NO_AUTO_UPDATE=1 ENV HOMEBREW_INSTALL_FROM_API=1 ENV MISE_DATA_DIR=/opt/mise @@ -106,14 +106,14 @@ RUN opencode --version \ && mkdir -p /home/opencode/.config/fish \ && printf '\nmise activate fish | source\n' >> /home/opencode/.config/fish/config.fish \ && printf '\neval "$(mise activate sh)"\n' >> /home/opencode/.profile \ - && mkdir -p /opt/mise/shims \ + && mkdir -p /opt/auto-install-shims \ && grep -E '^\s*"' /etc/mise/config.toml | while IFS='=' read -r key value; do \ key="$(echo "$key" | tr -d ' "')" \ && shim="${key#*:}" \ - && printf '#!/usr/bin/env bash\nexec /usr/local/bin/mise exec "%s" -- %s "$@"\n' "$key" "$shim" > "/opt/mise/shims/$shim" \ - && chmod 0755 "/opt/mise/shims/$shim"; \ + && printf '#!/usr/bin/env bash\nexec /usr/local/bin/mise exec "%s" -- %s "$@"\n' "$key" "$shim" > "/opt/auto-install-shims/$shim" \ + && chmod 0755 "/opt/auto-install-shims/$shim"; \ done \ - && chown -R opencode:opencode /opt/mise/shims + && chown -R opencode:opencode /opt/auto-install-shims USER opencode ENV HOME=/home/opencode From 4fc192defe19765b5513c385d39dc48fab01e69e Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 22:56:04 +0000 Subject: [PATCH 23/24] Add micro editor to lazy-installed tools --- README.md | 1 + mise-config.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index af07799..d8c7ab0 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ These tools install on first use (via mise → Homebrew): | fd | `fd` | brew | | Wget | `wget` | brew | | Vim | `vim` | brew | +| Micro | `micro` | brew | | Nano | `nano` | brew | | Python 3 | `python3` | brew | | n | `n` | brew | diff --git a/mise-config.toml b/mise-config.toml index 97b738d..cb60494 100644 --- a/mise-config.toml +++ b/mise-config.toml @@ -6,6 +6,7 @@ "brew:fd" = "latest" "brew:wget" = "latest" "brew:vim" = "latest" +"brew:micro" = "latest" "brew:nano" = "latest" "brew:python" = "latest" "brew:n" = "latest" From 0156979d151babbca45abc1861d2a38c0c478cd6 Mon Sep 17 00:00:00 2001 From: gabemeola <14303404+gabemeola@users.noreply.github.com> Date: Wed, 24 Jun 2026 22:56:33 +0000 Subject: [PATCH 24/24] Remove n from lazy tools (mise manages node versions now) --- README.md | 1 - mise-config.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/README.md b/README.md index d8c7ab0..36603ae 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,6 @@ These tools install on first use (via mise → Homebrew): | Micro | `micro` | brew | | Nano | `nano` | brew | | Python 3 | `python3` | brew | -| n | `n` | brew | | Node.js | `node` | brew | The image ships with a system config at `/etc/mise/config.toml` with these pre-approved tools. Users can add or override tools by creating `~/.config/mise/config.toml` — mise merges both. diff --git a/mise-config.toml b/mise-config.toml index cb60494..356b97e 100644 --- a/mise-config.toml +++ b/mise-config.toml @@ -9,5 +9,4 @@ "brew:micro" = "latest" "brew:nano" = "latest" "brew:python" = "latest" -"brew:n" = "latest" "brew:node" = "latest"