Skip to content

Commit 9700d33

Browse files
MinecraftFunsclaude
andcommitted
feat(code-server): Publish each toolchain as its own image
Extract the proot, Racket, Haskell, Go, and eBPF builder stages into standalone Dockerfiles, published as :code-server-{proot,racket,haskell, go,ebpf-tools}. Each stage body is copied byte for byte; the only addition is a scratch stage carrying the artifact at the path ./Dockerfile already reads it from. Scratch rather than the builder itself: these tags exist only to be read by a COPY --from, so republishing gcc or all of code-server-base beneath them twice a day would buy nothing. Phase one of two. The stages stay in ./Dockerfile, so the main image still builds unchanged and the toolchains are built twice per run -- deliberately more expensive for now. Deleting the inline stages and switching each COPY --from to the published tag is what turns five serial builds inside one task into five tasks the mesh can spread. Two things the consumer must keep when that switch happens: the --chown=${PUID}:${PGID} on each COPY, since these images carry root-owned files, and proot's rename to /usr/local/bin/proot. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 22bd8fa commit 9700d33

5 files changed

Lines changed: 269 additions & 0 deletions

File tree

code-server/ebpf-tools.Dockerfile

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# =============================================================================
2+
# Standalone build of the eBPF tools, published as :code-server-ebpf-tools
3+
#
4+
# Extracted verbatim from the ebpf_tools_builder stage of ./Dockerfile so it can
5+
# be built as its own task, in parallel with the other toolchains, instead of
6+
# serially inside the main image. The stage remains in ./Dockerfile for now;
7+
# that copy is retired once this tag exists in the registry.
8+
#
9+
# Currently includes bpftool, used for managing BPF programs and maps.
10+
# =============================================================================
11+
FROM ghcr.io/btreemap/dockerfiles:code-server-base AS ebpf_tools_builder
12+
13+
RUN set -ex && \
14+
# Determine target architecture
15+
ARCH=$(uname -m) && \
16+
case "$ARCH" in \
17+
x86_64) ARCH=amd64 ;; \
18+
aarch64|arm64) ARCH=arm64 ;; \
19+
*) echo "Unsupported arch: $ARCH" >&2; exit 1 ;; \
20+
esac && \
21+
echo "Fetching latest bpftool version" && \
22+
BPFTOOL_LATEST=$(curl -sSL https://api.github.com/repos/libbpf/bpftool/releases/latest | jq -r .tag_name) && \
23+
echo "Latest bpftool version: $BPFTOOL_LATEST" && \
24+
TARBALL="bpftool-${BPFTOOL_LATEST}-${ARCH}.tar.gz" && \
25+
URL="https://github.com/libbpf/bpftool/releases/download/${BPFTOOL_LATEST}/${TARBALL}" && \
26+
echo "Downloading $URL" && \
27+
curl -sSL -o /tmp/${TARBALL} ${URL} && \
28+
curl -sSL -o /tmp/${TARBALL}.sha256sum ${URL}.sha256sum && \
29+
(cd /tmp && sha256sum -c ${TARBALL}.sha256sum) && \
30+
mkdir -p $EBPF_TOOLS_HOME && \
31+
tar -C $EBPF_TOOLS_HOME -xzf /tmp/${TARBALL} && \
32+
# Make bpftool executable
33+
chmod +x $EBPF_TOOLS_HOME/bpftool && \
34+
rm -rf /tmp/*
35+
36+
# =============================================================================
37+
# Artifact image - carries the tools and nothing else
38+
#
39+
# Scratch rather than the builder: this tag exists only to be read by a
40+
# COPY --from, so republishing all of code-server-base underneath it twice a
41+
# day would buy nothing.
42+
#
43+
# /opt/ebpf-tools is code-server-base's $EBPF_TOOLS_HOME, repeated literally
44+
# because a scratch stage inherits no ENV. If the two ever diverge this COPY
45+
# fails the build outright rather than publishing an empty image.
46+
# =============================================================================
47+
FROM scratch
48+
49+
COPY --from=ebpf_tools_builder /opt/ebpf-tools /opt/ebpf-tools

code-server/go.Dockerfile

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# =============================================================================
2+
# Standalone build of the Go toolchain, published as :code-server-go
3+
#
4+
# Extracted verbatim from the go_builder stage of ./Dockerfile so it can be
5+
# built as its own task, in parallel with the other toolchains, instead of
6+
# serially inside the main image. The stage remains in ./Dockerfile for now;
7+
# that copy is retired once this tag exists in the registry.
8+
#
9+
# Downloads and installs the latest stable Go release for the detected
10+
# architecture.
11+
# =============================================================================
12+
FROM ghcr.io/btreemap/dockerfiles:code-server-base AS go_builder
13+
14+
RUN set -ex && \
15+
# Determine target architecture
16+
ARCH=$(uname -m) && \
17+
case "$ARCH" in \
18+
x86_64) ARCH=amd64 ;; \
19+
aarch64|arm64) ARCH=arm64 ;; \
20+
*) echo "Unsupported arch: $ARCH" >&2; exit 1 ;; \
21+
esac && \
22+
# Fetch Go releases JSON and extract latest stable version and checksum for our arch
23+
GO_JSON="$(curl -sSfL 'https://go.dev/dl/?mode=json')" && \
24+
GO_VERSION="$(echo "$GO_JSON" | jq -r '[.[] | select(.stable)][0].version')" && \
25+
TARBALL="${GO_VERSION}.linux-${ARCH}.tar.gz" && \
26+
GO_SHA256="$(echo "$GO_JSON" | jq -r --arg GO_VERSION "$GO_VERSION" --arg TARBALL "$TARBALL" '.[] | select(.version == $GO_VERSION) | .files[] | select(.filename == $TARBALL) | .sha256')" && \
27+
echo "Installing ${GO_VERSION} for ${ARCH}" && \
28+
curl -sSfL -o /tmp/go.tgz "https://go.dev/dl/${TARBALL}" && \
29+
echo "${GO_SHA256} /tmp/go.tgz" | sha256sum -c - && \
30+
tar -C /opt -xzf /tmp/go.tgz && \
31+
rm -rf /tmp/go.tgz && \
32+
$GO_HOME/bin/go version
33+
34+
# =============================================================================
35+
# Artifact image - carries the installation and nothing else
36+
#
37+
# Scratch rather than the builder: this tag exists only to be read by a
38+
# COPY --from, so republishing all of code-server-base underneath it twice a
39+
# day would buy nothing.
40+
#
41+
# /opt/go is code-server-base's $GO_HOME, repeated literally because a scratch
42+
# stage inherits no ENV. If the two ever diverge this COPY fails the build
43+
# outright rather than publishing an empty image.
44+
# =============================================================================
45+
FROM scratch
46+
47+
COPY --from=go_builder /opt/go /opt/go

code-server/haskell.Dockerfile

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# =============================================================================
2+
# Standalone build of the Haskell toolchain, published as :code-server-haskell
3+
#
4+
# Extracted verbatim from the haskell_builder stage of ./Dockerfile so it can be
5+
# built as its own task, in parallel with the other toolchains, instead of
6+
# serially inside the main image. The stage remains in ./Dockerfile for now;
7+
# that copy is retired once this tag exists in the registry.
8+
#
9+
# Sets up GHC (compiler), Cabal (package manager), and HLS (language server).
10+
# =============================================================================
11+
FROM ghcr.io/btreemap/dockerfiles:code-server-base AS haskell_builder
12+
13+
# Set environment variables for Haskell installation
14+
ENV GHCUP_HOME=/opt/ghcup \
15+
GHCUP_INSTALL_BASE_PREFIX=$HASKELL_HOME
16+
17+
# Set the working directory for Haskell installation
18+
WORKDIR $GHCUP_HOME
19+
20+
# Download the appropriate GHCup binary based on architecture
21+
RUN set -ex && \
22+
# Determine target architecture
23+
ARCH=$(uname -m) && \
24+
case "$ARCH" in \
25+
x86_64) ARCH=x86_64 ;; \
26+
aarch64|arm64) ARCH=aarch64 ;; \
27+
*) echo "Unsupported arch: $ARCH" >&2; exit 1 ;; \
28+
esac && \
29+
# Download GHCup installer
30+
GHCUP_URL="https://downloads.haskell.org/~ghcup/${ARCH}-linux-ghcup" && \
31+
curl -sSL "$GHCUP_URL" -o ghcup && \
32+
chmod +x ghcup
33+
34+
# Install GHC, Cabal, and HLS using GHCup
35+
RUN ./ghcup install ghc --set recommended && \
36+
./ghcup install cabal latest && \
37+
./ghcup install hls latest && \
38+
./ghcup gc --cache --hls-no-ghc --profiling-libs --tmpdirs && \
39+
rm -rf \
40+
$HASKELL_HOME/.ghcup/cache \
41+
$HASKELL_HOME/.ghcup/logs \
42+
$HASKELL_HOME/.ghcup/tmp \
43+
$HASKELL_HOME/.ghcup/trash
44+
45+
# =============================================================================
46+
# Artifact image - carries the installation and nothing else
47+
#
48+
# Scratch rather than the builder: this tag exists only to be read by a
49+
# COPY --from, so republishing all of code-server-base underneath it twice a
50+
# day would buy nothing.
51+
#
52+
# /opt/haskell is code-server-base's $HASKELL_HOME, repeated literally because a
53+
# scratch stage inherits no ENV. If the two ever diverge this COPY fails the
54+
# build outright rather than publishing an empty image. $GHCUP_HOME is
55+
# deliberately not carried: ./Dockerfile copies only $HASKELL_HOME.
56+
# =============================================================================
57+
FROM scratch
58+
59+
COPY --from=haskell_builder /opt/haskell /opt/haskell

code-server/proot.Dockerfile

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# =============================================================================
2+
# Standalone build of the proot binary, published as :code-server-proot
3+
#
4+
# Extracted verbatim from the proot_builder stage of ./Dockerfile so it can be
5+
# built as its own task, in parallel with the other toolchains, instead of
6+
# serially inside the main image. The stage remains in ./Dockerfile for now;
7+
# that copy is retired once this tag exists in the registry.
8+
#
9+
# proot allows for containerized chroot-like functionality without root
10+
# privileges.
11+
# =============================================================================
12+
FROM gcc:12.4.0-bookworm AS proot_builder
13+
14+
# Specify the proot version to ensure reproducible builds
15+
ARG PROOT_VERSION=v5.4.0
16+
ARG PROOT_REPOSITORY=https://github.com/BTreeMap/proot-2025-02-26-archive.git
17+
18+
# Install required build dependencies:
19+
# - git: For source code retrieval
20+
# - clang-tools: For static analysis during build
21+
# - libarchive-dev: For archive manipulation support
22+
# - libtalloc-dev: For memory allocation pooling
23+
# - Other tools for building, testing and documentation
24+
RUN set -eux && \
25+
apt-get update -y && \
26+
apt-get upgrade -y && \
27+
apt-get install -y \
28+
clang-tools-14 \
29+
curl \
30+
docutils-common \
31+
gdb \
32+
git \
33+
lcov \
34+
libarchive-dev \
35+
libtalloc-dev \
36+
strace \
37+
swig \
38+
uthash-dev \
39+
xsltproc
40+
41+
# Clone the specific version of proot from GitHub
42+
# Using --depth 1 to minimize download size (shallow clone)
43+
RUN git clone --depth 1 --branch $PROOT_VERSION $PROOT_REPOSITORY /proot
44+
45+
# Set the working directory for build operations
46+
WORKDIR /proot
47+
48+
# Compile a static version of proot for maximum portability
49+
# Static linking ensures the binary can run without external dependencies
50+
# The resulting binary will be copied to the final image
51+
RUN LDFLAGS="${LDFLAGS} -static" make -C src proot GIT=false && \
52+
mkdir -p dist && \
53+
cp src/proot dist/
54+
55+
# =============================================================================
56+
# Artifact image - carries the binary and nothing else
57+
#
58+
# Scratch rather than the gcc builder: this tag exists only to be read by a
59+
# COPY --from, so publishing the ~1.5 GB toolchain twice a day would buy
60+
# nothing. The path matches the one ./Dockerfile already copies from, which is
61+
# what lets the consumer switch by changing --from alone.
62+
# =============================================================================
63+
FROM scratch
64+
65+
COPY --from=proot_builder /proot/dist/proot /proot/dist/proot

code-server/racket.Dockerfile

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# =============================================================================
2+
# Standalone build of the Racket toolchain, published as :code-server-racket
3+
#
4+
# Extracted verbatim from the racket_builder stage of ./Dockerfile so it can be
5+
# built as its own task, in parallel with the other toolchains, instead of
6+
# serially inside the main image. The stage remains in ./Dockerfile for now;
7+
# that copy is retired once this tag exists in the registry.
8+
#
9+
# Racket is a general-purpose, multi-paradigm programming language in the
10+
# Lisp/Scheme family.
11+
# =============================================================================
12+
FROM ghcr.io/btreemap/dockerfiles:code-server-base AS racket_builder
13+
14+
# Set environment variables for Racket installation
15+
ENV BUILD_DIR=/build
16+
17+
# Specify the Racket version and download URL
18+
ARG RACKET_VERSION=8.16
19+
ARG RACKET_PACKAGE=racket-8.16-src-builtpkgs.tgz
20+
ARG RACKET_URL=https://download.racket-lang.org/releases/$RACKET_VERSION/installers/$RACKET_PACKAGE
21+
ARG RACKET_CHECKSUM=sha256:44d7c1ab34b52588f90dc22b15d96110e104d0c88ed1869f85b6f03c99843078
22+
23+
# Download the Racket source package
24+
ADD --checksum=$RACKET_CHECKSUM $RACKET_URL $BUILD_DIR/$RACKET_PACKAGE
25+
26+
# Set the working directory for build operations
27+
WORKDIR $BUILD_DIR
28+
29+
# Extract, configure, build, and install Racket
30+
RUN tar xfz $RACKET_PACKAGE && \
31+
cd racket-$RACKET_VERSION/src && \
32+
./configure --prefix=$RACKET_HOME && \
33+
make -j$(nproc) && \
34+
make install
35+
36+
# =============================================================================
37+
# Artifact image - carries the installation and nothing else
38+
#
39+
# Scratch rather than the builder: this tag exists only to be read by a
40+
# COPY --from, so republishing all of code-server-base underneath it twice a
41+
# day would buy nothing.
42+
#
43+
# /opt/racket is code-server-base's $RACKET_HOME, repeated literally because a
44+
# scratch stage inherits no ENV. If the two ever diverge this COPY fails the
45+
# build outright rather than publishing an empty image.
46+
# =============================================================================
47+
FROM scratch
48+
49+
COPY --from=racket_builder /opt/racket /opt/racket

0 commit comments

Comments
 (0)