From 77d37db950df0a6ae42938113d48209651f5e760 Mon Sep 17 00:00:00 2001 From: Asen Lekov Date: Mon, 19 Oct 2026 20:13:39 +0300 Subject: [PATCH 1/2] refactor(image): share a fixed-JDK SDK layer across tags and make the NDK opt-in - sdk stage builds the Android SDK on a fixed JDK (SDK_JAVA_VERSION) and is copied in right on top of the base image, before the shared apt layer, the JDK and Node, so the SDK layer is the same for every JDK/Node tag - one apt layer for the tag-independent packages (iproute2, cmake, build-essential, swig, xxd, pigz) with the apt lists removed - ndk and cmake;3.22.1 move to the opt-in runner-ndk target; runner stays the last stage and is the default - COMPILE_SDK 36.1 -> 37.0 (stable) - COPY --chmod instead of a separate chmod layer Co-Authored-By: Claude Sonnet 5.5 --- Dockerfile | 206 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 140 insertions(+), 66 deletions(-) diff --git a/Dockerfile b/Dockerfile index d90f1a4..ae746e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,93 +1,145 @@ ARG VERSION=2.338.0-ubuntu-noble ARG JAVA_VERSION=21 ARG NODE_VERSION=24 -ARG COMPILE_SDK=36.1 +ARG COMPILE_SDK=37.0 ARG BUILD_TOOLS=37.0.0 ARG NDK_VERSION=30.0.16248370 ARG SDK_TOOLS=8512546_latest ARG ANDROID_ROOT=/usr/local/lib/android +# JDK that sdkmanager runs on in the sdk stages. Fixed on purpose and independent +# of JAVA_VERSION: the SDK layer must come out identical for every JDK/Node tag, +# so it is shared in the registry and on hosts instead of stored once per tag. +ARG SDK_JAVA_VERSION=21 ################################################################################ -# base image from https://github.com/myoung34/docker-github-actions-runner -# - install temurin java into it -################################################################################ -FROM myoung34/github-runner:$VERSION AS java -ARG JAVA_VERSION -ARG NODE_VERSION -# TARGETARCH is set automatically by Buildx (e.g. amd64, arm64) and matches the -# arch suffix Temurin uses on its apt-installed JDK directory. -ARG TARGETARCH - -# https://adoptium.net/installation/linux -# add the gpp key and apt repo -RUN mkdir -p /etc/apt/keyrings && \ - wget -O - https://packages.adoptium.net/artifactory/api/gpg/key/public | tee /etc/apt/keyrings/adoptium.asc && \ - echo "deb [signed-by=/etc/apt/keyrings/adoptium.asc] https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main" | tee /etc/apt/sources.list.d/adoptium.list - -# install the temurin jdk -RUN apt-get -qq update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends temurin-$JAVA_VERSION-jdk -ENV JAVA_HOME=/usr/lib/jvm/temurin-$JAVA_VERSION-jdk-$TARGETARCH - -# install ip tools -RUN apt-get -qq update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends iproute2 - -# install cmake build-essential -RUN apt-get -qq update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends cmake build-essential swig - -# install nodejs from NodeSource. Replaces the base image's Ubuntu-default node -# (currently node 18 on noble) with a current LTS that user workflows can rely -# on without an extra setup-node step. -RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - \ - && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends nodejs \ - && rm -rf /var/lib/apt/lists/* - -################################################################################ -# - install android sdk +# sdk: the Android SDK, built once for every tag +# - runs on a fixed JDK (SDK_JAVA_VERSION), never on JAVA_VERSION/NODE_VERSION +# - only the build args below reach this stage, so a JDK or Node bump does not +# invalidate it ################################################################################ -FROM java AS android +FROM eclipse-temurin:${SDK_JAVA_VERSION}-jdk-noble AS sdk ARG COMPILE_SDK ARG BUILD_TOOLS -ARG NDK_VERSION ARG SDK_TOOLS ARG ANDROID_ROOT -WORKDIR /tmp - -# download android tools and use it to install the SDK -RUN mkdir -p ${ANDROID_ROOT}/sdk/cmdline-tools/latest -RUN wget -O android-sdk.zip https://dl.google.com/android/repository/commandlinetools-linux-$SDK_TOOLS.zip && unzip android-sdk.zip && cp -r ./cmdline-tools/* ${ANDROID_ROOT}/sdk/cmdline-tools/latest -RUN ${ANDROID_ROOT}/sdk/cmdline-tools/latest/bin/sdkmanager --licenses >/dev/null +RUN apt-get -qq update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends unzip wget \ + && rm -rf /var/lib/apt/lists/* -# Install latest Android SDK components only. Older versions are not needed since -# build-tools and NDK are backward compatible, and you only need the platform -# matching your project's compileSdk. Projects needing older platforms can install -# them at build time via sdkmanager. -# # sdkmanager has no download retry: a truncated download from dl.google.com # surfaces as "Archive is not a ZIP archive" and fails the whole build. It is # idempotent (already-installed packages are skipped), so retry the whole # command a few times, clearing its download scratch dir between attempts. -RUN attempt=1; \ - until echo "y" | ${ANDROID_ROOT}/sdk/cmdline-tools/latest/bin/sdkmanager --sdk_root=$ANDROID_ROOT/sdk/ \ +# Usage: sdk-install ... +RUN cat > /usr/local/bin/sdk-install <<'EOF' && chmod +x /usr/local/bin/sdk-install +#!/bin/sh +set -u +sdk_root=$1 +shift +attempt=1 +until echo "y" | "${ANDROID_ROOT}/sdk/cmdline-tools/latest/bin/sdkmanager" --sdk_root="$sdk_root" "$@"; do + if [ "$attempt" -ge 3 ]; then + echo "sdkmanager failed after $attempt attempts" >&2 + exit 1 + fi + echo "sdkmanager attempt $attempt failed, retrying in 15s..." >&2 + rm -rf "$sdk_root/.temp" + attempt=$((attempt + 1)) + sleep 15 +done +EOF + +# download android tools and use it to install the SDK +WORKDIR /tmp +RUN mkdir -p ${ANDROID_ROOT}/sdk/cmdline-tools/latest \ + && wget -q -O android-sdk.zip https://dl.google.com/android/repository/commandlinetools-linux-$SDK_TOOLS.zip \ + && unzip -q android-sdk.zip \ + && cp -r ./cmdline-tools/* ${ANDROID_ROOT}/sdk/cmdline-tools/latest \ + && rm -rf android-sdk.zip cmdline-tools +RUN ${ANDROID_ROOT}/sdk/cmdline-tools/latest/bin/sdkmanager --licenses >/dev/null + +# Install latest Android SDK components only. Older versions are not needed since +# build-tools are backward compatible, and you only need the platform matching +# your project's compileSdk. Projects needing older platforms can install them at +# build time via sdkmanager. The NDK and cmake live in the opt-in sdk-ndk stage. +RUN sdk-install ${ANDROID_ROOT}/sdk \ "platform-tools" \ "platforms;android-${COMPILE_SDK}" \ "build-tools;${BUILD_TOOLS}" \ + && rm -rf ${ANDROID_ROOT}/sdk/.temp + +################################################################################ +# sdk-ndk: NDK and the SDK's own cmake, for the opt-in runner-ndk target +# - installed into a separate sdk root, so the final image gets only the delta +# (ndk/ and cmake/) instead of a second full copy of the SDK +################################################################################ +FROM sdk AS sdk-ndk +ARG NDK_VERSION +ARG ANDROID_ROOT + +RUN mkdir -p /ndk-sdk && cp -r ${ANDROID_ROOT}/sdk/licenses /ndk-sdk/licenses +RUN sdk-install /ndk-sdk \ "ndk;${NDK_VERSION}" \ - "cmake;3.22.1"; do \ - if [ "$attempt" -ge 3 ]; then echo "sdkmanager failed after $attempt attempts" >&2; exit 1; fi; \ - echo "sdkmanager attempt $attempt failed, retrying in 15s..." >&2; \ - rm -rf ${ANDROID_ROOT}/sdk/.temp; \ - attempt=$((attempt + 1)); \ - sleep 15; \ - done + "cmake;3.22.1" \ + && rm -rf /ndk-sdk/.temp -WORKDIR /actions-runner -ENV PATH="${PATH}:/usr/local/lib/android/sdk/platform-tools/" +################################################################################ +# core: everything both runner targets share +# base image from https://github.com/myoung34/docker-github-actions-runner +# - the SDK comes first, straight on top of the base image, so its layer chain is +# identical for every JDK/Node tag +# - then the tag-independent apt packages, then the JDK, then Node +################################################################################ +FROM myoung34/github-runner:$VERSION AS core +ARG ANDROID_ROOT + +COPY --from=sdk /usr/local/lib/android /usr/local/lib/android # Set env variable for SDK Root (https://developer.android.com/studio/command-line/variables) # ANDROID_HOME is deprecated, but older versions of Gradle rely on it ENV ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk ENV ANDROID_HOME=$ANDROID_ROOT/sdk +ENV PATH="${PATH}:/usr/local/lib/android/sdk/platform-tools/" + +# Packages that do not depend on JAVA_VERSION/NODE_VERSION, so this layer is +# shared by every tag too. +# - iproute2: ip tools +# - cmake, build-essential, swig: native builds +# - xxd: not in the base image (https://github.com/compscidr/docker-github-actions-runner-android/issues/84) +# - pigz: parallel gzip, used by tar/docker when available +RUN apt-get -qq update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + iproute2 cmake build-essential swig xxd pigz \ + && rm -rf /var/lib/apt/lists/* + +# JAVA_VERSION and NODE_VERSION are declared only now, below the layers every tag +# shares: a build arg is part of the cache key of each RUN that follows its ARG, +# so declaring them at the top would rebuild (and re-hash) those layers per tag. +ARG JAVA_VERSION +ARG NODE_VERSION +# TARGETARCH is set automatically by Buildx (e.g. amd64, arm64) and matches the +# arch suffix Temurin uses on its apt-installed JDK directory. +ARG TARGETARCH + +# https://adoptium.net/installation/linux +# add the gpg key and apt repo, then install the temurin jdk +RUN mkdir -p /etc/apt/keyrings \ + && wget -qO /etc/apt/keyrings/adoptium.asc https://packages.adoptium.net/artifactory/api/gpg/key/public \ + && echo "deb [signed-by=/etc/apt/keyrings/adoptium.asc] https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main" > /etc/apt/sources.list.d/adoptium.list \ + && apt-get -qq update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends temurin-$JAVA_VERSION-jdk \ + && rm -rf /var/lib/apt/lists/* +ENV JAVA_HOME=/usr/lib/jvm/temurin-$JAVA_VERSION-jdk-$TARGETARCH + +# install nodejs from NodeSource. Replaces the base image's Ubuntu-default node +# (currently node 18 on noble) with a current LTS that user workflows can rely +# on without an extra setup-node step. +RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends nodejs \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /actions-runner LABEL maintainer="asen.lekov@dodi.li" # Jobs run as root here, and GNU tar run as root keeps the owner ids stored in an @@ -117,12 +169,13 @@ ENV MISE_DATA_DIR=/root/.local/share/mise \ # Job hooks: JOB_STARTED is the poison sweep (covers OOM / killed runner); # JOB_COMPLETED is the tidy-up. Neither wipes package / Gradle caches. -COPY pre-job.sh /usr/local/bin/pre-job.sh -COPY cleanup.sh /usr/local/bin/cleanup.sh # pruner.sh is not a hook: the `pruner` compose service runs it nightly # (LRU cache caps + docker prune), keeping those scans off the job path. -COPY pruner.sh /usr/local/bin/pruner.sh -RUN chmod +x /usr/local/bin/pre-job.sh /usr/local/bin/cleanup.sh /usr/local/bin/pruner.sh +# --chmod sets the mode in the COPY layer itself, so no separate chmod layer +# holds a second copy of the scripts. +COPY --chmod=755 pre-job.sh /usr/local/bin/pre-job.sh +COPY --chmod=755 cleanup.sh /usr/local/bin/cleanup.sh +COPY --chmod=755 pruner.sh /usr/local/bin/pruner.sh ENV ACTIONS_RUNNER_HOOK_JOB_STARTED=/usr/local/bin/pre-job.sh ENV ACTIONS_RUNNER_HOOK_JOB_COMPLETED=/usr/local/bin/cleanup.sh @@ -132,8 +185,17 @@ ENV ACTIONS_RUNNER_HOOK_JOB_COMPLETED=/usr/local/bin/cleanup.sh # crash-loops the container when the runner was removed server-side; the # preflight wipes provably-dead registrations so a fresh ACCESS_TOKEN # registration happens instead. See preflight-entrypoint.sh for details. -COPY preflight-entrypoint.sh /usr/local/bin/preflight-entrypoint.sh -RUN chmod +x /usr/local/bin/preflight-entrypoint.sh +COPY --chmod=755 preflight-entrypoint.sh /usr/local/bin/preflight-entrypoint.sh + +################################################################################ +# runner-ndk (opt-in, `--target runner-ndk`): core + NDK + the SDK's cmake +# - the NDK and cmake;3.22.1 are several GB that most consumers never use +################################################################################ +FROM core AS runner-ndk +ARG ANDROID_ROOT +COPY --from=sdk-ndk /ndk-sdk/ndk ${ANDROID_ROOT}/sdk/ndk +COPY --from=sdk-ndk /ndk-sdk/cmake ${ANDROID_ROOT}/sdk/cmake + # Image identity. deploy.yml passes the image tag and the commit it was built # from; jobs read them (e.g. the runner report) to tell which image they ran # on. They default to "dev" for local builds. Declared last: the values change @@ -149,3 +211,15 @@ ENTRYPOINT ["/usr/local/bin/preflight-entrypoint.sh"] # https://github.com/myoung34/docker-github-actions-runner#environment-variables # for how to use the image CMD ["./bin/Runner.Listener", "run", "--startuptype", "service"] + +################################################################################ +# runner (default, MUST stay the last stage so a plain `docker build` makes it) +################################################################################ +FROM core AS runner +ARG RUNNER_IMAGE_TAG=dev +ARG RUNNER_IMAGE_REVISION=dev +ENV RUNNER_IMAGE_TAG=$RUNNER_IMAGE_TAG \ + RUNNER_IMAGE_REVISION=$RUNNER_IMAGE_REVISION + +ENTRYPOINT ["/usr/local/bin/preflight-entrypoint.sh"] +CMD ["./bin/Runner.Listener", "run", "--startuptype", "service"] From b64ae0415de0f40e9104ac03162ec1e3083900d4 Mon Sep 17 00:00:00 2001 From: Asen Lekov Date: Mon, 19 Oct 2026 20:14:56 +0300 Subject: [PATCH 2/2] refactor(image): keep COMPILE_SDK at 36.1, version bumps land via the upstream sync Co-Authored-By: Claude Sonnet 5.5 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ae746e6..5880c1f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ARG VERSION=2.338.0-ubuntu-noble ARG JAVA_VERSION=21 ARG NODE_VERSION=24 -ARG COMPILE_SDK=37.0 +ARG COMPILE_SDK=36.1 ARG BUILD_TOOLS=37.0.0 ARG NDK_VERSION=30.0.16248370 ARG SDK_TOOLS=8512546_latest