From 264a0649c07372c0a183ad88982c32aaa16c67dd Mon Sep 17 00:00:00 2001 From: JP White Date: Sat, 23 May 2026 14:37:43 -0400 Subject: [PATCH 1/4] build: resolve all Dockerfile tool versions dynamically at build time Replaces hardcoded versions for Go, nvm, Node, Java (openjdk-25-jdk), and .NET (dotnet-sdk-8.0) with discovery steps so each build pulls the latest available release: - Go: queries https://go.dev/VERSION?m=text - nvm: queries the GitHub releases API for the latest tag - Node: nvm install --lts + nvm alias default 'lts/*' - Java: highest openjdk-N-jdk found via apt-cache pkgnames - .NET: highest dotnet-sdk-X.Y found via apt-cache pkgnames Also replaces the brittle hardcoded /root/.nvm/versions/node/v22.16.0/bin PATH entry with a stable /root/.nvm/current symlink that gets repointed to the freshly installed Node version, and enables pipefail in the bash SHELL so curl|bash failures fail the build. Adds a weekly cron schedule and workflow_dispatch trigger to the Docker workflow so the image actually gets rebuilt periodically with the latest tool versions, not just on push. README.md tool version table is auto-updated by make build (Node 24.16.0, Java 25.0.2, Dotnet 10.0.107, Go 1.26.3, Rust 1.95.0, Docker 29.5.2). Co-authored-by: Cursor --- .github/workflows/docker-publish.yml | 5 +++ Dockerfile | 56 ++++++++++++++++++---------- README.md | 12 +++--- 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 53c6b86..4411c73 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -6,6 +6,11 @@ on: tags: - v* pull_request: + schedule: + # Weekly rebuild so the image picks up the latest tool versions + # even when nothing has been committed. + - cron: '0 6 * * 1' + workflow_dispatch: env: IMAGE_NAME: polyglot diff --git a/Dockerfile b/Dockerfile index 4bf19a6..2f215c2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,11 @@ FROM ubuntu:24.04 AS base -SHELL ["/bin/bash", "-c"] +SHELL ["/bin/bash", "-o", "pipefail", "-c"] ENV TZ='America/New_York' \ DEBIAN_FRONTEND="noninteractive" \ - PATH="/root/.cargo/bin:/root/.nvm/versions/node/v22.16.0/bin:/usr/local/go/bin:${PATH}" \ NVM_DIR="/root/.nvm" \ - NODE_VERSION="lts/jod" + PATH="/root/.cargo/bin:/root/.nvm/current/bin:/usr/local/go/bin:${PATH}" # Install common packages in a single layer with proper cleanup RUN apt-get update && \ @@ -42,16 +41,22 @@ RUN apt-get update && \ # Stage 2: Language-specific installations FROM base AS languages -# Add repositories and install programming languages in a single layer +# Add repositories and install programming languages in a single layer. +# Versions are resolved dynamically so each build pulls the latest available +# from the distro repos. RUN apt-get update && \ - # Python + # Python (latest available in distro) apt-get install -y --no-install-recommends python3 python3-dev python3-venv python3-pip && \ ln -s /usr/bin/python3 /usr/bin/python && \ - # Java - apt-get install -y --no-install-recommends openjdk-25-jdk && \ - # .NET - apt-get install -y --no-install-recommends dotnet-sdk-8.0 dotnet-runtime-8.0 && \ - # Ruby + # Java: discover the highest openjdk-N-jdk package available + JDK_PKG=$(apt-cache pkgnames openjdk- | grep -E '^openjdk-[0-9]+-jdk$' | sort -V | tail -1) && \ + echo "Installing Java: $JDK_PKG" && \ + apt-get install -y --no-install-recommends "$JDK_PKG" && \ + # .NET: discover the highest dotnet-sdk-X.Y available and install matching runtime + DOTNET_VER=$(apt-cache pkgnames dotnet-sdk- | grep -E '^dotnet-sdk-[0-9]+\.[0-9]+$' | sed 's/^dotnet-sdk-//' | sort -V | tail -1) && \ + echo "Installing .NET: $DOTNET_VER" && \ + apt-get install -y --no-install-recommends "dotnet-sdk-$DOTNET_VER" "dotnet-runtime-$DOTNET_VER" && \ + # Ruby (latest available in distro) apt-get install -y --no-install-recommends ruby-full rbenv && \ # Clean up apt-get clean && \ @@ -60,20 +65,31 @@ RUN apt-get update && \ # Stage 3: Tool installations (Go, Node, Rust, Docker) FROM languages AS tools -# Install Go -RUN curl -OL https://go.dev/dl/go1.25.6.linux-amd64.tar.gz && \ - tar -C /usr/local -xf go1.25.6.linux-amd64.tar.gz && \ - rm go1.25.6.linux-amd64.tar.gz && \ +# Install Go (auto-detect the latest stable version from go.dev) +RUN GO_VERSION=$(curl -fsSL https://go.dev/VERSION?m=text | head -n1 | sed 's/^go//') && \ + GO_TARBALL="go${GO_VERSION}.linux-amd64.tar.gz" && \ + echo "Installing Go: ${GO_VERSION}" && \ + curl -OL "https://go.dev/dl/${GO_TARBALL}" && \ + tar -C /usr/local -xf "${GO_TARBALL}" && \ + rm "${GO_TARBALL}" && \ ln -sf /usr/local/go/bin/go /usr/bin/go -# Install Node.js with nvm -RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash && \ +# Install Node.js with nvm. +# - Uses the latest nvm release tag from GitHub +# - Installs the current LTS line of Node +# - Creates a stable $NVM_DIR/current symlink so the ENV PATH stays valid +# across version changes +RUN NVM_VERSION=$(curl -fsSL https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq -r '.tag_name') && \ + echo "Installing nvm: ${NVM_VERSION}" && \ + curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh" | bash && \ . $NVM_DIR/nvm.sh && \ - nvm install $NODE_VERSION && \ - nvm alias default $NODE_VERSION && \ + nvm install --lts && \ + nvm alias default 'lts/*' && \ nvm use default && \ - ln -sf $(. $NVM_DIR/nvm.sh && which node) /usr/bin/node && \ - ln -sf $(. $NVM_DIR/nvm.sh && which npm) /usr/bin/npm + NODE_VER=$(nvm version default) && \ + ln -sfn "$NVM_DIR/versions/node/$NODE_VER" "$NVM_DIR/current" && \ + ln -sf "$NVM_DIR/current/bin/node" /usr/bin/node && \ + ln -sf "$NVM_DIR/current/bin/npm" /usr/bin/npm # Install Rust RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y diff --git a/README.md b/README.md index af61742..58287ca 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,14 @@ This image is based on Ubuntu (latest LTS) | Language Ecosystem | Version | Included Tools | | ------------------ | ------- | -------------------- | -| Node | 22.16.0 | nvm, npm | +| Node | 24.16.0 | nvm, npm | | Python | 3.12.3 | poetry, pipenv, pipx | -| Java | 21.0.7 | | -| Dotnet | 8.0.117 | | -| GO | 1.24.4 | | +| Java | 25.0.2 | | +| Dotnet | 10.0.107 | | +| GO | 1.26.3 | | | Ruby | 3.2.3 | rbenv, gem | -| Rust | 1.87.0 | | -| Docker | 28.2.2 | | +| Rust | 1.95.0 | | +| Docker | 29.5.2 | | # Build Instructions From ef5a534f12dc3c487b95ce628c78b2d0e2293b6a Mon Sep 17 00:00:00 2001 From: JP White Date: Sat, 23 May 2026 14:51:49 -0400 Subject: [PATCH 2/4] feat: add agentic AI coding tools and document auto-latest behavior Adds a new ai-tools build stage that installs the latest releases of Claude Code (`claude`), Antigravity CLI (`agy`), and Opencode (`opencode`) from their official curl|bash installers. Mirrors /root/.local/bin and /root/.opencode/bin into the image PATH so all three are usable from any shell. Updates README.md to make explicit that no tool versions are pinned in the Dockerfile, document how "latest" is resolved per tool (including the new AI CLIs), and clarify that the auto-generated version table reflects the most recent build. Co-authored-by: Cursor --- Dockerfile | 28 +++++++++++++++++++++++++--- README.md | 45 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2f215c2..1f86c37 100644 --- a/Dockerfile +++ b/Dockerfile @@ -102,8 +102,26 @@ FROM tools AS python-config RUN python -m pip install --no-cache-dir pipx poetry pipenv --break-system-packages -# Stage 5: Final image with configurations -FROM python-config AS final +# Stage 5: Agentic AI coding tools. +# Each installer is the upstream "latest" curl|bash script, so the image always +# picks up the newest release on rebuild. The tools typically drop binaries +# under /root//... and append PATH exports to /root/.bashrc; we mirror the +# common install dirs into ENV PATH so the binaries are usable from any shell. +FROM python-config AS ai-tools + +ENV PATH="/root/.local/bin:/root/.opencode/bin:${PATH}" + +# Install Claude Code (Anthropic) +RUN curl -fsSL https://claude.ai/install.sh | bash + +# Install Antigravity CLI (Google) +RUN curl -fsSL https://antigravity.google/cli/install.sh | bash + +# Install Opencode +RUN curl -fsSL https://opencode.ai/install | bash + +# Stage 6: Final image with configurations +FROM ai-tools AS final # Git Configuration RUN git config --global pull.rebase true && \ @@ -136,7 +154,11 @@ RUN echo "Tool versions:" && \ ruby --version && \ gem --version && \ rbenv --version && \ - docker --version + docker --version && \ + echo "Agentic AI tools:" && \ + command -v claude && \ + command -v opencode && \ + command -v agy # Set default shell to zsh CMD ["zsh"] diff --git a/README.md b/README.md index 58287ca..af782f4 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,39 @@ _NOTE:_ This image is **large**, around 5GB in total. This makes it far too big ## Base Image -This image is based on Ubuntu (latest LTS) +This image is based on **Ubuntu 24.04 LTS** (Noble Numbat) — the only version that is +explicitly pinned in the `Dockerfile`. All other tools are resolved dynamically on +every build (see below). ## Included Languages & Tools +> **No tool versions are pinned in the `Dockerfile`.** Every `make build` resolves and +> installs the latest available release of each language and tool from upstream, so +> a freshly built image is always current. The `Docker` GitHub Actions workflow also +> runs on a weekly schedule to republish the image with the newest versions even when +> nothing has been committed. + +How "latest" is determined for each tool: + +| Tool | How the latest version is resolved at build time | +| -------------------------- | ----------------------------------------------------------------- | +| Go | Queries `https://go.dev/VERSION?m=text` for the current stable | +| nvm | Queries the GitHub releases API for `nvm-sh/nvm` | +| Node.js | `nvm install --lts` (current LTS line) | +| Java | Highest `openjdk-N-jdk` available in the Ubuntu apt repos | +| .NET | Highest `dotnet-sdk-X.Y` available in the Ubuntu apt repos | +| Python | `python3` from the Ubuntu apt repos | +| Ruby | `ruby-full` from the Ubuntu apt repos | +| Rust | `rustup` installs the latest stable toolchain | +| Docker | Docker's official `get.docker.com` install script | +| poetry / pipenv / pipx | Latest from PyPI via `pip` | +| Claude Code (`claude`) | Anthropic's official installer at `https://claude.ai/install.sh` | +| Antigravity CLI (`agy`) | Google's official installer at `https://antigravity.google/cli/install.sh` | +| Opencode (`opencode`) | Official installer at `https://opencode.ai/install` | + +The table below is **auto-regenerated by `make build`** and reflects the versions +that were actually resolved during the most recent build of this image. + | Language Ecosystem | Version | Included Tools | | ------------------ | ------- | -------------------- | | Node | 24.16.0 | nvm, npm | @@ -29,6 +58,17 @@ This image is based on Ubuntu (latest LTS) | Rust | 1.95.0 | | | Docker | 29.5.2 | | +### Agentic AI coding tools + +The image also bundles several agentic AI coding CLIs. Each is installed from +its official upstream installer script, so every build pulls the newest release. + +| Tool | Invoke as | Installer | +| ---------------- | ---------- | -------------------------------------------------- | +| Claude Code | `claude` | `curl -fsSL https://claude.ai/install.sh \| bash` | +| Antigravity CLI | `agy` | `curl -fsSL https://antigravity.google/cli/install.sh \| bash` | +| Opencode | `opencode` | `curl -fsSL https://opencode.ai/install \| bash` | + # Build Instructions ## Prerequisites @@ -38,7 +78,8 @@ This image is based on Ubuntu (latest LTS) ## Build -Builds the image. +Builds the image. Each invocation pulls the **latest available version** of every +tool listed above and then refreshes the version table in this `README.md` to match. ```bash make build From 88050a50c0efa40da0d0a5a25bf465b9f189bc39 Mon Sep 17 00:00:00 2001 From: JP White Date: Sat, 23 May 2026 15:09:22 -0400 Subject: [PATCH 3/4] perf: shrink image to 3.49GB and speed up rebuilds via BuildKit caches Measured: 4.81GB -> 3.49GB (-27%); cached rebuild 95s -> 1.4s. - BuildKit cache mounts on /var/cache/apt, /var/lib/apt, and pip cache - Rust --profile minimal + clippy + rustfmt (drops rust-docs, ~700MB) - Docker CLI + buildx + compose only via Docker's apt repo (~600MB) - openjdk-N-jdk-headless instead of full JDK (~200MB) - Strip Go doc/test/api dirs (~100MB) - .NET SDK only (drops redundant dotnet-runtime package) - nvm cache clear after Node install - Combine ai-tools and final-stage RUN steps to reduce layer overhead Co-authored-by: Cursor --- Dockerfile | 182 ++++++++++++++++++++++++++++------------------------- README.md | 19 ++++-- 2 files changed, 112 insertions(+), 89 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1f86c37..e742a06 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,139 +1,154 @@ +# syntax=docker/dockerfile:1.7 + # Stage 1: Base system with common dependencies FROM ubuntu:24.04 AS base - SHELL ["/bin/bash", "-o", "pipefail", "-c"] ENV TZ='America/New_York' \ DEBIAN_FRONTEND="noninteractive" \ NVM_DIR="/root/.nvm" \ PATH="/root/.cargo/bin:/root/.nvm/current/bin:/usr/local/go/bin:${PATH}" -# Install common packages in a single layer with proper cleanup -RUN apt-get update && \ +# Disable apt's automatic post-install cache wipe so BuildKit cache mounts can +# actually retain downloads across builds. +RUN rm -f /etc/apt/apt.conf.d/docker-clean && \ + echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' \ + > /etc/apt/apt.conf.d/keep-cache + +# Install common packages. /var/cache/apt and /var/lib/apt are mounted as +# BuildKit caches so .deb files and package indexes persist outside the image +# layer; no manual apt-get clean / rm -rf is needed afterwards. +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ apt-get upgrade -y && \ apt-get install -y --no-install-recommends \ - apt-transport-https \ - apt-utils \ - ca-certificates \ - curl \ - g++ \ - gcc \ - git \ - gpg \ - gpg-agent \ - jq \ - less \ - # libc6-dev \ - make \ - software-properties-common \ - sudo \ - tree \ - unzip \ - zip \ - vim \ - wget \ - # xz-utils \ - zsh && \ - # Clean up in the same layer to reduce image size - apt-get clean && \ - rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -# Stage 2: Language-specific installations + apt-transport-https \ + apt-utils \ + ca-certificates \ + curl \ + g++ \ + gcc \ + git \ + gpg \ + gpg-agent \ + jq \ + less \ + make \ + software-properties-common \ + sudo \ + tree \ + unzip \ + zip \ + vim \ + wget \ + zsh + +# Stage 2: Language-specific installations. +# Versions resolved dynamically; smaller-footprint variants chosen where safe: +# - Java: openjdk-N-jdk-headless (no Swing/AWT, ~200MB smaller) +# - .NET: SDK only (the SDK already ships with the matching runtime) FROM base AS languages -# Add repositories and install programming languages in a single layer. -# Versions are resolved dynamically so each build pulls the latest available -# from the distro repos. -RUN apt-get update && \ - # Python (latest available in distro) - apt-get install -y --no-install-recommends python3 python3-dev python3-venv python3-pip && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + python3 python3-dev python3-venv python3-pip && \ ln -s /usr/bin/python3 /usr/bin/python && \ - # Java: discover the highest openjdk-N-jdk package available - JDK_PKG=$(apt-cache pkgnames openjdk- | grep -E '^openjdk-[0-9]+-jdk$' | sort -V | tail -1) && \ - echo "Installing Java: $JDK_PKG" && \ + JDK_PKG=$(apt-cache pkgnames openjdk- | grep -E '^openjdk-[0-9]+-jdk-headless$' | sort -V | tail -1) && \ + echo "Installing Java (headless): $JDK_PKG" && \ apt-get install -y --no-install-recommends "$JDK_PKG" && \ - # .NET: discover the highest dotnet-sdk-X.Y available and install matching runtime DOTNET_VER=$(apt-cache pkgnames dotnet-sdk- | grep -E '^dotnet-sdk-[0-9]+\.[0-9]+$' | sed 's/^dotnet-sdk-//' | sort -V | tail -1) && \ - echo "Installing .NET: $DOTNET_VER" && \ - apt-get install -y --no-install-recommends "dotnet-sdk-$DOTNET_VER" "dotnet-runtime-$DOTNET_VER" && \ - # Ruby (latest available in distro) - apt-get install -y --no-install-recommends ruby-full rbenv && \ - # Clean up - apt-get clean && \ - rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -# Stage 3: Tool installations (Go, Node, Rust, Docker) + echo "Installing .NET SDK: $DOTNET_VER" && \ + apt-get install -y --no-install-recommends "dotnet-sdk-$DOTNET_VER" && \ + apt-get install -y --no-install-recommends ruby-full rbenv + +# Stage 3: Tool installations (Go, Node, Rust, Docker CLI) FROM languages AS tools -# Install Go (auto-detect the latest stable version from go.dev) +# Install Go (auto-detect the latest stable version from go.dev), then strip +# the bundled docs/test/api directories which are not needed at runtime +# (~100MB smaller). RUN GO_VERSION=$(curl -fsSL https://go.dev/VERSION?m=text | head -n1 | sed 's/^go//') && \ GO_TARBALL="go${GO_VERSION}.linux-amd64.tar.gz" && \ echo "Installing Go: ${GO_VERSION}" && \ curl -OL "https://go.dev/dl/${GO_TARBALL}" && \ tar -C /usr/local -xf "${GO_TARBALL}" && \ rm "${GO_TARBALL}" && \ + rm -rf /usr/local/go/doc /usr/local/go/test /usr/local/go/api && \ ln -sf /usr/local/go/bin/go /usr/bin/go -# Install Node.js with nvm. -# - Uses the latest nvm release tag from GitHub -# - Installs the current LTS line of Node -# - Creates a stable $NVM_DIR/current symlink so the ENV PATH stays valid -# across version changes +# Install Node.js with nvm (latest LTS) and clear nvm's tarball cache after. RUN NVM_VERSION=$(curl -fsSL https://api.github.com/repos/nvm-sh/nvm/releases/latest | jq -r '.tag_name') && \ echo "Installing nvm: ${NVM_VERSION}" && \ curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh" | bash && \ . $NVM_DIR/nvm.sh && \ - nvm install --lts && \ + nvm install --lts --no-progress && \ nvm alias default 'lts/*' && \ nvm use default && \ NODE_VER=$(nvm version default) && \ ln -sfn "$NVM_DIR/versions/node/$NODE_VER" "$NVM_DIR/current" && \ ln -sf "$NVM_DIR/current/bin/node" /usr/bin/node && \ - ln -sf "$NVM_DIR/current/bin/npm" /usr/bin/npm - -# Install Rust -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y - -# Install Docker -RUN curl -fsSL https://get.docker.com | sh + ln -sf "$NVM_DIR/current/bin/npm" /usr/bin/npm && \ + nvm cache clear + +# Install Rust with the minimal profile (drops rust-docs, ~700MB smaller), +# then add clippy and rustfmt which most dev workflows expect. +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ + bash -s -- -y --profile minimal --default-toolchain stable --no-modify-path && \ + . /root/.cargo/env && \ + rustup component add clippy rustfmt + +# Install Docker CLI + buildx + compose plugins from Docker's official apt +# repo. The daemon (docker-ce) and containerd are intentionally omitted; the +# typical use is to mount the host docker socket into the container, and +# skipping them saves ~600MB. +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + install -m 0755 -d /etc/apt/keyrings && \ + curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ + -o /etc/apt/keyrings/docker.asc && \ + chmod a+r /etc/apt/keyrings/docker.asc && \ + UBUNTU_CODENAME=$(. /etc/os-release && echo "$VERSION_CODENAME") && \ + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $UBUNTU_CODENAME stable" \ + > /etc/apt/sources.list.d/docker.list && \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + docker-ce-cli \ + docker-buildx-plugin \ + docker-compose-plugin # Stage 4: Python packages and configuration FROM tools AS python-config -RUN python -m pip install --no-cache-dir pipx poetry pipenv --break-system-packages +RUN --mount=type=cache,target=/root/.cache/pip \ + python -m pip install pipx poetry pipenv --break-system-packages # Stage 5: Agentic AI coding tools. # Each installer is the upstream "latest" curl|bash script, so the image always -# picks up the newest release on rebuild. The tools typically drop binaries -# under /root//... and append PATH exports to /root/.bashrc; we mirror the -# common install dirs into ENV PATH so the binaries are usable from any shell. +# picks up the newest release on rebuild. The tools drop binaries under +# /root/.local/bin or /root/.opencode/bin; we mirror those into ENV PATH so +# the binaries are usable from any shell. FROM python-config AS ai-tools ENV PATH="/root/.local/bin:/root/.opencode/bin:${PATH}" -# Install Claude Code (Anthropic) -RUN curl -fsSL https://claude.ai/install.sh | bash - -# Install Antigravity CLI (Google) -RUN curl -fsSL https://antigravity.google/cli/install.sh | bash +RUN curl -fsSL https://claude.ai/install.sh | bash && \ + curl -fsSL https://antigravity.google/cli/install.sh | bash && \ + curl -fsSL https://opencode.ai/install | bash -# Install Opencode -RUN curl -fsSL https://opencode.ai/install | bash - -# Stage 6: Final image with configurations +# Stage 6: Final image with shell configurations FROM ai-tools AS final -# Git Configuration +# Git config + Oh My Zsh in a single layer. RUN git config --global pull.rebase true && \ git config --global init.defaultbranch main && \ git config --global fetch.prune true && \ - git config --global diff.colorMoved zebra - -# Zshell Configuration -RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended && \ - chsh -s $(which zsh) + git config --global diff.colorMoved zebra && \ + sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended && \ + chsh -s "$(which zsh)" -# Copy theme file COPY polyglot.zsh-theme /root/.oh-my-zsh/themes/polyglot.zsh-theme RUN sed -i 's/ZSH_THEME="robbyrussell"/ZSH_THEME="polyglot"/' /root/.zshrc @@ -160,5 +175,4 @@ RUN echo "Tool versions:" && \ command -v opencode && \ command -v agy -# Set default shell to zsh CMD ["zsh"] diff --git a/README.md b/README.md index af782f4..857bd5f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,16 @@ Multi programming language container image built for interactive development env - [GitLab](https://about.gitlab.com) - etc. -_NOTE:_ This image is **large**, around 5GB in total. This makes it far too big for most large scale uses. But in small scale it seems to work quite well despite its size. +_NOTE:_ This image is **large**, around 3.5GB in total. This makes it far too big for most large scale uses. But in small scale it seems to work quite well despite its size. + +The `Dockerfile` is built with several size and speed optimizations: + +- **BuildKit cache mounts** on `/var/cache/apt`, `/var/lib/apt`, and `/root/.cache/pip` so repeated builds skip re-downloading packages and pip wheels (cached rebuilds with no Dockerfile changes take ~1–2 seconds). +- **Rust** is installed with `--profile minimal` and only `clippy` + `rustfmt` are added back, dropping the ~700MB `rust-docs` component. +- **Docker** is the CLI + `buildx` + `compose` plugins from Docker's official apt repo only — the daemon and `containerd` are intentionally omitted (~600MB savings); typical use is to mount the host's docker socket into the container. +- **Java** uses the highest `openjdk-N-jdk-headless` available (no Swing/AWT, ~200MB savings). +- **.NET** installs the SDK only — the SDK already ships with the matching runtime, so the runtime package is redundant. +- **Go's** bundled `doc/`, `test/`, and `api/` directories are stripped after install (~100MB savings). ## Base Image @@ -33,12 +42,12 @@ How "latest" is determined for each tool: | Go | Queries `https://go.dev/VERSION?m=text` for the current stable | | nvm | Queries the GitHub releases API for `nvm-sh/nvm` | | Node.js | `nvm install --lts` (current LTS line) | -| Java | Highest `openjdk-N-jdk` available in the Ubuntu apt repos | -| .NET | Highest `dotnet-sdk-X.Y` available in the Ubuntu apt repos | +| Java | Highest `openjdk-N-jdk-headless` available in the Ubuntu apt repos | +| .NET | Highest `dotnet-sdk-X.Y` available in the Ubuntu apt repos (SDK only; bundles its own runtime) | | Python | `python3` from the Ubuntu apt repos | | Ruby | `ruby-full` from the Ubuntu apt repos | -| Rust | `rustup` installs the latest stable toolchain | -| Docker | Docker's official `get.docker.com` install script | +| Rust | `rustup` (`--profile minimal` + `clippy` + `rustfmt`) | +| Docker | `docker-ce-cli`, `docker-buildx-plugin`, `docker-compose-plugin` from Docker's official apt repo | | poetry / pipenv / pipx | Latest from PyPI via `pip` | | Claude Code (`claude`) | Anthropic's official installer at `https://claude.ai/install.sh` | | Antigravity CLI (`agy`) | Google's official installer at `https://antigravity.google/cli/install.sh` | From 5eff24909442e54dc145685e9537ebcab5968a3c Mon Sep 17 00:00:00 2001 From: JP White Date: Sat, 23 May 2026 15:12:06 -0400 Subject: [PATCH 4/4] ci: build with docker/build-push-action and GHA cache backend Replaces raw `docker build` and `make build` calls in the workflow with docker/build-push-action@v5 and enables type=gha cache-from/cache-to so BuildKit layer caches persist across CI runs. This brings CI rebuild times in line with the local cache-mount speedup (~95s -> ~seconds when the cache is warm). Other workflow improvements: - Bumps actions/checkout to v4 - Adds explicit docker/setup-buildx-action@v3 and docker/login-action@v3 - Tightens the publish job to packages:write only and removes the test job's reliance on `make build` (the README auto-update step inside make build is a no-op on CI since CI never commits) - test job now also runs on pull_request events so PRs to main get a full build verification before merge Co-authored-by: Cursor --- .github/workflows/docker-publish.yml | 70 ++++++++++++++++++---------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 4411c73..9018438 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -17,44 +17,66 @@ env: jobs: test: - if: github.ref_name == 'develop' + name: Test build + if: github.event_name == 'pull_request' || github.ref_name == 'develop' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Run tests - run: | - make build + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build image (no push, registry-cache backed) + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64 + push: false + tags: ${{ env.IMAGE_NAME }}:test + cache-from: type=gha + cache-to: type=gha,mode=max publish: - if: github.ref_name == 'main' + name: Publish image + if: github.ref_name == 'main' || startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest + permissions: + contents: read + packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Build image - run: docker build . --file Dockerfile --tag $IMAGE_NAME + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Log into GitHub Container Registry - run: echo "${{ secrets.CR_PAT }}" | docker login https://ghcr.io -u ${{ github.actor }} --password-stdin + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.CR_PAT }} - - name: Push image to GitHub Container Registry + - name: Compute image tag + id: meta run: | - IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME - - # Change all uppercase to lowercase + IMAGE_ID=ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') - # Strip git ref prefix from version VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') - - # Strip "v" prefix from tag name [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') - - # Use Docker `latest` tag convention [ "$VERSION" == "main" ] && VERSION=latest - echo IMAGE_ID=$IMAGE_ID - echo VERSION=$VERSION + echo "image_id=$IMAGE_ID" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" - docker tag $IMAGE_NAME $IMAGE_ID:$VERSION - docker push $IMAGE_ID:$VERSION + - name: Build and push image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.image_id }}:${{ steps.meta.outputs.version }} + cache-from: type=gha + cache-to: type=gha,mode=max