Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
193 linesCodeBlameRaw
1# syntax=docker/dockerfile:1.7
2#
3# g1t-runner-base: everything a g1t sandbox has apart from the g1t runner
4# itself. Checks, the merge queue, workflow jobs and g1t.page
5# builds all run in it: git, Node, Python, Go, Rust (with the formatter,
6# the linter, and the wasm32 and musl targets), Java (Temurin 21), .NET
7# (SDK 8), Ruby (3.3), the usual build tools, Docker (Engine, Buildx,
8# Compose).
9#
10# Built and pushed by `node scripts/deploy.mjs build-base` (by hand, or by
11# .g1t/workflows/runner-base.yml), which records what it pushed in
12# services/runner/base.json. The runner's own image
13# (services/runner/Dockerfile) is this plus one file, so a change to the
14# runner builds in seconds. The deploy-to-cloudflare guide explains the two.
15#
16# Layers go from what changes least to what changes most: the system's
17# packages, Docker, then Go, then Rust, then Java, .NET and Ruby.
18#
19# Java and Ruby live in the runner's tool cache (RUNNER_TOOL_CACHE,
20# /home/runner/_tool), laid out as the setup actions look for them, so
21# `actions/setup-java` (temurin, 21) and `ruby/setup-ruby` (3.3) find them
22# there instead of downloading. .NET is in /usr/share/dotnet, where
23# `actions/setup-dotnet` installs, so it finds the SDK already there.
24#
25# Build context: this folder (nothing is copied from it).
26
27FROM node:24-bookworm-slim
28
29# Docs, man pages and translations are never read in a sandbox; dpkg
30# leaves them out of every package installed from here on (copyright
31# files stay). Downloaded packages are kept for the build's apt cache.
32RUN printf '%s\n' \
33 'path-exclude=/usr/share/doc/*' \
34 'path-include=/usr/share/doc/*/copyright' \
35 'path-exclude=/usr/share/man/*' \
36 'path-exclude=/usr/share/info/*' \
37 'path-exclude=/usr/share/groff/*' \
38 'path-exclude=/usr/share/lintian/*' \
39 'path-exclude=/usr/share/linda/*' \
40 'path-exclude=/usr/share/locale/*' \
41 'path-include=/usr/share/locale/locale.alias' \
42 > /etc/dpkg/dpkg.cfg.d/01-g1t-slim \
43 && rm -f /etc/apt/apt.conf.d/docker-clean \
44 && echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-downloads
45
46# The system's packages. The apt cache and package lists live in
47# BuildKit's cache, not the image (run `sudo apt-get update` before
48# installing more). dpkg skips its fsync after every file, which an image
49# build has no use for and which made this step several times slower.
50# musl-tools is musl-gcc, with which (and `rustup target add
51# x86_64-unknown-linux-musl`) the static g1t runner builds in a workflow
52# job (scripts/build-runner.mjs).
53RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
54 --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
55 apt-get update \
56 && apt-get install -y --no-install-recommends -o Dpkg::Options::=--force-unsafe-io \
57 git ca-certificates curl build-essential pkg-config libssl-dev \
58 python3 python3-pip python3-venv ripgrep jq zstd \
59 sudo unzip zip xz-utils wget file gnupg lsb-release openssh-client \
60 musl-tools \
61 && rm -rf /var/log/apt /var/log/dpkg.log /var/cache/debconf/*-old
62
63# Docker, for workflow jobs: the CLI with Buildx and Compose, and the
64# Engine (dockerd, containerd, runc), which the runner starts as a job's
65# own the first time the job uses it (crates/runner/src/docker). From
66# Docker's own repository, its signing key checked against the fingerprint
67# Docker publishes. Nothing here runs until a job asks for it.
68ARG DOCKER_KEY_FINGERPRINT=9DC858229FC7DD38854AE2D88D81803C0EBFCD88
69RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
70 --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
71 install -m 0755 -d /etc/apt/keyrings \
72 && curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc \
73 && GNUPGHOME="$(mktemp -d)" gpg --show-keys --with-colons /etc/apt/keyrings/docker.asc \
74 | grep -q "^fpr:::::::::${DOCKER_KEY_FINGERPRINT}:" \
75 && echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" \
76 > /etc/apt/sources.list.d/docker.list \
77 && apt-get update \
78 && apt-get install -y --no-install-recommends -o Dpkg::Options::=--force-unsafe-io \
79 docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin \
80 && rm -rf /var/log/apt /var/log/dpkg.log /var/cache/debconf/*-old \
81 && docker --version && dockerd --version && docker buildx version && docker compose version
82
83# Workflows expect GitHub's runner layout under /home/runner, and sudo
84# without a password. The agent works in /work.
85RUN mkdir /work && chown node:node /work \
86 && mkdir -p /home/runner/work /home/runner/_temp /home/runner/_tool /home/runner/_actions \
87 && chown -R node:node /home/runner \
88 && echo 'node ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/node \
89 && chmod 0440 /etc/sudoers.d/node
90
91# Go, from go.dev (Debian's is years behind), without its own test suite
92# and API history, which only Go's developers use.
93ARG GO_VERSION=1.27.1
94ARG GO_SHA256=63d339f0da5ab53635a56f2490a7984dfe12dfcff22ad749f63edaf590168445
95RUN curl -fsSLo /tmp/go.tgz "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" \
96 && echo "${GO_SHA256} /tmp/go.tgz" | sha256sum -c - \
97 && tar -C /usr/local -xzf /tmp/go.tgz \
98 && rm -rf /tmp/go.tgz /usr/local/go/test /usr/local/go/api /usr/local/go/doc \
99 && ln -s /usr/local/go/bin/go /usr/local/go/bin/gofmt /usr/local/bin/
100
101# Install the Rust toolchain for the unprivileged job user.
102USER node
103ENV HOME=/home/node
104ENV PATH=/home/node/.cargo/bin:/home/node/go/bin:$PATH
105
106# Rust stable, for the user the agent runs as, with the formatter and
107# linter that CI so often checks with (an agent that cannot run them only
108# finds out from a failed workflow), and the wasm32 target that Workers,
109# and g1t's own deploys, build for.
110RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
111 | sh -s -- -y --no-modify-path --profile minimal --default-toolchain stable \
112 --component rustfmt --component clippy --target wasm32-unknown-unknown \
113 && rm -rf /home/node/.rustup/downloads /home/node/.rustup/tmp \
114 /home/node/.rustup/toolchains/*/share/doc \
115 /home/node/.rustup/toolchains/*/share/man
116
117USER root
118
119# What Java, .NET and Ruby need from the system: ICU for .NET, and the
120# headers that gems with native extensions compile against (Ruby's own
121# were built with them).
122RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
123 --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
124 apt-get update \
125 && apt-get install -y --no-install-recommends -o Dpkg::Options::=--force-unsafe-io \
126 libicu72 libyaml-dev libffi-dev zlib1g-dev libgmp-dev libreadline-dev \
127 && rm -rf /var/log/apt /var/log/dpkg.log /var/cache/debconf/*-old
128
129# Java: Eclipse Temurin 21 (LTS), from Adoptium, in the tool cache under
130# the name `actions/setup-java` gives it (Adoptium's semver with `+`
131# written as `-`), with the `x64.complete` marker it checks for. The JDK's
132# source archive is left out.
133ARG TEMURIN_RELEASE=jdk-21.0.12.1+1
134ARG TEMURIN_TOOLCACHE_VERSION=21.0.12-101.0.LTS
135ARG TEMURIN_SHA256=ce79869e1307ed8ee1e2baa86a412b1eb5b75d10a01006d788a6f968bcfaee94
136RUN file="$(echo "${TEMURIN_RELEASE#jdk-}" | tr + _)" \
137 && tag="$(echo "${TEMURIN_RELEASE}" | sed 's/+/%2B/')" \
138 && curl -fsSLo /tmp/jdk.tgz "https://github.com/adoptium/temurin21-binaries/releases/download/${tag}/OpenJDK21U-jdk_x64_linux_hotspot_${file}.tar.gz" \
139 && echo "${TEMURIN_SHA256} /tmp/jdk.tgz" | sha256sum -c - \
140 && dir="/home/runner/_tool/Java_Temurin-Hotspot_jdk/${TEMURIN_TOOLCACHE_VERSION}" \
141 && mkdir -p "$dir/x64" \
142 && tar -C "$dir/x64" --strip-components=1 -xzf /tmp/jdk.tgz \
143 && rm -f /tmp/jdk.tgz "$dir/x64/lib/src.zip" \
144 && touch "$dir/x64.complete" \
145 && chown -R node:node /home/runner/_tool
146ENV JAVA_HOME=/home/runner/_tool/Java_Temurin-Hotspot_jdk/${TEMURIN_TOOLCACHE_VERSION}/x64
147ENV JAVA_HOME_21_X64=${JAVA_HOME}
148
149# .NET: the SDK 8 (LTS), from Microsoft's builds, checked against the
150# SHA-512 in its release metadata. In /usr/share/dotnet, owned by the
151# job's user, so `actions/setup-dotnet` can add other SDKs beside it. The
152# SDK's translated messages (about 100 MB) are left out: it speaks English.
153ARG DOTNET_SDK_VERSION=8.0.425
154ARG DOTNET_SDK_SHA512=934b8060a7190e5909ad1fd0785db542f487b3bbf6cdd14826b02095fdd0d0394298b1634085eff302928fccc33f7c1a7253e9b87df555fc36fce819bcd2e798
155RUN curl -fsSLo /tmp/dotnet.tgz "https://builds.dotnet.microsoft.com/dotnet/Sdk/${DOTNET_SDK_VERSION}/dotnet-sdk-${DOTNET_SDK_VERSION}-linux-x64.tar.gz" \
156 && echo "${DOTNET_SDK_SHA512} /tmp/dotnet.tgz" | sha512sum -c - \
157 && mkdir -p /usr/share/dotnet \
158 && tar -C /usr/share/dotnet -xzf /tmp/dotnet.tgz \
159 && rm /tmp/dotnet.tgz \
160 && find /usr/share/dotnet/sdk -type d \( -name cs -o -name de -o -name es -o -name fr -o -name it \
161 -o -name ja -o -name ko -o -name pl -o -name pt-BR -o -name ru -o -name tr -o -name zh-Hans -o -name zh-Hant \) \
162 -prune -exec rm -rf {} + \
163 && chown -R node:node /usr/share/dotnet \
164 && ln -s /usr/share/dotnet/dotnet /usr/local/bin/dotnet
165ENV DOTNET_ROOT=/usr/share/dotnet \
166 DOTNET_NOLOGO=1 \
167 DOTNET_CLI_TELEMETRY_OPTOUT=1 \
168 DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 \
169 DOTNET_MULTILEVEL_LOOKUP=0
170
171# Ruby 3.3, built from ruby-lang.org's source in the tool cache, where
172# `ruby/setup-ruby` looks on a Debian machine (its prebuilt Rubies are for
173# other systems), with the `x64.complete` marker it checks for. The prefix
174# is fixed when Ruby is built, so it is built in place. One step, so the
175# source and objects never reach a layer, and the previous base's inline
176# cache covers it.
177ARG RUBY_VERSION=3.3.12
178ARG RUBY_SHA256=b06d63beae271933033e27f0a389bc582a009e7845357d44365c39de525a051b
179RUN prefix="/home/runner/_tool/Ruby/${RUBY_VERSION}/x64" \
180 && curl -fsSLo /tmp/ruby.tgz "https://cache.ruby-lang.org/pub/ruby/${RUBY_VERSION%.*}/ruby-${RUBY_VERSION}.tar.gz" \
181 && echo "${RUBY_SHA256} /tmp/ruby.tgz" | sha256sum -c - \
182 && mkdir /tmp/ruby && tar -C /tmp/ruby --strip-components=1 -xzf /tmp/ruby.tgz \
183 && cd /tmp/ruby \
184 && ./configure --prefix="$prefix" --enable-shared --disable-install-doc \
185 && make -j"$(nproc)" \
186 && make install \
187 && cd / && rm -rf /tmp/ruby /tmp/ruby.tgz \
188 && "$prefix/bin/ruby" -ropenssl -rpsych -rzlib -rfiddle -e 'puts RUBY_DESCRIPTION' \
189 && touch "$prefix.complete" \
190 && chown -R node:node "/home/runner/_tool/Ruby"
191ENV PATH=${JAVA_HOME}/bin:/home/runner/_tool/Ruby/${RUBY_VERSION}/x64/bin:$PATH
192
193USER node