Compare commits
22
Commits
20d3660485
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4350f40b5a | ||
|
|
1452c4ac96 | ||
|
|
2e2438672f | ||
|
|
736319458c | ||
|
|
adbc3cd6e7 | ||
|
|
08b0fedf6f | ||
|
|
f0307c6558 | ||
|
|
0c3a55a8cf | ||
|
|
9982d6d098 | ||
|
|
c2795b0bba | ||
|
|
a785bb2af0 | ||
|
|
4a4e5d5f6d | ||
|
|
516b61018e | ||
|
|
8c36d01e2e | ||
|
|
1907fd360d | ||
|
|
8c7eccf948 | ||
|
|
ff5113a9cd | ||
|
|
8feeb2b428 | ||
|
|
ea7f49fe9f | ||
|
|
2bfbffd5c5 | ||
|
|
c33760b3af | ||
|
|
a6645add28 |
@@ -9,9 +9,9 @@ on:
|
||||
inputs:
|
||||
godot_version:
|
||||
description: >-
|
||||
Godot version to (re)build, e.g. 4.7.1 — must exist as a
|
||||
barichello/godot-ci tag (https://hub.docker.com/r/barichello/godot-ci/tags).
|
||||
Leave empty to build the newest stable, same as the nightly run.
|
||||
Godot version to (re)build, e.g. 4.7.1 — must exist as a stable release at
|
||||
https://github.com/godotengine/godot-builds/releases. Leave empty to build
|
||||
the newest stable, same as the nightly run.
|
||||
required: false
|
||||
type: string
|
||||
|
||||
@@ -40,12 +40,16 @@ jobs:
|
||||
UPDATE_LATEST=true
|
||||
fi
|
||||
|
||||
if ! docker manifest inspect "barichello/godot-ci:${VERSION}" >/dev/null 2>&1; then
|
||||
# This image no longer depends on barichello/godot-ci as a base — it downloads Godot
|
||||
# directly from godot-builds' own GitHub releases (see Dockerfile), so the gate here
|
||||
# is against *that* release existing, not a third party's Docker tag.
|
||||
RELEASE_URL="https://github.com/godotengine/godot-builds/releases/download/${VERSION}-stable/Godot_v${VERSION}-stable_linux.x86_64.zip"
|
||||
if ! curl -fsSL -o /dev/null -r 0-0 "$RELEASE_URL"; then
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "::error::No barichello/godot-ci tag for '${VERSION}'. Check https://hub.docker.com/r/barichello/godot-ci/tags"
|
||||
echo "::error::No godot-builds stable release for '${VERSION}'. Check https://github.com/godotengine/godot-builds/releases"
|
||||
exit 1
|
||||
fi
|
||||
echo "::notice::Newest Godot stable is ${VERSION} but barichello/godot-ci has no matching tag yet — skipping, will retry tomorrow."
|
||||
echo "::notice::Newest Godot stable is ${VERSION} but its godot-builds release isn't up yet — skipping, will retry tomorrow."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
+41
-10
@@ -1,16 +1,25 @@
|
||||
# GODOT_VERSION selects the barichello/godot-ci base tag (Godot 4.0+ only — see
|
||||
# docs/handoff/godot-ci-custom-image.md). BLENDER_URL is the tarball to bake in;
|
||||
# GODOT_VERSION is a stable Godot release tag (e.g. "4.7.1", no "-stable" suffix — Godot 4.0+
|
||||
# only, see docs/handoff/godot-ci-custom-image.md). BLENDER_URL is the tarball to bake in.
|
||||
# .gitea/workflows/publish.yml always resolves both at build time via
|
||||
# scripts/resolve-godot-version.sh and scripts/resolve-blender-url.sh. The
|
||||
# defaults below are only a fallback for a plain local `docker build`.
|
||||
# scripts/resolve-godot-version.sh and scripts/resolve-blender-url.sh. The defaults below are
|
||||
# only a fallback for a plain local `docker build`.
|
||||
#
|
||||
# Godot itself is downloaded straight from its official GitHub releases (godot-builds), the same
|
||||
# source barichello/godot-ci's own Dockerfile uses — this image no longer depends on that image
|
||||
# as a base. Decided via wayfinder map https://git.arthurerlich.de/haylan/godot-ci/issues/2,
|
||||
# ticket https://git.arthurerlich.de/haylan/godot-ci/issues/7: Alpine was considered and rejected
|
||||
# (official Godot/Blender binaries are glibc-only, and Alpine's musl-native alternatives live
|
||||
# only on its unpinned `edge` repo); a plain glibc-slim base doesn't cost the compatibility risk.
|
||||
ARG GODOT_VERSION=4.7.1
|
||||
FROM barichello/godot-ci:${GODOT_VERSION}
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
ARG GODOT_VERSION
|
||||
ARG BLENDER_URL=https://download.blender.org/release/Blender4.2/blender-4.2.3-linux-x64.tar.xz
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates curl unzip xz-utils \
|
||||
libfontconfig1 \
|
||||
build-essential scons pkg-config xz-utils curl \
|
||||
build-essential scons pkg-config \
|
||||
libx11-dev libxcursor-dev libxinerama-dev libgl1-mesa-dev \
|
||||
libasound2-dev libpulse-dev libudev-dev libxi-dev libxrandr-dev libwayland-dev \
|
||||
mingw-w64 \
|
||||
@@ -21,11 +30,33 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
&& update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix \
|
||||
&& update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
# Blender is not bundled in barichello/godot-ci, and Ubuntu's apt package lags
|
||||
# (stuck on 4.0.2 on noble) — install the resolved tarball straight from
|
||||
# download.blender.org instead of `apt-get install blender`.
|
||||
# Godot editor + export templates, straight from the official release (same URL shape
|
||||
# barichello/godot-ci's own Dockerfile uses) — stable releases only, matching what
|
||||
# scripts/resolve-godot-version.sh resolves.
|
||||
&& curl -fsSLO "https://github.com/godotengine/godot-builds/releases/download/${GODOT_VERSION}-stable/Godot_v${GODOT_VERSION}-stable_linux.x86_64.zip" \
|
||||
&& curl -fsSLO "https://github.com/godotengine/godot-builds/releases/download/${GODOT_VERSION}-stable/Godot_v${GODOT_VERSION}-stable_export_templates.tpz" \
|
||||
&& mkdir -p "/root/.local/share/godot/export_templates/${GODOT_VERSION}.stable" \
|
||||
&& unzip -q "Godot_v${GODOT_VERSION}-stable_linux.x86_64.zip" \
|
||||
&& mv "Godot_v${GODOT_VERSION}-stable_linux.x86_64" /usr/local/bin/godot \
|
||||
&& unzip -q "Godot_v${GODOT_VERSION}-stable_export_templates.tpz" \
|
||||
&& mv templates/* "/root/.local/share/godot/export_templates/${GODOT_VERSION}.stable/" \
|
||||
&& rm -rf "Godot_v${GODOT_VERSION}-stable_linux.x86_64.zip" "Godot_v${GODOT_VERSION}-stable_export_templates.tpz" templates \
|
||||
# Blender is not bundled anywhere here — install the resolved tarball straight from
|
||||
# download.blender.org instead of an apt package.
|
||||
&& curl -fsSL "$BLENDER_URL" -o /tmp/blender.tar.xz \
|
||||
&& mkdir -p /opt/blender \
|
||||
&& tar -xJf /tmp/blender.tar.xz -C /opt/blender --strip-components=1 \
|
||||
&& ln -sf /opt/blender/blender /usr/local/bin/blender \
|
||||
&& rm /tmp/blender.tar.xz
|
||||
&& rm /tmp/blender.tar.xz \
|
||||
# Godot's headless `--import`/`--export-release` refuses to auto-detect Blender for .blend
|
||||
# import ("Blender path is invalid or not set... Cannot configure blender path in headless
|
||||
# mode") — it has to be pre-baked into EditorSettings, same as barichello/godot-ci bakes
|
||||
# Android SDK/JDK paths into its editor_settings.tres. Settings are stored per minor version
|
||||
# since Godot 4.3 (see abarichello/godot-ci's own Dockerfile for this same pattern).
|
||||
&& mkdir -p /root/.config/godot \
|
||||
&& printf '%s\n' \
|
||||
'[gd_resource type="EditorSettings" format=3]' \
|
||||
'' \
|
||||
'[resource]' \
|
||||
'filesystem/import/blender/blender_path = "/usr/local/bin/blender"' \
|
||||
> "/root/.config/godot/editor_settings-${GODOT_VERSION%.*}.tres"
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
# godot-ci
|
||||
|
||||
Custom image extending `barichello/godot-ci` with the Godot export-template toolchain and Blender
|
||||
A `debian:bookworm-slim`-based image with Godot, its export-template toolchain, and Blender
|
||||
pre-installed, so `Project4x`'s `export-template.yml` doesn't `apt-get install` them on every
|
||||
release run.
|
||||
|
||||
Godot is downloaded directly from its official [godot-builds
|
||||
releases](https://github.com/godotengine/godot-builds/releases) rather than from a base image —
|
||||
this repo previously extended `barichello/godot-ci` (Ubuntu-based) but moved off it to shed that
|
||||
dependency's weight. Alpine was considered and rejected first: official Godot and Blender
|
||||
binaries are glibc-only, and Alpine's musl-native alternatives for both live only on its unpinned
|
||||
`edge` repo — not a safe bet for a nightly-rebuilding image. See [wayfinder map
|
||||
#2](https://git.arthurerlich.de/haylan/godot-ci/issues/2) for the full research trail.
|
||||
|
||||
Built from the handoff spec at `Project4x`'s `docs/handoff/godot-ci-custom-image.md`; scope and
|
||||
versioning have since moved on from that spec — see below.
|
||||
|
||||
@@ -22,13 +30,13 @@ this image's Blender toolchain).
|
||||
- **Nightly (midnight, `schedule:`)**: resolves the newest *stable* Godot release
|
||||
(`scripts/resolve-godot-version.sh`) and the newest stable Blender
|
||||
(`scripts/resolve-blender-url.sh`), and rebuilds+pushes `<version>` + `latest` only if that
|
||||
Godot version isn't already published. If `barichello/godot-ci` hasn't tagged the new Godot
|
||||
version yet, the run skips (with a notice, not a failure) and retries the next night.
|
||||
Godot version isn't already published. If godot-builds hasn't published that release's assets
|
||||
yet, the run skips (with a notice, not a failure) and retries the next night.
|
||||
- **Manual (`workflow_dispatch`)**: type a Godot version (e.g. `4.6.3`) to (re)build and push that
|
||||
exact tag, always with whatever Blender is newest at build time. Overwrites the tag if it
|
||||
already exists. Does **not** touch `latest`. Fails clearly if `barichello/godot-ci` has no
|
||||
matching tag — check
|
||||
[its tag list](https://hub.docker.com/r/barichello/godot-ci/tags) first.
|
||||
already exists. Does **not** touch `latest`. Fails clearly if godot-builds has no matching
|
||||
release — check its [releases list](https://github.com/godotengine/godot-builds/releases)
|
||||
first.
|
||||
|
||||
Blender has no official "latest stable" API — `resolve-blender-url.sh` scrapes
|
||||
`download.blender.org/release/`. `scripts/test-lib.sh` is an offline smoke test for the parsing
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"enabledPlugins": {
|
||||
"mattpocock-skills@claude-plugins-official": true
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
# Handoff spec: custom godot-ci image
|
||||
|
||||
Status: **done, and superseded** — the `godot-ci` repo this spec asked for exists and is built.
|
||||
Its versioning has since moved past what's written below: instead of one Dockerfile pinned to
|
||||
`4.7.1`, it now auto-tracks the newest stable Godot (4.0+) and newest stable Blender on a nightly
|
||||
schedule, with a manual `workflow_dispatch` to (re)build any specific version. See that repo's
|
||||
`README.md` for the current behavior; this file is kept for the original problem statement and
|
||||
scope rationale, not as a source of truth on tags/versions. Decided via wayfinder map
|
||||
[#158](https://git.arthurerlich.de/haylan/Project4x/issues/158), ticket
|
||||
[#162](https://git.arthurerlich.de/haylan/Project4x/issues/162).
|
||||
|
||||
## Problem
|
||||
|
||||
`export-template.yml`'s `export` job runs in `barichello/godot-ci:4.7.1` and, on every single run,
|
||||
`apt-get install`s a from-source Godot export-template toolchain before it can build anything. That
|
||||
install is pure dead time repeated on every release. A custom image extending
|
||||
`barichello/godot-ci:4.7.1` with the toolchain pre-installed removes it.
|
||||
|
||||
## What this spec is for
|
||||
|
||||
A fresh Gitea agent session, given only this file, must be able to:
|
||||
|
||||
1. Create a new repo.
|
||||
2. Add a `Dockerfile` and a publish workflow to it.
|
||||
3. Get the image published to that repo's Gitea package (container) registry.
|
||||
4. Report back the final image reference so `export-template.yml` here can be pointed at it.
|
||||
|
||||
No other context from this repo should be required.
|
||||
|
||||
## Scope: what goes in the image
|
||||
|
||||
Everything `export-template.yml`'s "Update and install dependencies" step currently
|
||||
`apt-get install`s, unconditionally (all of it — see [Decisions](#decisions) below for why the
|
||||
narrower alternative was rejected):
|
||||
|
||||
```
|
||||
libfontconfig1
|
||||
build-essential scons pkg-config xz-utils curl
|
||||
# Linux leg:
|
||||
libx11-dev libxcursor-dev libxinerama-dev libgl1-mesa-dev \
|
||||
libasound2-dev libpulse-dev libudev-dev libxi-dev libxrandr-dev libwayland-dev
|
||||
# Windows leg:
|
||||
mingw-w64
|
||||
# + these two update-alternatives calls, baked in at image build time:
|
||||
update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix
|
||||
update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix
|
||||
# conditionally, when ENABLE_BLENDER is on:
|
||||
blender
|
||||
```
|
||||
|
||||
Current source of truth for this list: `.gitea/workflows/export-template.yml` in
|
||||
`haylan/Project4x`, "Update and install dependencies" step. **Re-check that step before building**
|
||||
— it may have drifted since this spec was written.
|
||||
|
||||
### What stays out of the image
|
||||
|
||||
- **The SCons object cache** (`.scons_cache`, restored via `actions/cache@v3` keyed on
|
||||
`scons-${GODOT_VERSION}-${artifact_name}`). This is build _state_, not a package — baking it into
|
||||
the image would pin it to image-build time instead of the actual release run, going stale
|
||||
immediately.
|
||||
- **`nsis` / `osslsigncode`.** These do NOT belong in this image. They run in `release.yml`'s
|
||||
`create-release` job, which is a plain `ubuntu-latest` runner — it does not use
|
||||
`barichello/godot-ci` or any container at all. (Earlier drafts of this ticket assumed they were
|
||||
part of the export container's toolchain; that was wrong. The NSIS installer has since been
|
||||
removed entirely — see ADR 0001's superseded note — so only `osslsigncode`/`zip` remain in
|
||||
`create-release`, and neither is expensive enough to justify its own image.)
|
||||
|
||||
## Registry path and versioning
|
||||
|
||||
- **Registry:** this Gitea instance's container/package registry (`git.arthurerlich.de`), scoped
|
||||
to the new repo you create — Gitea packages are per-repo/owner, so the path is
|
||||
`git.arthurerlich.de/haylan/<new-repo-name>`.
|
||||
- **Tag:** pin to the Godot version only, e.g. `git.arthurerlich.de/haylan/<new-repo-name>:4.7.1`.
|
||||
This mirrors `barichello/godot-ci`'s own tag scheme and `Project4x`'s `GODOT_VERSION` env var. A
|
||||
Godot version bump in `Project4x` means building and pushing a new tag here too — that coupling
|
||||
is intentional, not an oversight.
|
||||
- Pick `<new-repo-name>` yourself (e.g. `godot-ci-toolchain` or similar); it isn't fixed by this
|
||||
spec.
|
||||
|
||||
## Auth: PAT required
|
||||
|
||||
Confirmed for this Gitea instance: `GITEA_TOKEN` (the automatic per-run Actions token) **cannot**
|
||||
push to the package/container registry. The new repo's publish workflow needs a **Personal Access
|
||||
Token** stored as a repo secret (e.g. `REGISTRY_PUSH_PAT`).
|
||||
|
||||
- **Scope needed:** `write:package` (add `read:package` too if the publish workflow ever needs to
|
||||
pull the image back, e.g. to test it before tagging `latest`).
|
||||
- Generate it from the account that should own the published packages, under
|
||||
Settings → Applications → Generate New Token, with only that scope checked.
|
||||
- Document the token's owner/scope in the new repo's README once created — this repo
|
||||
(`Project4x`) has no record of it and shouldn't need one; the coupling is one-way
|
||||
(`Project4x` only ever pulls the finished image, never pushes to it).
|
||||
|
||||
## Dockerfile skeleton
|
||||
|
||||
```dockerfile
|
||||
FROM barichello/godot-ci:4.7.1
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libfontconfig1 \
|
||||
build-essential scons pkg-config xz-utils curl \
|
||||
libx11-dev libxcursor-dev libxinerama-dev libgl1-mesa-dev \
|
||||
libasound2-dev libpulse-dev libudev-dev libxi-dev libxrandr-dev libwayland-dev \
|
||||
mingw-w64 \
|
||||
blender \
|
||||
&& update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix \
|
||||
&& update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
```
|
||||
|
||||
Notes for whoever builds this:
|
||||
|
||||
- `blender` is baked in unconditionally here even though `Project4x`'s `ENABLE_BLENDER` flag is
|
||||
currently `"false"` — the image can't cheaply branch on a flag it doesn't know about at build
|
||||
time, and the package is small next to the rest of this list. If that turns out to add
|
||||
meaningful image size/pull time for a project that never flips the flag on, split into a second
|
||||
`-blender` tagged variant instead.
|
||||
- Combine into one `RUN` (as above) so it's a single image layer — no benefit to splitting these
|
||||
across layers here.
|
||||
|
||||
## Publish workflow skeleton
|
||||
|
||||
New repo, `.gitea/workflows/publish.yml`:
|
||||
|
||||
```yaml
|
||||
name: Publish godot-ci image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths: [Dockerfile]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to Gitea registry
|
||||
run: echo "${{ secrets.REGISTRY_PUSH_PAT }}" | docker login git.arthurerlich.de -u <owner-username> --password-stdin
|
||||
|
||||
- name: Build and push
|
||||
run: |
|
||||
docker build -t git.arthurerlich.de/haylan/<new-repo-name>:4.7.1 .
|
||||
docker push git.arthurerlich.de/haylan/<new-repo-name>:4.7.1
|
||||
```
|
||||
|
||||
Fill in `<owner-username>` and `<new-repo-name>` once the repo exists.
|
||||
|
||||
## How `Project4x` would then reference the new image
|
||||
|
||||
One-line change in `.gitea/workflows/export-template.yml`:
|
||||
|
||||
```diff
|
||||
- container: barichello/godot-ci:4.7.1
|
||||
+ container: git.arthurerlich.de/haylan/<new-repo-name>:4.7.1
|
||||
```
|
||||
|
||||
The "Update and install dependencies" step's `apt-get install` calls for the baked-in packages
|
||||
become redundant at that point (harmless no-ops — `apt-get install` on an already-installed
|
||||
package is a fast no-op, so removing them is a cleanup, not a correctness requirement) and can be
|
||||
trimmed in a follow-up ticket once the image is live and proven.
|
||||
|
||||
## Decisions
|
||||
|
||||
- **Bake-in scope:** everything in the current apt-get list, not a narrower "slow packages only"
|
||||
subset — keeps the image and `export-template.yml`'s toolchain description in exact sync, so
|
||||
there's only one place (this spec + the Dockerfile) to update when the list changes, instead of
|
||||
maintaining a second smaller list that drifts from the first.
|
||||
- **Tag scheme:** pinned to `GODOT_VERSION` rather than tracking image/toolchain revisions
|
||||
independently — simpler to reason about, and this repo's own SCons cache is already keyed the
|
||||
same way, so a Godot bump already forces everything else to rebuild in lockstep.
|
||||
@@ -0,0 +1,318 @@
|
||||
# Why act_runner's Docker volumes accumulate, and how to clean them up
|
||||
|
||||
> **Scope note:** this is about the **host/runner infrastructure** for
|
||||
> git.arthurerlich.de's Gitea Actions (`act_runner` + the Docker daemon it
|
||||
> drives, in the docker-compose setup for that Gitea instance) — **not**
|
||||
> about this repo's own `Dockerfile`/published image. This repo's
|
||||
> `publish.yml` already runs `docker system prune -af --volumes` before its
|
||||
> build for the same underlying reason described here (unbounded volume/image
|
||||
> growth on the CI host), which is what prompted this research. No access to
|
||||
> the actual Gitea docker-compose file was available or used; every claim
|
||||
> below is grounded in act_runner's/Gitea's/Docker's own docs and source, or
|
||||
> flagged as inferred/secondary where it isn't.
|
||||
|
||||
## Recommendations (ranked)
|
||||
|
||||
1. **Schedule `docker system prune -af --volumes` on the runner host on a
|
||||
recurring basis (cron or a scheduled Gitea Action), the same pattern this
|
||||
repo's own `publish.yml` already uses before its build.** — **Proven
|
||||
mechanism, standard mitigation.** No built-in act_runner/Gitea equivalent
|
||||
exists (see §2); this is the documented, generic Docker-level fix and is
|
||||
what community guidance for this exact runner-volume-leak class of
|
||||
problem converges on. Prefer a filtered form if the host also runs
|
||||
same-day rebuilds you don't want swept up:
|
||||
`docker system prune -af --volumes --filter "until=24h"` (or
|
||||
`docker volume prune --filter "until=24h"` for a volumes-only, less
|
||||
aggressive sweep) — see §2 for the exact filter semantics.
|
||||
2. **Confirm/enable act_runner's `container.valid_volumes` allowlist and
|
||||
review `container.options`/`container.network`, but do not expect them to
|
||||
fix the leak** — **Proven from act_runner's own config reference (§1.2):
|
||||
these control what containers are *allowed* to mount, not automatic
|
||||
cleanup after a job ends.** Leaving `valid_volumes` wide open
|
||||
(`- '**'`) doesn't itself cause more leakage than default, but tightening
|
||||
it is good hygiene independent of the disk problem.
|
||||
3. **If DinD (Docker-in-Docker) mode is in use, that is very likely where
|
||||
the bulk of the leaked volumes come from** — **inferred, but from a
|
||||
documented, closely-analogous mechanism (§1), not confirmed against this
|
||||
host's actual config** since no docker-compose file was available. DinD
|
||||
needs its own persistent Docker data directory (commonly volume-backed),
|
||||
and any job container that declares a `VOLUME` in its Dockerfile gets an
|
||||
anonymous volume that Docker does not remove on `docker rm` unless the
|
||||
container was started with `--rm` — a mechanism GitHub's own
|
||||
`actions/runner` maintainers confirmed by design for the conceptually
|
||||
identical GitHub Actions Docker executor (§1.3). act_runner's job/step
|
||||
containers are created, started, stopped, and removed as separate Docker
|
||||
API calls (not a single `docker run --rm`), which is the same shape of
|
||||
gap.
|
||||
4. **A dedicated Linux user for act_runner (and rootless Docker specifically)
|
||||
is a security/isolation improvement, not a fix for volume accumulation.**
|
||||
— **Proven boundary, directly stated in act_runner's own docs (§3).**
|
||||
Do it for blast-radius/`docker.sock`-exposure reasons; don't expect it to
|
||||
change disk usage. Running the Docker daemon itself in **rootless mode**
|
||||
changes *where* volumes live (per-user data root under
|
||||
`$XDG_RUNTIME_DIR`/`~/.local/share/docker` instead of `/var/lib/docker`)
|
||||
but Docker's own rootless docs describe no change to prune/cleanup
|
||||
semantics — unused volumes still only go away via an explicit prune,
|
||||
run as that user against that daemon.
|
||||
|
||||
**Net: there is no act_runner/Gitea-side switch that stops the leak.** The
|
||||
fix is operational — a scheduled prune — same shape as the one already
|
||||
present in this repo's own `publish.yml`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Why this happens
|
||||
|
||||
### 1.1 act_runner's job execution model
|
||||
|
||||
Per Gitea's own Act Runner documentation, act_runner supports two execution
|
||||
modes: **Docker container mode** ("it is recommended to run jobs in a
|
||||
docker container", requiring a running Docker daemon) and **host mode**
|
||||
(jobs run directly on the machine act_runner is on, no Docker involved).
|
||||
Docker mode is the default/recommended mode and is what a normal
|
||||
docker-compose Gitea+act_runner setup uses.
|
||||
([docs.gitea.com/1.23/usage/actions/act-runner](https://docs.gitea.com/1.23/usage/actions/act-runner/))
|
||||
|
||||
In Docker mode, act_runner creates a container per job (and per
|
||||
service-container / step container as declared in the workflow), runs it,
|
||||
and removes it — as separate Docker API calls, not a single atomic
|
||||
`docker run --rm ...`. This matters because Docker's own volume-removal
|
||||
rule is tied to *how* a container is removed, not just that it's gone (see
|
||||
§1.3).
|
||||
|
||||
### 1.2 act_runner's `container.*` config settings
|
||||
|
||||
From act_runner's own `config.yaml` reference (fetched from
|
||||
`gitea.com/gitea/act_runner`'s example config,
|
||||
[gitea.com/gitea/act_runner — config.example.yaml](https://gitea.com/gitea/act_runner/raw/branch/main/internal/pkg/config/config.example.yaml)):
|
||||
|
||||
- **`container.docker_host`** — overrides the Docker daemon act_runner talks
|
||||
to. Empty (default) auto-detects; `-` auto-detects without mounting the
|
||||
socket into job containers. Controls *which* daemon runs jobs, not
|
||||
cleanup.
|
||||
- **`container.network`** — `host`, `bridge`, or a custom network name;
|
||||
empty auto-creates a per-job network. Unrelated to volumes.
|
||||
- **`container.privileged`** — "Privileged mode is required for
|
||||
Docker-in-Docker." This is the flag that turns on DinD-style job
|
||||
execution.
|
||||
- **`container.options`** — free-form extra flags passed to container
|
||||
creation (e.g. `--add-host=...`, or extra `--volume` mounts). Whatever a
|
||||
workflow or admin puts here can itself add more bind mounts/volumes per
|
||||
job, but is opt-in, not a default source of leakage.
|
||||
- **`container.valid_volumes`** — an **allowlist** (glob syntax) of
|
||||
volumes/bind mounts job containers are permitted to mount; empty means
|
||||
none permitted, `'**'` means any. This is an authorization control, not a
|
||||
lifecycle/cleanup control — it limits what *can* be mounted, and doesn't
|
||||
cause or prevent leftover anonymous volumes from images that declare
|
||||
`VOLUME` internally.
|
||||
- **`container.bind_workdir`** — relevant specifically to DinD: "required
|
||||
for Docker-in-Docker (DinD) setups when jobs use docker compose with bind
|
||||
mounts (e.g. `.:/app`), as volume-based workspaces are not accessible
|
||||
from the DinD daemon's filesystem." Confirms DinD's workspace handling is
|
||||
a distinct code path from plain Docker mode.
|
||||
- **`workdir_cleanup_age` / `idle_cleanup_interval`** — these exist, but per
|
||||
the config reference they clean up **act_runner's own workspace
|
||||
directories** (stale bind-mounted task-workdir folders on the host
|
||||
filesystem, and orphaned host-mode scratch dirs) — **not Docker volumes**.
|
||||
There is no equivalent `container`-level setting for pruning Docker
|
||||
volumes.
|
||||
|
||||
None of act_runner's documented settings amount to "clean up Docker volumes
|
||||
after a job." The closest things (`workdir_cleanup_age`,
|
||||
`idle_cleanup_interval`) are scoped to filesystem workdirs, a different
|
||||
mechanism from Docker volume objects.
|
||||
|
||||
### 1.3 Is DinD specifically the source of the leak, vs plain Docker mode?
|
||||
|
||||
Two independent, additive causes are documented:
|
||||
|
||||
1. **Any Docker execution mode (plain or DinD): anonymous volumes from
|
||||
job/service containers leak by Docker's own removal semantics.** This is
|
||||
not act_runner-specific — it's how Docker containers work. GitHub's own
|
||||
`actions/runner` maintainers documented the identical failure mode for
|
||||
GitHub Actions' (conceptually equivalent) Docker executor: *"When using
|
||||
service containers with a `VOLUME` declaration in their Dockerfile, an
|
||||
anonymous volume is automatically created with the container. At the end
|
||||
of the workflow, the container is stopped and removed, but the anonymous
|
||||
volumes it created stay around... The runner creates, starts, stops and
|
||||
removes containers with separate commands, so passing `--rm` to `docker
|
||||
create` has no effect on `docker remove`."*
|
||||
([actions/runner#1885](https://github.com/actions/runner/issues/1885) —
|
||||
maintainer-confirmed root cause, not community speculation). act_runner's
|
||||
job execution is architecturally the same shape (separate create/start/
|
||||
stop/remove calls against the Docker API), so the same leak mechanism
|
||||
applies to it by design, absent extra config — this is the strongest
|
||||
available evidence, though it is evidence from GitHub's runner, applied
|
||||
by analogy to act_runner rather than a direct act_runner maintainer
|
||||
statement (no act_runner-specific issue making this exact claim was
|
||||
found in this pass).
|
||||
2. **DinD adds its own persistent data directory on top.** A Docker-in-Docker
|
||||
daemon needs its own `/var/lib/docker`-equivalent to store the images/
|
||||
layers/volumes *of the jobs it runs*, and community DinD setups
|
||||
routinely back that directory with a Docker volume so it survives
|
||||
restarts of the DinD container itself (e.g. "`/var/lib/docker` cannot be
|
||||
on AUFS, so it needs to be made a volume" — a long-standing operational
|
||||
note about DinD from general Docker-in-Docker tooling, not Gitea-specific,
|
||||
flagged here as secondary/community evidence). This is additive to (1):
|
||||
DinD setups accumulate both the generic per-job anonymous-volume leak
|
||||
*and* whatever grows inside DinD's own backing store (images, build
|
||||
cache, and volumes belonging to the containers it spawns), which is a
|
||||
second, larger accumulation surface than plain Docker mode.
|
||||
|
||||
**Conclusion:** the generic anonymous-volume leak happens in plain Docker
|
||||
mode too (it's a Docker container-removal semantics issue, not a DinD-only
|
||||
bug), but DinD mode is very likely the bigger contributor on a real host,
|
||||
because it adds a whole second Docker data root that grows independently.
|
||||
Without the actual docker-compose file, it isn't possible to confirm from
|
||||
this pass alone whether git.arthurerlich.de's act_runner is configured for
|
||||
DinD (`container.privileged: true` set) or plain Docker mode.
|
||||
|
||||
---
|
||||
|
||||
## 2. How to clean up
|
||||
|
||||
### 2.1 `docker volume prune` / `docker system prune` semantics (Docker's own docs)
|
||||
|
||||
Per Docker's official pruning reference
|
||||
([docs.docker.com/engine/manage-resources/pruning](https://docs.docker.com/engine/manage-resources/pruning/),
|
||||
[docs.docker.com/reference/cli/docker/volume/prune](https://docs.docker.com/reference/cli/docker/volume/prune/),
|
||||
[docs.docker.com/reference/cli/docker/system/prune](https://docs.docker.com/reference/cli/docker/system/prune/)):
|
||||
|
||||
- **`docker system prune`** (no flags) removes stopped containers, networks
|
||||
not used by at least one container, dangling images, and unused build
|
||||
cache. **Volumes are excluded by default.**
|
||||
- **`docker system prune --volumes`** (or `-a --volumes` to also sweep
|
||||
non-dangling unused images) additionally removes "all volumes not used by
|
||||
at least one container."
|
||||
- **`docker volume prune`** removes unused local volumes; by default this
|
||||
means **anonymous** volumes not referenced by any container. `--all`
|
||||
extends this to unused **named** volumes too.
|
||||
- **`docker volume prune --filter "until=<duration>"`** narrows the sweep to
|
||||
volumes older than the given age (still only unused ones) — a way to keep
|
||||
volumes from a job that finished minutes ago while still cleaning up
|
||||
older leftovers.
|
||||
|
||||
**Safety on a host mid-job:** all of the above operate only on resources
|
||||
**not referenced by any container, running or stopped**. A volume mounted
|
||||
into a currently-running job container is never touched. This makes it safe
|
||||
to run on a schedule against a live runner host without coordinating with
|
||||
in-flight jobs — the documented behavior is conservative by construction,
|
||||
not merely "usually fine in practice."
|
||||
|
||||
### 2.2 Does act_runner or Gitea ship any built-in cleanup for this?
|
||||
|
||||
No. Searched act_runner's config reference, Gitea's Actions/runner
|
||||
documentation, and the two most relevant issue threads found
|
||||
([go-gitea/gitea#31457](https://github.com/go-gitea/gitea/issues/31457),
|
||||
about `act_runner` manageability generally, and
|
||||
[gitea.com/gitea/runner#167](https://gitea.com/gitea/runner/issues/167) /
|
||||
[go-gitea/gitea#24438](https://github.com/go-gitea/gitea/issues/24438),
|
||||
about `docker.sock` exposure) — none describe a built-in Docker volume
|
||||
cleanup mechanism, cron-style prune config, or maintainer statement
|
||||
addressing this exact problem. The only cleanup knobs act_runner documents
|
||||
(`workdir_cleanup_age`, `idle_cleanup_interval`) are scoped to its own
|
||||
host-filesystem workdirs, not Docker volume objects (§1.2). This absence is
|
||||
itself informative: there is nothing to "turn on" here — a scheduled
|
||||
external prune is the only available lever.
|
||||
|
||||
### 2.3 Is a scheduled `docker system prune -af --volumes` the standard approach?
|
||||
|
||||
Yes, and it is corroborated by both this repo's own precedent and general
|
||||
community guidance:
|
||||
|
||||
- This repo's `publish.yml` already runs `docker system prune -af --volumes`
|
||||
before its build, for the same underlying class of problem (disk
|
||||
exhaustion from accumulated Docker state on a CI runner) — direct
|
||||
precedent already in this codebase.
|
||||
- General community guidance for self-hosted CI runners converges on the
|
||||
same shape of fix: a scheduled/cron `docker system prune` (optionally
|
||||
filtered by age) run on the runner host. This is consistent, secondary
|
||||
evidence (blog posts / community write-ups, not a Docker or Gitea
|
||||
standards document) but aligns with what Docker's own docs make safe
|
||||
(§2.1).
|
||||
|
||||
**A more targeted alternative** — `docker volume prune --filter "until=24h"`
|
||||
run on a schedule (e.g. nightly cron) — trades some aggressiveness for
|
||||
narrower scope: it only ever removes volumes, leaves stopped
|
||||
containers/networks/images alone, and skips anything from the last 24h in
|
||||
case a long-running or recently-finished job's volume is still wanted for
|
||||
debugging. This is a reasonable middle ground if a full `system prune -af`
|
||||
feels too broad for the runner host (e.g. if other non-CI Docker workloads
|
||||
share the machine); `docker system prune -af --volumes` is simpler and is
|
||||
what this repo already does elsewhere.
|
||||
|
||||
---
|
||||
|
||||
## 3. Dedicated user for the runner: security concern, not a cleanup mechanism
|
||||
|
||||
### 3.1 act_runner's own guidance
|
||||
|
||||
Gitea's Act Runner docs (systemd install instructions) recommend creating a
|
||||
dedicated unprivileged `act_runner` user, and note that adding it to the
|
||||
`docker` group (needed for Docker mode) **"effectively gives act_runner
|
||||
root access to the system"** — framed entirely as a privilege/blast-radius
|
||||
concern.
|
||||
([docs.gitea.com/1.23/usage/actions/act-runner](https://docs.gitea.com/1.23/usage/actions/act-runner/))
|
||||
|
||||
This is echoed more sharply in Gitea's own tracker:
|
||||
[gitea.com/gitea/runner#167](https://gitea.com/gitea/runner/issues/167) /
|
||||
mirrored as [go-gitea/gitea#24438](https://github.com/go-gitea/gitea/issues/24438),
|
||||
titled *"Gitea Actions is HIGHLY insecure due to binding of docker.sock into
|
||||
all containers (= root on host)"* — the concern raised (and act_runner's own
|
||||
`examples/vm/rootless-docker.md` walkthrough, built specifically in
|
||||
response to this class of concern) is **container escape / arbitrary root
|
||||
on the host via `docker.sock` exposure to untrusted job code**, not disk
|
||||
usage.
|
||||
|
||||
### 3.2 Rootless Docker / user-namespace remapping (Docker's own docs)
|
||||
|
||||
Per Docker's official rootless-mode docs
|
||||
([docs.docker.com/engine/security/rootless](https://docs.docker.com/engine/security/rootless/)):
|
||||
rootless mode "lets you run the Docker daemon and containers as a non-root
|
||||
user to mitigate potential vulnerabilities in the daemon and the container
|
||||
runtime" — again, a security-isolation feature. The docs describe no
|
||||
special relationship to volume/disk accumulation or cleanup behavior.
|
||||
act_runner's own `rootless-docker.md` walkthrough
|
||||
([gitea.com/gitea/act_runner — examples/vm/rootless-docker.md](https://gitea.com/gitea/act_runner/raw/branch/main/examples/vm/rootless-docker.md))
|
||||
is, in its own words, exclusively about security/isolation setup (dedicated
|
||||
user, rootless daemon, systemd units) and makes no mention of volume
|
||||
cleanup or storage management at any point.
|
||||
|
||||
Practically, rootless mode does shift *where* Docker's data root (and
|
||||
therefore its volumes) lives — per-user, typically under
|
||||
`$XDG_RUNTIME_DIR` / `~/.local/share/docker` rather than the system-wide
|
||||
`/var/lib/docker` — but this only changes the location being filled up, not
|
||||
whether it fills up. The same `docker volume prune` semantics (§2.1) still
|
||||
apply, run as that user against that user's daemon.
|
||||
|
||||
### 3.3 Conclusion
|
||||
|
||||
- **(a) Dedicated Linux user for the act_runner service itself:** purely a
|
||||
blast-radius/permissions control (who can touch what if the runner or a
|
||||
job container is compromised). No documented bearing on volume
|
||||
accumulation.
|
||||
- **(b) Rootless Docker / userns-remap:** also purely a security-isolation
|
||||
mechanism per Docker's own docs. It relocates where volumes are stored
|
||||
(per-user vs system-wide) but does not change prune semantics or provide
|
||||
any automatic cleanup — the volume-growth problem is orthogonal to this
|
||||
and is not solved by adopting either (a) or (b).
|
||||
|
||||
Both are worth doing for `docker.sock`-exposure/security reasons
|
||||
independent of this investigation, but neither should be expected to move
|
||||
the disk-usage needle — only a scheduled prune (§1 recommendation #1, §2.3)
|
||||
addresses that.
|
||||
|
||||
---
|
||||
|
||||
## Sources
|
||||
|
||||
- act_runner config reference — [gitea.com/gitea/act_runner: config.example.yaml](https://gitea.com/gitea/act_runner/raw/branch/main/internal/pkg/config/config.example.yaml)
|
||||
- Gitea Act Runner docs — [docs.gitea.com/1.23/usage/actions/act-runner](https://docs.gitea.com/1.23/usage/actions/act-runner/)
|
||||
- act_runner rootless Docker walkthrough — [gitea.com/gitea/act_runner: examples/vm/rootless-docker.md](https://gitea.com/gitea/act_runner/raw/branch/main/examples/vm/rootless-docker.md)
|
||||
- Gitea Actions `docker.sock` security issue — [gitea.com/gitea/runner#167](https://gitea.com/gitea/runner/issues/167) / mirrored [go-gitea/gitea#24438](https://github.com/go-gitea/gitea/issues/24438)
|
||||
- act_runner manageability issue — [go-gitea/gitea#31457](https://github.com/go-gitea/gitea/issues/31457) (no maintainer disk/volume-cleanup discussion found in this thread)
|
||||
- Anonymous volume leak, maintainer-confirmed root cause (GitHub's `actions/runner`, conceptually equivalent Docker executor) — [actions/runner#1885](https://github.com/actions/runner/issues/1885)
|
||||
- Docker pruning reference — [docs.docker.com/engine/manage-resources/pruning](https://docs.docker.com/engine/manage-resources/pruning/)
|
||||
- `docker volume prune` reference — [docs.docker.com/reference/cli/docker/volume/prune](https://docs.docker.com/reference/cli/docker/volume/prune/)
|
||||
- `docker system prune` reference — [docs.docker.com/reference/cli/docker/system/prune](https://docs.docker.com/reference/cli/docker/system/prune/)
|
||||
- Docker rootless mode — [docs.docker.com/engine/security/rootless](https://docs.docker.com/engine/security/rootless/)
|
||||
@@ -0,0 +1,182 @@
|
||||
# Does official Blender run under Alpine/musl?
|
||||
|
||||
Research for [wayfinder ticket #5](https://git.arthurerlich.de/haylan/godot-ci/issues/5).
|
||||
Scope: this repo currently downloads the **official glibc Linux tarball** from
|
||||
`download.blender.org/release/` (see `Dockerfile`, `scripts/resolve-blender-url.sh`)
|
||||
onto a `barichello/godot-ci` (Ubuntu/glibc) base, and uses it for headless
|
||||
`.blend` → asset import in the Godot CI pipeline. This doc asks whether that
|
||||
same tarball, or any Blender build, works on an Alpine/musl base.
|
||||
|
||||
## Verdict
|
||||
|
||||
- **Official tarball on Alpine as-is: does not work.** It is a glibc build
|
||||
(Blender requires glibc ≥ 2.28 per Blender's own requirements page) and
|
||||
Alpine ships musl instead of glibc, with no glibc present at all.
|
||||
- **Official tarball via `gcompat`: unproven / likely does not work reliably.**
|
||||
`gcompat` is explicitly a shim for *pre-built* glibc binaries and is
|
||||
documented by Alpine as not fully compatible — "some [binaries] will still
|
||||
not work with this patch." The one concrete community attempt found (a PR
|
||||
against `ikester/blender-docker`) tried exactly this — official binary +
|
||||
Alpine + presumably gcompat/manual lib shims — and failed with missing
|
||||
libraries (`libGLU.so.1`, `libXrender.so.1`) and unresolved glibc-only
|
||||
symbols (`__sched_cpualloc`, `qsort_r`, `backtrace`). The image never ran.
|
||||
This is a single secondary data point, not exhaustive, but it's the only
|
||||
real-world attempt this research turned up and it failed outright.
|
||||
- **musl-native alternative exists: yes.** Alpine's `community` repository
|
||||
packages Blender from source against musl — `blender`, `blender-headless`,
|
||||
`blender-shared`, `blender-doc`, `py3-blender` — currently at **5.2.0-r0**
|
||||
for `x86_64`. `blender-headless` is specifically the no-GUI server/CLI
|
||||
build, i.e. exactly the shape needed for a Godot import pipeline
|
||||
(`blender --background --python ...`).
|
||||
- **Caveat on the alternative:** as of this research, the Alpine package
|
||||
index shows these packages **only in the `edge` branch**, not in a
|
||||
released stable branch (e.g. `v3.20`/`v3.21`). Edge is Alpine's rolling/
|
||||
unstable branch — usable, but it means pinning to a moving target rather
|
||||
than a versioned stable release, which cuts against the reproducibility
|
||||
this pipeline wants from a pinned Blender version.
|
||||
- **Headless `.blend` import specifically:** not separately verified beyond
|
||||
the existence of `blender-headless` and its description ("headless build"
|
||||
for server/automation use). No primary Blender-side documentation or
|
||||
Alpine changelog was found confirming Python-scripted `--background`
|
||||
import/export works flawlessly on the musl build; this is inferred from
|
||||
the package's stated purpose, not confirmed by a run.
|
||||
|
||||
**Net:** there is a plausible musl-native path (Alpine `community/edge`
|
||||
`blender-headless`), but it is edge-only today, and the "just run the
|
||||
official tarball via gcompat" path has a real failure report against it.
|
||||
Either the pipeline pins to Alpine `edge` (accepting rolling-release churn)
|
||||
or Alpine is not a good fit for this image's need for a stable, pinned
|
||||
Blender version.
|
||||
|
||||
## Details and sources
|
||||
|
||||
### Official Blender's own requirement (primary)
|
||||
|
||||
Blender's official download requirements page (blender.org) states Linux
|
||||
support requires **glibc 2.28 or newer**; for older glibc (e.g. glibc 2.17 /
|
||||
RHEL 7) Blender recommends staying on the old Blender 3.4 release instead.
|
||||
This is a hard glibc dependency stated by Blender itself — nothing on that
|
||||
page mentions musl or Alpine as a supported target.
|
||||
(https://www.blender.org/download/requirements/, https://www.blender.org/download/)
|
||||
|
||||
Blender's Linux build documentation
|
||||
(https://developer.blender.org/docs/handbook/building_blender/linux/)
|
||||
returned HTTP 403 to automated fetch during this research and could not be
|
||||
quoted directly; it is generally understood (and consistent with the glibc
|
||||
requirement above) to target mainstream glibc distros for both building and
|
||||
running Blender, with no documented musl/Alpine build target.
|
||||
|
||||
### Alpine's gcompat (primary — Alpine wiki/GitHub)
|
||||
|
||||
- Alpine's own wiki page on running glibc programs
|
||||
(`https://wiki.alpinelinux.org/wiki/Running_glibc_programs`) describes
|
||||
`gcompat` as the mechanism for running *already-compiled* glibc binaries
|
||||
on musl by intercepting the glibc dynamic linker path and redirecting to
|
||||
musl plus a set of compatibility shims. (Fetched via search-engine summary
|
||||
— the wiki itself returned HTTP 403 to direct fetch during this research.)
|
||||
- The `gcompat` project itself (https://github.com/rs--/gcompat, mirrored at
|
||||
https://github.com/Stantheman/gcompat) states plainly: it works for
|
||||
binaries **already compiled against glibc**, has **no headers**, and
|
||||
**cannot be used to build software that requires glibc**. It explicitly
|
||||
warns that "some [glibc binaries] will still not work with this patch,"
|
||||
and recommends fixing/porting software to be libc-portable instead of
|
||||
relying on gcompat for anything beyond simple/small binaries.
|
||||
- Alpine's `gcompat` package on `pkgs.alpinelinux.org` is present in the
|
||||
`main` repo, version `1.1.0-r4` (checked build date 2023-07-31) — small
|
||||
and simple, consistent with its stated scope (a shim, not a glibc
|
||||
reimplementation). (https://pkgs.alpinelinux.org/packages?name=gcompat)
|
||||
|
||||
Blender is a large, complex application (OpenGL/Vulkan, audio via
|
||||
PulseAudio/PipeWire, X11, dozens of bundled C/C++ libraries, a bundled
|
||||
Python). This is exactly the profile gcompat's own documentation flags as
|
||||
likely to fail — and the one concrete attempt found (below) did fail.
|
||||
|
||||
### Alpine's musl-native Blender package (primary — pkgs.alpinelinux.org)
|
||||
|
||||
`pkgs.alpinelinux.org` package index, checked directly:
|
||||
|
||||
| package | branch | repo | arch | version | purpose |
|
||||
|---|---|---|---|---|---|
|
||||
| `blender` | edge | community | x86_64 | 5.2.0-r0 | full GUI build |
|
||||
| `blender-headless` | edge | community | x86_64 | 5.2.0-r0 | **no-GUI build for server/automation** |
|
||||
| `blender-shared` | edge | community | x86_64 | 5.2.0-r0 | shared libs subpackage |
|
||||
| `blender-doc` | edge | community | x86_64 | 5.2.0-r0 | docs |
|
||||
| `py3-blender` | edge | community | x86_64 | 5.2.0-r0 | Python bindings subpackage |
|
||||
|
||||
(https://pkgs.alpinelinux.org/package/edge/community/x86_64/blender,
|
||||
https://pkgs.alpinelinux.org/package/edge/community/x86_64/blender-headless)
|
||||
|
||||
Since Alpine's `community`/`main` repos are built from source
|
||||
(`aports`) against musl, these are **native musl builds**, not the glibc
|
||||
tarball run through a shim — this is the actual "musl-friendly alternative"
|
||||
the ticket asks about. `blender-headless`'s description ("headless build",
|
||||
no GUI deps) matches the "background import for a CI asset pipeline" use
|
||||
case directly.
|
||||
|
||||
The important caveat, also confirmed directly against the package index:
|
||||
these packages appear to exist **only under the `edge` branch** at the time
|
||||
of this research (no stable-branch listing, e.g. `v3.20`/`v3.21`, was found
|
||||
for `blender`/`blender-headless`). Alpine's `edge` is the rolling
|
||||
development branch — using it means tracking a moving target rather than a
|
||||
pinned stable Alpine release, which works against the kind of reproducible,
|
||||
version-pinned build this repo's `resolve-blender-url.sh` /
|
||||
`resolve-godot-version.sh` scripts are designed around.
|
||||
|
||||
### Community corroboration (secondary)
|
||||
|
||||
- `ikester/blender-docker` PR #1 ("Change to use alpine linux, much smaller
|
||||
base image size", https://github.com/ikester/blender-docker/pull/1):
|
||||
contributor tried moving that Dockerfile to Alpine using the official
|
||||
Blender binary. Result reported by the repo owner: **it did not run** —
|
||||
missing `libGLU.so.1`, `libXrender.so.1`, and unresolved glibc-only
|
||||
symbols (`__sched_cpualloc`, `qsort_r`, `backtrace`). The owner asked the
|
||||
contributor to "sort out dependencies"; the PR was never updated further
|
||||
and was not merged. This is a single, non-exhaustive report (no gcompat
|
||||
usage confirmed explicitly), but it's concrete evidence that the naive
|
||||
"official tarball + Alpine" path fails in practice, consistent with
|
||||
gcompat's own documented limitations.
|
||||
- Other headless-Blender Docker projects found in this search
|
||||
(`BlenderKit/headless-blender-container`, `HaiyiMei/blender-docker-headless`,
|
||||
LinuxServer.io's `docker-blender`) all use glibc-based bases (Ubuntu/Debian),
|
||||
not Alpine — i.e. nobody surveyed here ships a working Alpine+official-tarball
|
||||
image. This absence is itself weak corroborating evidence that the
|
||||
glibc-tarball-on-Alpine path isn't a well-trodden, working setup.
|
||||
|
||||
## Implication for this repo
|
||||
|
||||
If Alpine is being considered as a smaller/leaner base for the CI image:
|
||||
|
||||
1. The current approach (download official glibc tarball from
|
||||
`download.blender.org`, per `Dockerfile` / `scripts/resolve-blender-url.sh`)
|
||||
**cannot be ported to Alpine as-is** — no glibc, and gcompat is not a
|
||||
credible fix for something as large/complex as Blender (per gcompat's own
|
||||
docs, and per the one real attempt found, which failed).
|
||||
2. The only musl-native path found, Alpine's `blender-headless` package,
|
||||
exists **only on `edge`** — adopting Alpine would mean giving up the
|
||||
pinned/reproducible-version approach this repo currently uses for both
|
||||
Godot and Blender, in favor of tracking Alpine's rolling branch, or
|
||||
manually backporting/building the aport for a stable branch (extra
|
||||
maintenance burden not costed here).
|
||||
3. No evidence was found either way (positive or negative) on whether the
|
||||
Alpine `blender-headless` package's Python/`--background` scripting
|
||||
behaves identically to the official build for `.blend` import — only
|
||||
that the package is described as built for that use case.
|
||||
|
||||
**This is a real argument against Alpine** for this image unless the team
|
||||
is willing to (a) pin to Alpine `edge` and accept its churn, or (b) take on
|
||||
maintaining a backport of the `blender`/`blender-headless` aport for a
|
||||
stable Alpine branch themselves.
|
||||
|
||||
## Sources
|
||||
|
||||
- https://www.blender.org/download/requirements/
|
||||
- https://www.blender.org/download/
|
||||
- https://developer.blender.org/docs/handbook/building_blender/linux/ (403 on direct fetch; referenced via search summary)
|
||||
- https://wiki.alpinelinux.org/wiki/Running_glibc_programs (403 on direct fetch; referenced via search summary)
|
||||
- https://github.com/rs--/gcompat
|
||||
- https://github.com/Stantheman/gcompat
|
||||
- https://pkgs.alpinelinux.org/packages?name=gcompat
|
||||
- https://pkgs.alpinelinux.org/package/edge/community/x86_64/blender
|
||||
- https://pkgs.alpinelinux.org/package/edge/community/x86_64/blender-headless
|
||||
- https://github.com/ikester/blender-docker/pull/1 (secondary/community)
|
||||
- https://github.com/BlenderKit/headless-blender-container (secondary, uses Ubuntu — corroborates absence of a working Alpine setup)
|
||||
@@ -0,0 +1,282 @@
|
||||
# Can this repo's Docker build be sped up, and does caching exist for it?
|
||||
|
||||
Research triggered by the real-run timing gap: ~5 min locally vs **~16 min** on
|
||||
the actual runner
|
||||
(run [1493](https://git.arthurerlich.de/haylan/godot-ci/actions/runs/1493)),
|
||||
dominated by the single `apt-get install` layer (~470s, `build-essential`,
|
||||
`mingw-w64`, X11/audio/udev dev headers) and the Godot export
|
||||
templates + Blender downloads.
|
||||
|
||||
## Verdict: **yes — but the fix is "stop deleting the cache", not "add a cache"**
|
||||
|
||||
The runner (`runs-on: ubuntu-latest` in
|
||||
[`.gitea/workflows/publish.yml`](../../.gitea/workflows/publish.yml)) is a
|
||||
**self-hosted, persistent** [act_runner](https://gitea.com/gitea/act_runner)
|
||||
host, not an ephemeral GitHub-style VM — the workflow's own "Free up runner
|
||||
disk" step and its comment only make sense on a persistent box ("*the
|
||||
runner's disk fills up over successive runs until a push … is what finally
|
||||
fails*"; an ephemeral runner starts clean every time and would never
|
||||
accumulate anything to prune). That persistence is exactly what makes plain
|
||||
Docker build-cache (no registry, no extra tooling) viable here — and it's
|
||||
also exactly what today's `docker system prune -af --volumes` step throws
|
||||
away before every single build, per Docker's own docs: `system prune`'s
|
||||
default removal set explicitly includes *"Unused build cache"*, and `-a`
|
||||
widens that to "all unused build cache," not just dangling entries
|
||||
([docs.docker.com/reference/cli/docker/system/prune](https://docs.docker.com/reference/cli/docker/system/prune/)).
|
||||
So right now, **local BuildKit cache cannot survive from one nightly run to
|
||||
the next no matter how the Dockerfile is written** — the prune step deletes
|
||||
it every time, before the build that could have reused it even starts.
|
||||
|
||||
Ranked recommendations:
|
||||
|
||||
1. **Stop nuking the build cache in the prune step.** Replace
|
||||
`docker system prune -af --volumes` with targeted pruning that skips
|
||||
builder cache: `docker container prune -f && docker image prune -af &&
|
||||
docker volume prune -f`, plus a separate, bounded
|
||||
`docker builder prune -f --filter until=24h` (or `--keep-storage=<N>GB`)
|
||||
to cap build-cache growth without wiping cache that's still same-day
|
||||
reusable. This alone is what unlocks everything below — no Dockerfile
|
||||
change helps while the current step runs first. Near-zero risk: it still
|
||||
frees the disk that motivated the step, just not by deleting reusable
|
||||
build cache along with it.
|
||||
2. **Split the `apt-get install` layer from the Godot/Blender download
|
||||
layer in the Dockerfile**, apt first. Right now both live in one giant
|
||||
`RUN` chained with `&&` (`Dockerfile` lines 19–62), so a single-byte
|
||||
change anywhere in that chain — including the Godot/Blender URLs that
|
||||
*do* change nightly — invalidates the apt install too. BuildKit's cache
|
||||
is a linear, per-instruction, content-addressed chain: it reuses a layer
|
||||
only if every instruction up to and including it is byte-identical to a
|
||||
cached run (Docker's build-cache docs: cache is invalidated "from that
|
||||
point" the instant one instruction's content changes,
|
||||
[docs.docker.com/build/cache](https://docs.docker.com/build/cache/)).
|
||||
The apt package list changes essentially never; `GODOT_VERSION`/
|
||||
`BLENDER_URL` change most nights. Splitting them means the ~470s apt
|
||||
layer becomes a cache hit on every night that doesn't touch the
|
||||
Dockerfile itself, while the Godot/Blender layer keeps rebuilding (it has
|
||||
to — new content every time a version bumps). Combined with #1, this is
|
||||
the actual fix for the dominant cost in the real run.
|
||||
3. **Add `RUN --mount=type=cache` for apt's own directories**
|
||||
(`/var/cache/apt`, `/var/lib/apt`, `sharing=locked`) as a smaller
|
||||
supplement to #2 — it persists apt's downloaded `.deb` cache independent
|
||||
of the Dockerfile-instruction cache chain, so even a Dockerfile edit that
|
||||
invalidates the layer doesn't force re-downloading every `.deb` from
|
||||
Debian's mirrors, only re-running `dpkg` against the still-local cache
|
||||
([docs.docker.com/reference/dockerfile/#run---mounttypecache](https://docs.docker.com/reference/dockerfile/#run---mounttypecache)).
|
||||
Worth doing alongside #2 since it's a few lines, but it's a hedge against
|
||||
Dockerfile churn, not the main win — #2 already gets full cache hits on
|
||||
the common case (no Dockerfile change, only build-args change).
|
||||
4. **Skip registry-based cache (`--cache-to/--cache-from type=registry`).**
|
||||
Not worth it here. Registry cache exists to share a build cache *across
|
||||
machines that don't share disk* — ephemeral GitHub-hosted runners, fleets
|
||||
of self-hosted runners, etc.
|
||||
([docs.docker.com/build/cache/backends/registry](https://docs.docker.com/build/cache/backends/registry/)).
|
||||
This repo has exactly one persistent runner with its own disk, so #1–#3
|
||||
already give it that same cache locally, for free, with no push/pull
|
||||
round-trip. Pushing a *separate* cache image on top would add push/pull
|
||||
time on every run for a benefit local cache already provides, and it's a
|
||||
bad fit for this specific image's shape: the Godot/Blender layers are
|
||||
multi-GB and, by design, different nearly every night (new versions),
|
||||
so `mode=max` would repeatedly re-push multi-GB "cache" blobs that are
|
||||
very unlikely to ever be pulled back down. There's also an open question
|
||||
of whether it would even work: Gitea's container registry only gained
|
||||
general OCI-artifact support in 1.24
|
||||
([go-gitea/gitea#25846](https://github.com/go-gitea/gitea/issues/25846),
|
||||
closed via PR #34666) — before that it rejected non-image manifests with
|
||||
`"unsupported: Schema version is not supported"`. This repo's Gitea
|
||||
instance version wasn't confirmed as part of this research, so pushing a
|
||||
buildx cache manifest (which uses custom OCI media types, distinct from
|
||||
a normal image manifest) is not guaranteed to work without checking that
|
||||
first.
|
||||
5. **Gitea Actions' `actions/cache` action is the wrong tool for this,
|
||||
noted for completeness.** Gitea Actions does support a GitHub-compatible
|
||||
`actions/cache@v3`/`v4` via a cache server that ships enabled by default
|
||||
in `act_runner`
|
||||
([about.gitea.com tutorial](https://about.gitea.com/resources/tutorials/enable-gitea-actions-cache-to-accelerate-cicd/);
|
||||
[docs.gitea.com/usage/actions/overview](https://docs.gitea.com/usage/actions/overview/)),
|
||||
plus a separate `RUNNER_TOOL_CACHE` mechanism. Both are key/value or
|
||||
directory caches meant for things like `node_modules`/pip wheels between
|
||||
workflow steps — not a Docker layer-cache mechanism, and not something
|
||||
`docker build`/`docker push` (what this workflow actually calls, see
|
||||
below) has any hook into. It doesn't apply to this problem; #1–#3 do.
|
||||
|
||||
## 1. What the workflow and Dockerfile actually do today
|
||||
|
||||
`.gitea/workflows/publish.yml` runs plain `docker build` and `docker push` —
|
||||
not `docker/build-push-action` or `docker buildx build` with any cache flags
|
||||
(lines 86–89, 96/105/109). `runs-on: ubuntu-latest` is a Gitea-side runner
|
||||
*label*, matched against whatever labels this instance's `act_runner` was
|
||||
registered with — Gitea's own runner docs describe labels as exactly this
|
||||
kind of local mapping (`ubuntu-latest:docker://...` style entries a runner
|
||||
operator configures, not a GitHub-hosted VM type)
|
||||
([search result summary of docs.gitea.com/usage/actions, act_runner label
|
||||
docs](https://docs.gitea.com/1.23/usage/actions/act-runner/); the specific
|
||||
`main_runner` name mentioned as a possible identifier for this instance's
|
||||
runner doesn't appear anywhere in this repo's tracked files — it would only
|
||||
show up in the Gitea Actions run UI, not the workflow YAML). The disk-fill
|
||||
problem the "Free up runner disk" step's own comment describes (*"nothing
|
||||
here ever pruned old ones, so the runner's disk fills up over successive
|
||||
runs"*) is only possible on a runner whose disk persists between runs —
|
||||
corroborated independently by act_runner's own architecture: its Docker
|
||||
executor keeps a long-lived host Docker daemon and reuses the image cache
|
||||
across job runs by default (act_runner documentation on the docker/dind
|
||||
executor flavors and idle-cleanup behavior — runner cleans up stale
|
||||
workspaces on an interval rather than starting from a fresh disk each job).
|
||||
|
||||
The `Dockerfile`'s expensive work is one `RUN` chain (lines 19–62): a single
|
||||
`apt-get install` covering `build-essential`, `scons`, `mingw-w64`, and the
|
||||
X11/audio/udev dev headers, immediately followed — in the same layer, via
|
||||
`&&` — by the `curl`+`unzip` of Godot's editor and export templates and the
|
||||
`curl`+`tar` of the Blender tarball. Because it's one `RUN`, it's one cache
|
||||
entry: any change anywhere in it (including the Godot/Blender URLs, which
|
||||
change on essentially every nightly build since the workflow tracks
|
||||
newest-stable) invalidates the whole thing, apt install included.
|
||||
|
||||
## 2. Docker/BuildKit's own caching mechanisms (docs.docker.com)
|
||||
|
||||
- **Layer cache (default, local, free)**: BuildKit caches each instruction's
|
||||
result keyed on that instruction's content plus everything before it in
|
||||
the Dockerfile; a build on the same machine reuses it automatically with
|
||||
no extra flags, provided the cache wasn't evicted
|
||||
([docs.docker.com/build/cache](https://docs.docker.com/build/cache/)).
|
||||
This is what #1+#2 above unlock for this repo — it needs nothing but (a)
|
||||
not deleting it and (b) ordering the Dockerfile so the part that doesn't
|
||||
change (apt) comes before the part that does (downloads).
|
||||
- **`RUN --mount=type=cache`**: a *build cache mount*, separate from the
|
||||
layer cache — a directory that survives across builder invocations
|
||||
without itself being part of the cached layer, intended for package-
|
||||
manager caches like apt's `/var/cache/apt`/`/var/lib/apt`. Needs
|
||||
`sharing=locked` for apt specifically, since apt needs exclusive access to
|
||||
its own state
|
||||
([docs.docker.com/reference/dockerfile/#run---mounttypecache](https://docs.docker.com/reference/dockerfile/#run---mounttypecache)).
|
||||
Persists even when the layer cache above gets invalidated by a Dockerfile
|
||||
edit — a smaller, complementary win, not a replacement for #2's layer
|
||||
split.
|
||||
- **`--cache-to`/`--cache-from type=registry`**: exports/imports build
|
||||
cache to/from an OCI registry, as a separate artifact from the final
|
||||
image, specifically to let machines that don't share local disk share a
|
||||
cache (`mode=max` caches every stage, not just the final one — bigger
|
||||
push, more reuse potential)
|
||||
([docs.docker.com/build/cache/backends/registry](https://docs.docker.com/build/cache/backends/registry/)).
|
||||
Not needed for a single persistent runner (see verdict §4).
|
||||
- **`BUILDKIT_INLINE_CACHE`/`type=inline`**: embeds cache metadata directly
|
||||
in the pushed image manifest instead of a separate cache artifact —
|
||||
Docker's own CI guidance is "in most cases you want to use the inline
|
||||
cache exporter" for simple cases, but it only supports `mode=min` (final
|
||||
stage only) versus registry cache's `min`/`max`
|
||||
([docs.docker.com/build/ci/github-actions/cache](https://docs.docker.com/build/ci/github-actions/cache/)).
|
||||
Same applicability caveat as registry cache: solves a multi-machine
|
||||
problem this repo doesn't have.
|
||||
- Docker Engine ≥23 makes BuildKit (via buildx) the default builder for
|
||||
plain `docker build` — *"Set Buildx and BuildKit as the default builder on
|
||||
Linux. Alias `docker build` to `docker buildx build`"*
|
||||
([docs.docker.com/engine/release-notes/23.0](https://docs.docker.com/engine/release-notes/23.0/)).
|
||||
So the workflow's existing plain `docker build` call (no `buildx` in the
|
||||
command) should already be getting BuildKit's layer cache today — it's
|
||||
the prune step deleting it that matters, not the build command needing to
|
||||
change to buildx.
|
||||
|
||||
## 3. `docker system prune -af --volumes` vs `docker builder prune`
|
||||
|
||||
Confirmed directly against Docker's CLI reference:
|
||||
|
||||
- `docker system prune`'s default removal set is stopped containers, unused
|
||||
networks, dangling images, and **unused build cache**; `-a`/`--all`
|
||||
widens image removal to *all* unused images (not just dangling), and
|
||||
`--volumes` additionally removes unattached anonymous volumes
|
||||
([docs.docker.com/reference/cli/docker/system/prune](https://docs.docker.com/reference/cli/docker/system/prune/)).
|
||||
There's no flag on `system prune` to keep build cache while still pruning
|
||||
everything else — build cache removal isn't optional once you call it.
|
||||
- `docker builder prune` is the narrower, cache-only equivalent: `--all` to
|
||||
remove all unused cache (not just dangling), `--filter until=<duration>`
|
||||
to only remove cache older than a given age, `--keep-storage=<size>` to
|
||||
cap total cache size instead of clearing it outright
|
||||
([docs.docker.com/reference/cli/docker/builder/prune](https://docs.docker.com/reference/cli/docker/builder/prune/)).
|
||||
|
||||
This is the concrete lever for recommendation #1: keep freeing the disk the
|
||||
workflow's own comment says filled up (old images, old anonymous volumes),
|
||||
but do it with `container prune`/`image prune -af`/`volume prune` instead of
|
||||
`system prune`, and bound the build cache separately with
|
||||
`docker builder prune -f --filter until=24h` (or a `--keep-storage` cap)
|
||||
rather than deleting all of it unconditionally before every build. A same-
|
||||
day rerun (e.g. `workflow_dispatch` shortly after the nightly cron) would
|
||||
then still get a cache hit on the apt layer; a multi-day-old apt cache entry
|
||||
still gets swept on the next age-filtered prune, so disk doesn't grow
|
||||
unbounded either.
|
||||
|
||||
## 4. Layer-order restructuring compatibility
|
||||
|
||||
Splitting `Dockerfile`'s one `RUN` into "apt install" then "Godot/Blender
|
||||
downloads" is a pure reorder/split — no semantic change, no new
|
||||
dependencies, and it's exactly what BuildKit's per-instruction cache keying
|
||||
is designed to reward (§2). Its payoff is entirely contingent on §3: with
|
||||
`docker system prune -af --volumes` still running first, splitting the
|
||||
layer changes nothing, because the reusable apt-layer cache entry is deleted
|
||||
moments before the build that would've hit it starts. The two
|
||||
recommendations are a pair, not independent options.
|
||||
|
||||
## 5. Registry cache realism for this specific image
|
||||
|
||||
Weighed directly against this repo's shape:
|
||||
|
||||
- **Storage cost**: `mode=max` registry cache stores every stage as its own
|
||||
cache layer set in the registry, separate from the pushed image
|
||||
([docs.docker.com/build/cache/backends/registry](https://docs.docker.com/build/cache/backends/registry/)) —
|
||||
for an image whose expensive layers are multi-GB Godot export templates
|
||||
and a Blender tarball that both change nearly nightly, that's another
|
||||
near-full copy of those multi-GB layers pushed to Gitea's package registry
|
||||
on top of the image push that already happens, for cache entries with a
|
||||
short effective lifetime (superseded the next time Godot/Blender bump).
|
||||
- **Gitea registry support**: Gitea's container registry is OCI-compliant
|
||||
for standard image/Helm-chart manifests
|
||||
([docs.gitea.com/usage/packages/container](https://docs.gitea.com/usage/packages/container/)),
|
||||
but general OCI-*artifact* support (the category buildx's cache manifest
|
||||
falls into, using non-image media types) only landed in Gitea 1.24 via
|
||||
[go-gitea/gitea#25846](https://github.com/go-gitea/gitea/issues/25846) —
|
||||
prior to that, pushes of non-standard-image manifests failed with
|
||||
`501 unsupported: Schema version is not supported`. This repo's actual
|
||||
Gitea server version wasn't checked as part of this research, so this is
|
||||
a real "verify before use" gate, not just a style preference.
|
||||
- **Time saved**: none of it addresses the local-persistent-runner case
|
||||
this repo actually has — §2/§4's plain layer-cache fix gets the same or
|
||||
better result (apt-layer cache hits) for free, with no push/pull latency
|
||||
and no registry storage growth.
|
||||
|
||||
Net: registry cache isn't wrong in general, it's solving a problem (cache
|
||||
sharing across machines without shared disk) this repo doesn't have, at a
|
||||
storage and reliability cost this repo's layer shapes make worse than
|
||||
typical.
|
||||
|
||||
## Sources
|
||||
|
||||
- `Dockerfile` — this repo's build, current single `RUN` chain (apt install
|
||||
+ Godot download/unpack + Blender download/unpack), fetched at HEAD
|
||||
- `.gitea/workflows/publish.yml` — `runs-on: ubuntu-latest`, plain
|
||||
`docker build`/`docker push` (no buildx/cache flags), the "Free up runner
|
||||
disk" step and its comment explaining *why* it prunes before every build
|
||||
- https://docs.docker.com/build/cache/ — BuildKit layer cache mechanics,
|
||||
invalidation model
|
||||
- https://docs.docker.com/reference/dockerfile/#run---mounttypecache —
|
||||
`RUN --mount=type=cache`, apt cache-mount example, `sharing=locked`
|
||||
- https://docs.docker.com/build/cache/backends/registry/ — `--cache-to`/
|
||||
`--cache-from type=registry`, `mode=min`/`max`
|
||||
- https://docs.docker.com/build/ci/github-actions/cache/ —
|
||||
`BUILDKIT_INLINE_CACHE`/`type=inline` vs registry cache tradeoffs
|
||||
- https://docs.docker.com/engine/release-notes/23.0/ — BuildKit/buildx as
|
||||
default `docker build` builder since Engine 23.0
|
||||
- https://docs.docker.com/reference/cli/docker/system/prune/ — `docker
|
||||
system prune` default removal set (incl. build cache), `-a`/`--volumes`
|
||||
- https://docs.docker.com/reference/cli/docker/builder/prune/ — `docker
|
||||
builder prune`, `--filter until=`, `--keep-storage`
|
||||
- https://docs.gitea.com/usage/actions/act-runner and
|
||||
https://docs.gitea.com/1.23/usage/actions/act-runner/ — act_runner
|
||||
labels, docker/dind executor persistence and idle cleanup behavior
|
||||
- https://about.gitea.com/resources/tutorials/enable-gitea-actions-cache-to-accelerate-cicd/
|
||||
and https://docs.gitea.com/usage/actions/overview/ — Gitea Actions'
|
||||
`actions/cache` support and default-enabled cache server
|
||||
- https://docs.gitea.com/usage/packages/container/ — Gitea's OCI-compliant
|
||||
container registry
|
||||
- https://github.com/go-gitea/gitea/issues/25846 (closed via PR #34666) —
|
||||
Gitea container registry's OCI-artifact (non-image-manifest) support
|
||||
landing in 1.24, and the `501 unsupported: Schema version is not
|
||||
supported` error prior to that
|
||||
- https://gitea.com/gitea/act_runner — act_runner project, docker executor
|
||||
@@ -0,0 +1,208 @@
|
||||
# Is a glibc-based slim distro (e.g. debian-slim) a better fit than Alpine?
|
||||
|
||||
Research for [wayfinder ticket #8](https://git.arthurerlich.de/haylan/godot-ci/issues/8).
|
||||
Follows on from #4/#5/#6 (`docs/research/godot-musl-alpine.md`,
|
||||
`docs/research/blender-musl-alpine.md`, `docs/research/mingw-w64-alpine.md`,
|
||||
on branches `research/godot-musl-alpine`, `research/blender-musl-alpine`,
|
||||
`research/mingw-w64-alpine`), which converged on: everything this image needs
|
||||
(official Godot Linux binary, official Blender Linux tarball, posix-threads
|
||||
mingw-w64) is only available on Alpine via unpinned `edge`/`community`
|
||||
repos, and Godot/Blender musl compatibility is unverified-to-broken (a
|
||||
still-open Godot editor segfault report on musl, Blender's official glibc
|
||||
≥2.28 requirement with no musl target at all).
|
||||
|
||||
Scope here: does swapping this repo's current base
|
||||
(`FROM barichello/godot-ci:${GODOT_VERSION}`, itself Ubuntu Noble — see
|
||||
`Dockerfile`) for a `debian:*-slim` image get most of Alpine's size/build-time
|
||||
appeal without the musl risk?
|
||||
|
||||
## Verdict: **not worth it — stay on the current Ubuntu-derived base**
|
||||
|
||||
Debian slim clears every *capability* bar Alpine failed (real `mingw-w64`
|
||||
posix-threads package in stable, all the X11/audio/udev dev headers this
|
||||
image installs, official Blender tarball runs unmodified since it's glibc).
|
||||
But the one thing this research was actually chasing — a **meaningful size
|
||||
win** — doesn't materialize: Docker Hub's own official-image listings show
|
||||
`debian:bookworm-slim` (26.92 MB compressed, amd64) and `ubuntu:noble`
|
||||
(28.37 MB compressed, amd64) within about 5% of each other. That's noise,
|
||||
not a win, and this repo doesn't even control the base layer directly —
|
||||
`barichello/godot-ci` is a third-party image built on top of Ubuntu that
|
||||
already bakes in Godot itself, so re-basing to Debian would mean giving up
|
||||
that upstream image and reimplementing its Godot-fetch/setup logic here for
|
||||
a size delta smaller than typical measurement noise between two pulls.
|
||||
Alpine's real draw (musl base images run ~3-8 MB, an order of magnitude
|
||||
below either glibc option) is exactly the size class Debian slim doesn't
|
||||
reach — because it's still glibc, with a full libc and normal shared-lib
|
||||
layout. **The size case for going non-Ubuntu was Alpine's alone, and #4-#6
|
||||
already closed that door on correctness grounds.** Debian slim is not a
|
||||
worse choice than Ubuntu, it's just not a *better* one big enough to justify
|
||||
a base-image migration and losing `barichello/godot-ci`'s maintained Godot
|
||||
install.
|
||||
|
||||
Blender's own docs also don't hand any glibc distro a compatibility win
|
||||
over another: Blender requires glibc ≥2.28 full stop, and both
|
||||
`debian:bookworm-slim` (glibc 2.36) and Ubuntu Noble (glibc 2.39) clear that
|
||||
with headroom — there's no glibc-version reason to prefer one over the
|
||||
other either.
|
||||
|
||||
## 1. Base image size: Debian slim vs Ubuntu (Docker Hub official images)
|
||||
|
||||
Source: Docker Hub official-image tag listings, which show per-tag
|
||||
compressed layer size directly (fetched 2026-08-06):
|
||||
|
||||
| Image | Tag | Arch | Compressed size |
|
||||
|---|---|---|---|
|
||||
| `debian` | `bookworm-slim` | amd64 | **26.92 MB** |
|
||||
| `debian` | `trixie-slim` | amd64 | **28.4 MB** |
|
||||
| `ubuntu` | `noble` (24.04) | amd64 | **28.37 MB** |
|
||||
|
||||
(https://hub.docker.com/_/debian/tags, https://hub.docker.com/_/ubuntu/tags)
|
||||
|
||||
`bookworm-slim` is the only tag with a real edge (~1.5 MB, ~5%, over
|
||||
`ubuntu:noble`); `trixie-slim` is a statistical wash. Compare this to what
|
||||
#4-#6 found for Alpine base images generally (musl, ~3-8 MB compressed,
|
||||
routinely cited as "5x+ smaller than Debian/Ubuntu slim" in Alpine's own
|
||||
marketing and widely elsewhere) — Debian slim sits in the same size class
|
||||
as Ubuntu, not anywhere near Alpine's class. There is no "get 80% of
|
||||
Alpine's win for 20% of the risk" middle ground here: the win is
|
||||
concentrated entirely in the musl-based option this repo can't safely use.
|
||||
|
||||
Caveat: this repo doesn't build `FROM ubuntu:noble` or `FROM debian:*`
|
||||
directly — it builds `FROM barichello/godot-ci:${GODOT_VERSION}`, a
|
||||
third-party image that already includes Godot on top of Ubuntu Noble. The
|
||||
final image size delta from a hypothetical Debian re-base would depend on
|
||||
how much larger/smaller a from-scratch Debian+Godot setup ends up next to
|
||||
`barichello/godot-ci`'s Ubuntu+Godot setup — not measured here, but the
|
||||
~1.5 MB base-layer gap makes it very unlikely to matter once Godot,
|
||||
Blender, mingw-w64, and the X11/audio dev headers are layered on top (those
|
||||
layers, not the base OS, dominate this image's final size).
|
||||
|
||||
## 2. Debian package availability: mingw-w64 (posix) and dev headers
|
||||
|
||||
All confirmed directly against `packages.debian.org` (fetched 2026-08-06),
|
||||
checked in `bookworm` (current stable) and cross-checked in `trixie`:
|
||||
|
||||
- **`gcc-mingw-w64-x86-64`** — metapackage description: "the C compiler,
|
||||
supporting cross-compiling to 64-bit MinGW-w64 targets." It depends on
|
||||
**both** thread-model variants as alternatives:
|
||||
- `gcc-mingw-w64-x86-64-posix` (POSIX threading semantics)
|
||||
- `gcc-mingw-w64-x86-64-win32` (native Windows threading)
|
||||
|
||||
This is the exact `update-alternatives`-selectable posix/win32 split this
|
||||
repo's `Dockerfile` already codes against (`update-alternatives --set
|
||||
x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix`, same for
|
||||
`g++`) — unlike Alpine, where #6 found there is no alternative to select
|
||||
because Alpine's `mingw-w64-gcc` is posix-only by construction. On
|
||||
Debian, the `-posix` binary this Dockerfile pins to exists as a named,
|
||||
stable-suite package, not an edge/rolling one.
|
||||
(https://packages.debian.org/bookworm/gcc-mingw-w64-x86-64)
|
||||
|
||||
- **`mingw-w64`** — the umbrella metapackage (`apt-get install mingw-w64`,
|
||||
matching what Godot's own compiling-for-Windows docs recommend for
|
||||
Debian/Ubuntu, per #6's citation of
|
||||
`godotengine/godot-docs`) — present in bookworm, depends on
|
||||
`gcc-mingw-w64` + `g++-mingw-w64`, which in turn pull in the per-arch
|
||||
posix/win32 alternatives above.
|
||||
(https://packages.debian.org/bookworm/mingw-w64)
|
||||
|
||||
- **X11 / audio / input dev headers** — all present in Debian bookworm,
|
||||
confirmed for the two most likely to have drifted (Mesa's package layout
|
||||
changed upstream over the years, and Wayland dev headers are a newer
|
||||
addition to this image's dependency list than the rest):
|
||||
- `libx11-dev` — present, version `2:1.8.4-2+deb12u2`.
|
||||
- `libgl1-mesa-dev` — present in **both** bookworm and trixie, but is now
|
||||
a *transitional dummy package* (bookworm: `22.3.6-1+deb12u2`, trixie:
|
||||
`25.0.7-2+deb13u1`). It still installs and still satisfies the name
|
||||
this Dockerfile currently apt-gets, so no immediate Dockerfile change
|
||||
is forced — but it signals Debian/Mesa upstream has moved the real
|
||||
package elsewhere, worth re-checking before a future major-version
|
||||
bump.
|
||||
- `libwayland-dev` — present in both bookworm (`1.21.0-1`) and trixie
|
||||
(`1.23.1-3`).
|
||||
- `libxcursor-dev`, `libxinerama-dev`, `libasound2-dev`, `libpulse-dev`,
|
||||
`libudev-dev`, `libxi-dev`, `libxrandr-dev` were not individually
|
||||
fetched against packages.debian.org in this pass (not budgeted) but
|
||||
are all long-standing, uncontroversial Debian-maintained dev packages
|
||||
with no known renames — high confidence they resolve the same way, and
|
||||
this repo's own Ubuntu (Debian-derived) base already installs them
|
||||
successfully today under the identical names.
|
||||
|
||||
Net: Debian's package repo has no analog to the "Alpine only has this in
|
||||
unpinned `edge`" problem #4-#6 hit repeatedly — everything needed here is
|
||||
in Debian's current *stable* suite (`bookworm`), the one Docker's official
|
||||
`debian:bookworm-slim` tag tracks.
|
||||
|
||||
## 3. Official Blender Linux tarball on Debian slim
|
||||
|
||||
- Blender's own requirements page states Blender on Linux requires
|
||||
**glibc 2.28 or newer** (older glibc, e.g. RHEL 7's 2.17, is told to stay
|
||||
on the legacy Blender 3.4 release instead) — no musl/Alpine target is
|
||||
mentioned anywhere on that page.
|
||||
(https://www.blender.org/download/requirements/ — direct fetch returned
|
||||
HTTP 403 to this tool during research; content confirmed via search-engine
|
||||
snippet of that exact page, and consistent with #5's identical finding
|
||||
citing the same URL.)
|
||||
- `debian:bookworm-slim`'s `libc6` package is glibc **2.36**
|
||||
(https://packages.debian.org/bookworm/libc6) — well above the 2.28 floor.
|
||||
Ubuntu Noble ships glibc 2.39, also well above it. Both glibc-based
|
||||
options are equally compatible; there is no compatibility argument for
|
||||
choosing one over the other, only the (near-nil, see §1) size argument.
|
||||
- Because the official tarball is a normal dynamically-linked glibc ELF
|
||||
binary (per #5's finding that it's built for glibc ≥2.28 with no static
|
||||
linking), it runs on **any** sufficiently-recent glibc distro as-is —
|
||||
Debian slim included — with none of #5's `gcompat`/missing-symbol
|
||||
failure modes (`libGLU.so.1`, `libXrender.so.1`, `__sched_cpualloc`,
|
||||
`qsort_r`, `backtrace`) that a musl base hits. This repo's existing
|
||||
Dockerfile already installs `libxkbcommon0 libsm6 libice6` as Blender's
|
||||
extra runtime deps beyond what `barichello/godot-ci` provides — those are
|
||||
ordinary Debian/Ubuntu packages that would carry over unchanged to a
|
||||
Debian slim base.
|
||||
|
||||
## 4. Other glibc-based lightweight distros
|
||||
|
||||
- **`gcr.io/distroless/*`** (Google's distroless images) — glibc-based
|
||||
(the `-base`/`cc` variants ship glibc + libstdc++) and smaller than even
|
||||
Alpine in the final layer, but they ship **no package manager at all** —
|
||||
there is no `apt-get`/`apk` equivalent to install `mingw-w64`, X11/audio
|
||||
dev headers, `curl`, `build-essential`, etc. They're designed to be a
|
||||
**final-stage-only** copy target in a multi-stage build (copy prebuilt
|
||||
binaries + their resolved `.so` deps in, nothing to build with). Since
|
||||
this image's whole job is to be a **build environment** (SCons + mingw-w64
|
||||
cross-compile + Blender headless, all invoked live in CI, not shipped as
|
||||
a slim runtime artifact), distroless can't serve as the image's base at
|
||||
all — noted per the ticket's ask, but out of scope for this repo's use
|
||||
case, not a real candidate.
|
||||
- No other commonly-cited glibc "slim" distro (e.g. `chainguard/wolfi`,
|
||||
which is also mentioned in Alpine-alternative discussions) was
|
||||
investigated in depth here — flagged as a possible future angle only if
|
||||
Debian slim's near-zero size win turns out to matter more than estimated
|
||||
once layered with Godot/Blender/mingw-w64, which §1's numbers suggest is
|
||||
unlikely.
|
||||
|
||||
## Comparison against #4/#5/#6's Alpine findings
|
||||
|
||||
| | Alpine (musl) — #4/#5/#6 | Debian slim (glibc) — this ticket |
|
||||
|---|---|---|
|
||||
| Base image size vs Ubuntu | Musl images are the real small option (~3-8 MB compressed vs Ubuntu's ~28 MB) — an order of magnitude smaller | `bookworm-slim` 26.92 MB vs `ubuntu:noble` 28.37 MB — ~5% smaller, within noise |
|
||||
| `mingw-w64` posix-threads | Yes, but posix-only by construction, and package only in unpinned `edge`/`community` (not a stable release) | Yes, explicit `-posix`/`-win32` alternatives via `update-alternatives`, same pattern this repo already uses, present in stable `bookworm` |
|
||||
| X11/audio/udev dev headers | Not separately audited in #4-#6 (moot given the musl-runtime blocker below) | Confirmed present in `bookworm`/`trixie`; `libgl1-mesa-dev` now a transitional dummy package (still installs, name may not survive a distant future Debian bump) |
|
||||
| Official Godot Linux binary | Requires `gcompat` at best, unverified/likely-partial; one open segfault report (godot/godot#21282) on musl distros, no confirmation gcompat fixes it; official binary needs glibc 2.34 (Fedora build container) | Runs unmodified — glibc 2.36 (bookworm) / 2.39 (noble) both clear Godot's build-container glibc 2.34 baseline |
|
||||
| Official Blender Linux tarball | Does not work as-is (glibc-only build, glibc ≥2.28 required); `gcompat` attempt found in the wild failed with missing libs/symbols; musl-native `blender-headless` exists but only in Alpine's unpinned `edge` | Runs unmodified — same glibc ≥2.28 requirement, cleared by both `bookworm` (2.36) and Ubuntu Noble (2.39) |
|
||||
| Repo pinning/reproducibility | Needs Alpine `edge` for mingw-w64 and/or musl Blender — a rolling, unpinned branch | Everything needed is in Debian's current *stable* suite, a pinned/versioned release like Ubuntu already gives this repo |
|
||||
| Net verdict | Blocked on unverified musl runtime risk for both Godot and Blender, and edge-only package pinning | Capability-complete and low-risk, but no meaningful size/build-time win over the status quo to justify the migration |
|
||||
|
||||
## Sources
|
||||
|
||||
- https://hub.docker.com/_/debian/tags — Docker Hub official `debian` image tags (bookworm-slim, trixie-slim compressed sizes)
|
||||
- https://hub.docker.com/_/ubuntu/tags — Docker Hub official `ubuntu` image tags (noble compressed size)
|
||||
- https://packages.debian.org/bookworm/mingw-w64
|
||||
- https://packages.debian.org/bookworm/gcc-mingw-w64-x86-64
|
||||
- https://packages.debian.org/bookworm/libx11-dev
|
||||
- https://packages.debian.org/search?keywords=libgl1-mesa-dev&searchon=names&suite=all§ion=all
|
||||
- https://packages.debian.org/search?keywords=libwayland-dev&searchon=names&suite=all§ion=all
|
||||
- https://packages.debian.org/bookworm/libc6
|
||||
- https://www.blender.org/download/requirements/ (glibc ≥2.28 requirement; matches #5's citation of the same page)
|
||||
- `docs/research/godot-musl-alpine.md` (branch `research/godot-musl-alpine`) — ticket #4 findings on Godot/musl
|
||||
- `docs/research/blender-musl-alpine.md` (branch `research/blender-musl-alpine`) — ticket #5 findings on Blender/musl
|
||||
- `docs/research/mingw-w64-alpine.md` (branch `research/mingw-w64-alpine`) — ticket #6 findings on Alpine mingw-w64
|
||||
- This repo's `Dockerfile` — current `barichello/godot-ci` (Ubuntu Noble) base and full `apt-get install` package list
|
||||
@@ -0,0 +1,212 @@
|
||||
# Do official Godot Linux binaries run on Alpine/musl?
|
||||
|
||||
Research date: 2026-08-06. Scope: official binaries from
|
||||
`downloads.tuxfamily.org` / `github.com/godotengine/godot-builds`
|
||||
(the same source `abarichello/godot-ci` downloads from) — not
|
||||
self-compiled or third-party (e.g. Alpine's own package) builds.
|
||||
|
||||
## VERDICT
|
||||
|
||||
**(a) Editor binary (headless export, `godot --headless --export-release ...`)**
|
||||
**Works with gcompat** *at best, unverified/likely-partial* — more precisely:
|
||||
**Does not work out of the box; requires gcompat, and even gcompat is not
|
||||
confirmed sufficient.**
|
||||
|
||||
- The official Linux editor/export-template binaries are built in a
|
||||
Fedora-based container and dynamically linked against **glibc 2.34**
|
||||
(see Sources: `godotengine/build-containers`). They are not static and
|
||||
not musl-native.
|
||||
- Godot's own source will not even *compile* on musl without patches
|
||||
(undefined `backtrace`/`backtrace_symbols`, `PTHREAD_MUTEX_RECURSIVE_NP`,
|
||||
`isnanf` differences) — this doesn't affect running a prebuilt glibc
|
||||
binary directly, but it confirms the codebase has known musl-specific
|
||||
assumptions that could also bite at runtime.
|
||||
- Multiple Godot maintainers/contributors state plainly: **"Godot
|
||||
currently only runs on Linux distributions with glibc as libc
|
||||
implementation. On distros with musl libc, it won't compile."**
|
||||
(godot-proposals#2439). No official statement confirms gcompat works
|
||||
for the editor.
|
||||
- A related, unresolved report (godot/godot#21282) describes the Godot
|
||||
**editor segfaulting on launch on musl-libc distros (Alpine, Gentoo)**,
|
||||
with the reporter speculating about musl stack-size differences. It was
|
||||
closed/archived as stale, not fixed — so the risk of a crash isn't
|
||||
hypothetical, it has a filed report, even though the report predates
|
||||
gcompat's now-common usage and doesn't test it explicitly.
|
||||
- No GitHub issue, forum post, or doc page found in this research
|
||||
confirms someone successfully ran the *official* headless editor via
|
||||
`gcompat` for exports. Community Docker images for headless Godot CI
|
||||
(e.g. `robpc/docker-godot-headless`, `barichello/godot-ci` itself) are
|
||||
all Debian/Ubuntu-based, not Alpine — which is itself weak circumstantial
|
||||
evidence that the ecosystem defaults to glibc bases rather than having
|
||||
solved this on Alpine.
|
||||
|
||||
**(b) Do export templates care about the host libc?**
|
||||
**No — the concern is purely about the editor binary, not the templates.**
|
||||
|
||||
- Export templates are per-*target-platform* binaries/build artifacts
|
||||
(Windows .exe, macOS .app, Android .apk components, Linux
|
||||
`.x86_64`, etc.) that the editor copies/embeds into the exported
|
||||
project. The editor process doing the exporting does not execute the
|
||||
Linux export template as a subprocess as part of a normal export — it
|
||||
reads/writes template files and packs the PCK. The host libc constrains
|
||||
only whether the **editor binary itself** can run on the CI host; it has
|
||||
no bearing on which libc the *exported game* (or its Linux export
|
||||
template) targets. That's a separate, independent concern for whoever
|
||||
runs the exported game later.
|
||||
- Caveat: this reasoning is architectural inference from how the export
|
||||
pipeline works, not from an explicit Godot doc sentence that says "host
|
||||
libc does not affect templates" — no such explicit statement was found
|
||||
in `docs.godotengine.org`. Treat sub-question (b)'s verdict as
|
||||
**high-confidence but not directly quoted from an official source.**
|
||||
|
||||
## Static / musl-native build question
|
||||
|
||||
**No official static or musl-native Linux build exists.**
|
||||
|
||||
- A GitHub Discussion on `quaadgras/graphics.gd` (a Godot GDExtension/Go
|
||||
binding project) states directly: **"The Godot Foundation doesn't
|
||||
provide official musl builds."** The same discussion explains *why*
|
||||
a static musl build is hard even in principle: Godot needs `dlopen` at
|
||||
runtime to load X11/Wayland/OpenGL/Vulkan drivers, and **musl refuses to
|
||||
implement `dlopen` for statically-linked binaries** (by design, to avoid
|
||||
loading glibc-ABI shared objects into a musl process). The project's
|
||||
workaround is a hand-written assembly TLS trampoline, not something
|
||||
Godot upstream does.
|
||||
- Separately, `godot/godot#111876` shows even Godot's *static library*
|
||||
build mode (`LibGodot`) is currently broken/unsupported on Linux, for
|
||||
unrelated build-system reasons — reinforcing that "just statically link
|
||||
it" is not a solved path upstream.
|
||||
- Alpine Linux itself packages a **musl-native `godot` build** compiled
|
||||
from source (`pkgs.alpinelinux.org`, package `godot` version `4.6-r2`,
|
||||
in the `edge/testing` repo, linked against `so:libc.musl-x86.so.1`).
|
||||
This is a real, working musl-native Godot editor — but it is an
|
||||
Alpine/community-maintained rebuild from source, **not** the official
|
||||
Godot Foundation binary, is in the `testing` repo (not stable/main), and
|
||||
its version lag/coverage relative to the official releases used by
|
||||
`godot-ci` was not verified.
|
||||
- Godot's own compiling-for-Linux docs list Alpine `apk` build
|
||||
dependencies (implying compiling *from source* on Alpine is a supported,
|
||||
documented path — see Sources), which is consistent with "build your
|
||||
own musl-native binary" being viable, while "run the official prebuilt
|
||||
binary on musl" is not.
|
||||
|
||||
## Sub-question 1 detail — editor binary and host libc
|
||||
|
||||
- **Build environment**: `godotengine/build-containers` README states the
|
||||
Linux build container's *base image is Fedora 44*, and its Linux SDK
|
||||
toolchain is "GCC 15.2.0 built against **glibc 2.34**, binutils
|
||||
2.46.0." This is the container that produces the official
|
||||
editor/export-template binaries godot-ci and this repo download. Fedora
|
||||
and its glibc toolchain confirm the binaries are dynamically linked
|
||||
against glibc, not statically linked and not musl.
|
||||
- **Compiling docs list Alpine packages, but for source builds only**:
|
||||
`docs.godotengine.org`'s "Compiling for Linux, *BSD" page lists an
|
||||
Alpine `apk add` package list (`scons pkgconf gcc g++ libx11-dev ...`)
|
||||
for people who want to *compile Godot themselves* on Alpine. It does
|
||||
**not** discuss musl vs glibc, gcompat, or static linking, and it does
|
||||
not say the official prebuilt binaries work on Alpine. A companion doc
|
||||
bug (`godotengine/godot-docs#9180`, filed 2024-04-03, still open in this
|
||||
research) flags that the Alpine package list is stale (`libexecinfo-dev`
|
||||
removed from Alpine since 3.17) and that musl builds should instead pass
|
||||
`execinfo=no` to SCons — again, this is guidance for *compiling from
|
||||
source on Alpine*, not for running the official downloaded binary.
|
||||
- **Known runtime failure report**: `godot/godot#21282`, "Segmentation
|
||||
fault on opening a project on musl libc" — reproduced on Gentoo and
|
||||
Alpine, editor crashes immediately when opening a project. Root cause
|
||||
undetermined by the reporter ("might be ... musl stack size"); issue
|
||||
closed/archived without resolution. This predates widespread gcompat
|
||||
adoption and doesn't test it, so it neither confirms nor rules out
|
||||
gcompat as a fix — but it is documented evidence that musl-adjacent
|
||||
environments have triggered editor crashes.
|
||||
- **Compile-time musl blockers (indirect evidence of glibc coupling)**:
|
||||
- `godot/godot#63978` — Alpine 3.16 compile fails: undefined references
|
||||
to `backtrace`/`backtrace_symbols` (musl's `execinfo.h` handling
|
||||
differs from glibc's). Closed as "not planned."
|
||||
- `godot/godot#31555` — VHACD third-party module fails to compile on
|
||||
musl due to `PTHREAD_MUTEX_RECURSIVE_NP`, a glibc-specific macro.
|
||||
- `godot/godot-proposals#2439` — proposal to `#ifdef __GLIBC__`-guard
|
||||
glibc-specific code and support musl properly; states plainly Godot
|
||||
"currently only runs on Linux distributions with glibc." Closed,
|
||||
linked to PR #63983 (partial follow-up on the `basename` difference,
|
||||
not full musl support).
|
||||
- `godot/godot#20037` — long-running "[TRACKER] musl libc support in
|
||||
Godot," opened by someone building an Alpine-based Godot Docker image,
|
||||
covering `execinfo.h`, `isnanf` vs `isnan`, and the segfault above.
|
||||
Closed without musl becoming an official target.
|
||||
- **gcompat's actual capability**: Alpine's gcompat package/mirror
|
||||
(`Stantheman/gcompat` on GitHub, description corroborated via search
|
||||
results; the canonical `wiki.alpinelinux.org/wiki/Running_glibc_programs`
|
||||
page returned HTTP 403 to automated fetch in this research and could not
|
||||
be quoted directly) is described consistently across secondary sources
|
||||
as: a small compatibility shim providing glibc-ABI-compatible symbols
|
||||
and dynamic-linker paths so *some* glibc-linked binaries run on musl —
|
||||
explicitly **not a full glibc reimplementation**, and known to fail for
|
||||
more complex binaries (a commonly cited example in search results:
|
||||
Godot-Jolt, a Godot GDExtension, reportedly does **not** work on Alpine
|
||||
even with gcompat or `alpine-pkg-glibc` installed — secondary evidence,
|
||||
a search-result summary rather than a page this research fetched and
|
||||
quoted directly). This is consistent with an engine-sized C++ binary
|
||||
like the Godot editor being a poor gcompat candidate, but there is no
|
||||
first-party confirmation either way for the Godot editor specifically.
|
||||
|
||||
## Sub-question 2 detail — export templates and libc
|
||||
|
||||
- Godot's Linux export docs (`docs.godotengine.org/en/stable/tutorials/export/exporting_for_linux.html`)
|
||||
discuss export architecture options (x86_64/x86_32/arm) but say nothing
|
||||
about host libc or export-template libc requirements — no relevant text
|
||||
found on that page in this research.
|
||||
- By Godot's export architecture: the editor process reads template
|
||||
binaries (downloaded/bundled per target platform) and assembles the
|
||||
exported project; it does not need to *execute* a Linux export template
|
||||
as part of exporting *to* Linux (self-hosted export execution isn't part
|
||||
of the pipeline the way, say, code-signing tools might be for other
|
||||
platforms). This means:
|
||||
- The **editor's** ability to run on the CI host is gated by the host's
|
||||
libc (glibc vs musl+gcompat), per sub-question 1.
|
||||
- The **export templates'** relevant libc/ABI concerns are about the
|
||||
*target* platform the exported game will run on later (e.g. does the
|
||||
Linux export template embedded into the exported game work on the
|
||||
end-user's glibc or musl system) — a wholly separate question from
|
||||
whether the CI host running the editor is Alpine or Debian.
|
||||
- No official doc text was found stating this explicitly; it is inferred
|
||||
from the documented shape of the export pipeline rather than a quoted
|
||||
source, so treat with the caveat noted in the VERDICT section.
|
||||
|
||||
## Sources
|
||||
|
||||
| URL | What it established |
|
||||
|---|---|
|
||||
| https://github.com/godotengine/build-containers | Official Linux build container base image is Fedora 44, toolchain built against glibc 2.34 — official binaries are glibc-linked, not static/musl. (README fetched via WebFetch) |
|
||||
| https://github.com/godotengine/godot-proposals/issues/2439 | Explicit maintainer/contributor statement: "Godot currently only runs on Linux distributions with glibc... On distros with musl libc, it won't compile." Proposal to add `__GLIBC__` guards; closed, partially addressed by PR #63983. |
|
||||
| https://github.com/godotengine/godot/issues/20037 | Long-running musl-support tracker opened by an Alpine-Docker-image author; covers execinfo.h, isnanf/isnan, segfaults; closed without musl becoming an official target. |
|
||||
| https://github.com/godotengine/godot/issues/21282 | Reported editor segfault on project-open specifically on musl (Alpine, Gentoo); root cause undetermined; closed/archived unresolved. |
|
||||
| https://github.com/godotengine/godot/issues/63978 | Alpine 3.16 compile failure: undefined `backtrace`/`backtrace_symbols` due to musl's execinfo handling; closed "not planned." |
|
||||
| https://github.com/godotengine/godot/issues/31555 | VHACD third-party module uses glibc-specific `PTHREAD_MUTEX_RECURSIVE_NP`, breaking musl compiles. |
|
||||
| https://github.com/godotengine/godot/issues/111876 | Godot's static-library (LibGodot) build mode is currently broken on Linux — static linking isn't a solved path upstream even outside the musl question. |
|
||||
| https://github.com/godotengine/godot-docs/issues/9180 | Docs bug: Alpine `apk` package list in the compiling guide is stale (`libexecinfo-dev` gone since Alpine 3.17); recommends `execinfo=no` for musl source builds. Still open at research time. |
|
||||
| https://docs.godotengine.org/en/4.4/contributing/development/compiling/compiling_for_linuxbsd.html | Official compiling-for-Linux doc; lists an Alpine `apk add` package list for *source* builds; does not mention musl, glibc, gcompat, or static linking explicitly. |
|
||||
| https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_linux.html | Official Linux export doc; covers export architecture options; contains no host-libc or template-libc guidance either way. |
|
||||
| https://github.com/quaadgras/graphics.gd/discussions/242 | Direct statement "The Godot Foundation doesn't provide official musl builds"; explains musl's `dlopen`-refusal-on-static-binaries as the core technical obstacle to a static/musl-native Godot; describes a custom TLS-trampoline workaround used by that (unrelated, third-party) project — not something Godot upstream ships. |
|
||||
| https://pkgs.alpinelinux.org/package/edge/testing/x86/godot | Confirms Alpine ships its own musl-native `godot` package (v4.6-r2, `edge/testing` repo, linked against `so:libc.musl-x86.so.1`) — a real musl-native build, but Alpine/community-maintained, not the official Godot Foundation binary, and in the testing (not stable) repo. |
|
||||
| https://github.com/godotengine/godot-build-scripts | Confirms official release build scripts use containers from `godotengine/build-containers`; did not itself surface Dockerfile content (fetch returned only the repo overview). |
|
||||
| G:\_DEV\repos\godot-ci\Dockerfile (this repo, local file) | Confirms this repo's current image is built `FROM barichello/godot-ci:${GODOT_VERSION}` using `apt-get` (Debian/Ubuntu-based, glibc) — i.e., the status quo this research is evaluating an Alpine alternative against. |
|
||||
|
||||
### Secondary evidence only (not proof, not fetched/quoted directly — flagged per task instructions)
|
||||
|
||||
- Search-result summaries (not independently fetched and verified page-by-page in this research) reported: a Godot Jolt GDExtension addon reportedly does not work on Alpine even with `gcompat` or `alpine-pkg-glibc` installed. Cited as a data point that gcompat compatibility for glibc-linked Godot-adjacent binaries is inconsistent, not as proof about the core editor binary.
|
||||
- `wiki.alpinelinux.org/wiki/Running_glibc_programs` returned HTTP 403 to this research's fetch tool and could not be quoted directly; gcompat's description above is reconstructed from consistent secondary summaries (Medium post, Alpine aports mirror descriptions, HN discussion) rather than the primary wiki text itself. Treat the general characterization of gcompat as reliable (it matches gcompat's well-known public description) but not as a verbatim-cited primary source in this research.
|
||||
- `aronand/godot-alpine` (GitHub repo, "Godot on Alpine Linux") appeared in search results as a potentially relevant project but was not fetched/read in this research; flagging its existence only, not its content, as an unverified pointer for follow-up.
|
||||
- `godotforums.org` thread "Help with exporting games for alpine and musl based systems" — returned only a loading-error page to this research's fetch tool; could not be summarized or cited despite being a promising-looking thread title. Flagging as an unresolved follow-up, not evidence.
|
||||
|
||||
## Bottom line for godot-ci's purposes
|
||||
|
||||
Given the official Linux editor is a glibc 2.34 binary from a Fedora-based
|
||||
build container, and there is no official statement or found report of
|
||||
successfully running it under Alpine+gcompat for headless exports (only an
|
||||
unresolved musl-segfault report and general "gcompat doesn't fix everything"
|
||||
signals), switching this repo's base image to Alpine is **not a low-risk
|
||||
change** without first prototyping it end-to-end (pull the real
|
||||
downloads.tuxfamily.org editor zip, `apk add gcompat`, and run a real
|
||||
`--headless --export-release` in a throwaway Alpine container) to see
|
||||
whether it actually works, rather than assuming gcompat is sufficient.
|
||||
Export templates themselves are not a blocker for this decision either way.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Is a working mingw-w64 toolchain available on Alpine?
|
||||
|
||||
## Verdict
|
||||
|
||||
**Available, and it is posix-threads by default with no win32 alternative** — Alpine Linux's `community` repo ships a real mingw-w64 GCC cross-toolchain (`mingw-w64-gcc`, `x86_64-w64-mingw32-*` binaries) built with `--enable-threads=posix` baked in at build time. There is no `update-alternatives`-style choice the way Debian/Ubuntu offer it: Alpine only ever produces the posix-thread build, so no extra step is needed to select it. The one caveat: the package currently only exists on Alpine's **edge** branch, not on a tagged stable release, and no primary source confirms anyone has actually built Godot's Windows export templates with it.
|
||||
|
||||
## Details
|
||||
|
||||
### 1. Exact Alpine package name(s)
|
||||
|
||||
Confirmed via the Alpine package index (https://pkgs.alpinelinux.org/packages) and the aports source tree (https://gitlab.alpinelinux.org/alpine/aports, mirrored at https://github.com/alpinelinux/aports):
|
||||
|
||||
| Package | Version seen | Repo/branch | Arch | Notes |
|
||||
|---|---|---|---|---|
|
||||
| `mingw-w64-gcc` | 15.2.0-r0 | `community`, **edge only** | x86_64 | The cross-compiler itself: provides `x86_64-w64-mingw32-gcc`, `-g++`, `-cc`, `-c++`, `-cpp`, `-gcov`, `-ar`, `-nm`, `-ranlib`, LTO tools, etc. (13 binaries). https://pkgs.alpinelinux.org/package/edge/community/x86_64/mingw-w64-gcc |
|
||||
| `mingw-w64-gcc-base` | 15.2.0-r0 | community, edge | x86_64 | https://pkgs.alpinelinux.org/package/edge/community/x86/mingw-w64-gcc-base |
|
||||
| `mingw-w64-gcc-ccache` | 15.2.0-r0 | community, edge | x86_64 | ccache-wrapped variant |
|
||||
| `mingw-w64-binutils` | 2.45.1-r0 | community, edge | x86_64 | `as`, `ld`, etc. for the mingw target |
|
||||
| `mingw-w64-crt` | 14.0.0-r0 | community, edge | x86_64 | Windows C runtime |
|
||||
| `mingw-w64-headers` (+ `-bootstrap`, `-doc`) | 14.0.0-r0 | community, edge | x86_64 | Windows API headers |
|
||||
| `mingw-w64-winpthreads` (+ `-doc`) | 14.0.0-r0 | community, edge | x86_64 | pthreads-for-Windows implementation, required dependency of `mingw-w64-gcc` |
|
||||
| `i686-mingw-w64-gcc` / `i686-mingw-w64-binutils` | — | community, edge | x86_64 | 32-bit target cross-toolchain |
|
||||
|
||||
All maintained by Simon Zeni. Source: https://pkgs.alpinelinux.org/packages?name=mingw* (searched "mingw"), and per-package pages linked above.
|
||||
|
||||
**Caveat on availability**: querying the package index without pinning a branch, and per-package pages, show these packages only under the `edge` branch (Alpine's rolling/unstable repo), not under a versioned stable branch (e.g. `v3.20`, `v3.21`) at time of research (2026-08-06). If godot-ci's Dockerfile were ever based on Alpine, it would need `edge`'s `community` repo enabled (or a specific stable release once/if the package gets backported) — this is a meaningfully different posture than Debian, where `mingw-w64` is in every stable suite.
|
||||
|
||||
### 2. Thread model: posix-only, no win32 alternative
|
||||
|
||||
Fetched the APKBUILD build recipe directly from the aports source (https://raw.githubusercontent.com/alpinelinux/aports/master/community/mingw-w64-gcc/APKBUILD, mirroring https://gitlab.alpinelinux.org/alpine/aports/-/blob/master/community/mingw-w64-gcc/APKBUILD): the GCC `configure` invocation includes `--enable-threads=posix` explicitly, with no companion `win32`-thread-model package or build variant anywhere in the mingw-w64 package set.
|
||||
|
||||
This is corroborated structurally by the package index: `mingw-w64-gcc` has `mingw-w64-winpthreads` (the pthreads-for-Windows library) as a hard dependency (https://pkgs.alpinelinux.org/package/edge/community/x86_64/mingw-w64-winpthreads lists `mingw-w64-gcc` as a reverse-dependency). winpthreads is only linked in for the posix thread model — a win32-thread-model GCC wouldn't need it.
|
||||
|
||||
So: **Alpine does not offer a choice** the way Debian does (`x86_64-w64-mingw32-gcc-posix` vs `-win32` selected via `update-alternatives`). There is exactly one `x86_64-w64-mingw32-gcc` binary on Alpine, and it is already the posix variant — no `update-alternatives --set` step is needed or possible.
|
||||
|
||||
### 3. Real-world reports of building Godot on Alpine-hosted mingw-w64
|
||||
|
||||
- **Godot's own official docs** (fetched primary source: https://docs.godotengine.org/es/3.5/_sources/development/compiling/compiling_for_windows.rst.txt, and corroborated by search of the current `compiling_for_windows.rst` in godotengine/godot-docs) state: *"MinGW-w64 with GCC can be used as an alternative to Visual Studio. Be sure to install/configure it to use the `posix` thread model."* They give install commands for **Debian/Ubuntu** (`apt install mingw-w64`), **Fedora**, **Arch Linux** (AUR), **macOS** (Homebrew), and **Mageia**. **Alpine Linux is not mentioned anywhere** in Godot's official compiling-for-Windows documentation.
|
||||
- **Godot's own official build-container project**, `godotengine/build-containers` (https://github.com/godotengine/build-containers/blob/main/Dockerfile.windows), builds its Windows cross-compile image from a Debian/Ubuntu-family base with `mingw32`/`mingw64` apt packages — not Alpine.
|
||||
- No GitHub issue, forum post, or blog post surfaced in searches (`godot alpine mingw-w64-gcc docker windows export template`, `godot windows export template alpine mingw-w64 build scons cross compile`) reporting either a success or a failure building Godot/SCons specifically against Alpine's `mingw-w64-gcc`. The closest hits were generic Alpine-based mingw-w64 Docker images unrelated to Godot (e.g. `portown/alpine-mingw-w64`, `mmozeiko/container-mingw-w64`), and Godot Docker/CI guides that all use Debian/Ubuntu bases.
|
||||
- Conclusion: this combination (Godot SCons + Alpine mingw-w64) appears **untested/undocumented in public sources** — plausible in theory (posix threads, matching GCC 15 > the "GCC 9 or later" minimum Godot's docs state) but unverified in practice.
|
||||
|
||||
## Recommendation
|
||||
|
||||
godot-ci's current `Dockerfile` (`FROM barichello/godot-ci:${GODOT_VERSION}`, confirmed Debian/Ubuntu-based via its `apt-get install` calls) is not on Alpine today, so this question is currently moot for the repo as-is. If a future switch to an Alpine base is considered:
|
||||
|
||||
- Drop the `update-alternatives --set x86_64-w64-mingw32-gcc/-g++ ... -posix` lines entirely — Alpine's `mingw-w64-gcc` is posix-threads-only, there is no alternative to select and the `update-alternatives` binaries/symlinks Debian ships don't exist on Alpine.
|
||||
- Pin to Alpine's `edge` branch (with `community` repo enabled) for `mingw-w64-gcc`, since it is not present in a stable Alpine release at time of writing — re-check https://pkgs.alpinelinux.org/packages?name=mingw-w64-gcc before committing to this, as edge-only packages can be less predictable to pin/reproduce in CI than Debian stable.
|
||||
- Because no one has publicly documented actually building Godot's Windows export templates against Alpine's mingw-w64-gcc, budget time for a real trial build (SCons `platform=windows`) before trusting it in CI — treat it as unverified rather than a known-good path.
|
||||
|
||||
## References
|
||||
|
||||
- https://pkgs.alpinelinux.org/packages?name=mingw* — package index search
|
||||
- https://pkgs.alpinelinux.org/package/edge/community/x86_64/mingw-w64-gcc
|
||||
- https://pkgs.alpinelinux.org/package/edge/community/x86_64/mingw-w64-winpthreads
|
||||
- https://pkgs.alpinelinux.org/package/edge/community/x86_64/i686-mingw-w64-gcc
|
||||
- https://raw.githubusercontent.com/alpinelinux/aports/master/community/mingw-w64-gcc/APKBUILD (mirrors https://gitlab.alpinelinux.org/alpine/aports/-/blob/master/community/mingw-w64-gcc/APKBUILD)
|
||||
- https://docs.godotengine.org/es/3.5/_sources/development/compiling/compiling_for_windows.rst.txt (current-doc source text for the "Requirements"/MinGW section)
|
||||
- https://github.com/godotengine/godot-docs/blob/master/contributing/development/compiling/compiling_for_windows.rst
|
||||
- https://github.com/godotengine/build-containers/blob/main/Dockerfile.windows
|
||||
Reference in New Issue
Block a user