Compare commits

..
18 Commits
Author SHA1 Message Date
haylan 4350f40b5a Merge branch 'main' of https://git.arthurerlich.de/haylan/godot-ci 2026-08-07 01:05:21 +02:00
haylan 1452c4ac96 chore(cache): cache rework and update 2026-08-07 01:05:10 +02:00
haylan 2e2438672f Merge pull request 'docs(research): act_runner Docker volume accumulation and cleanup' (#19) from research/act-runner-volume-cleanup into main
Reviewed-on: #19
2026-08-06 23:00:39 +00:00
haylan 736319458c Merge pull request 'docs(research): investigate Docker build caching options' (#20) from research/build-caching into main
Reviewed-on: #20
2026-08-06 23:00:32 +00:00
haylanandClaude-Bot adbc3cd6e7 docs(research): act_runner Docker volume accumulation and cleanup
Investigates why git.arthurerlich.de's act_runner/Docker CI host
accumulates Docker volumes, grounded in act_runner's own config docs,
Gitea's Actions docs, Docker's official prune/rootless docs, and a
maintainer-confirmed analogous root cause from GitHub's actions/runner.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 00:57:16 +02:00
haylan 08b0fedf6f docs(research): investigate Docker build caching options
Ranked recommendation: fix the docker system prune -af --volumes step
first (it deletes BuildKit's build cache before every build, so no
Dockerfile change can help until that stops), then split the
apt-get install layer from the Godot/Blender download layer so the
~470s apt layer becomes cache-eligible across nightly runs. Registry
cache and Gitea Actions' actions/cache don't fit this repo's single
persistent runner / multi-GB nightly-changing layers.

Research only, no code changes.
2026-08-07 00:52:49 +02:00
haylan f0307c6558 Merge pull request 'docs(research): investigate Blender on Alpine/musl' (#18) from research/blender-musl-alpine into main 2026-08-06 22:43:59 +00:00
haylan 0c3a55a8cf Merge pull request 'docs(research): investigate Godot on Alpine/musl' (#17) from research/godot-musl-alpine into main 2026-08-06 22:43:42 +00:00
haylan 9982d6d098 Merge pull request 'docs(research): evaluate debian-slim vs alpine for base image (#8)' (#15) from research/glibc-slim-alternative into main 2026-08-06 22:41:51 +00:00
haylan c2795b0bba Merge pull request 'fix(docker): bake blender_path into EditorSettings for headless import' (#16) from fix/bake-blender-editor-settings into main 2026-08-06 22:38:53 +00:00
haylan a785bb2af0 Merge pull request 'docs(research): investigate mingw-w64 availability on Alpine' (#14) from research/mingw-w64-alpine into main 2026-08-06 22:37:58 +00:00
haylanandClaude-Bot 4a4e5d5f6d fix(docker): bake blender_path into EditorSettings for headless import
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" (modules/gltf/editor/editor_scene_importer_blend.cpp).
Without this, the image's Blender install was silently unusable for its one
job — no .blend file would ever import in CI.

Bakes filesystem/import/blender/blender_path into a per-minor-version
EditorSettings .tres, same pattern barichello/godot-ci and abarichello's own
Dockerfile use for Android SDK/JDK paths.

Found and verified while resolving ticket #9 (Merge, publish, and validate
the debian-slim image end-to-end): confirmed a .blend import produces a real
.scn with this fix, and fails without it, on a real container built from
this Dockerfile.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 00:37:48 +02:00
haylan 516b61018e Merge branch 'main' into research/glibc-slim-alternative 2026-08-06 22:31:37 +00:00
haylan 8c36d01e2e Merge branch 'main' into research/mingw-w64-alpine 2026-08-06 22:31:24 +00:00
haylan ea7f49fe9f docs(research): evaluate debian-slim vs alpine for base image (#8)
Answers wayfinder ticket #8: does a glibc-based slim distro
(debian:bookworm-slim / trixie-slim) get most of Alpine's size win
without the musl risk #4-#6 found?

Verdict: not worth it. Debian bookworm-slim (26.92 MB compressed) is
within ~5% of ubuntu:noble (28.37 MB) per Docker Hub's official image
listings - nowhere near Alpine's musl size class. Debian does clear
every capability gap Alpine hit (posix-threads mingw-w64 in stable,
X11/audio dev headers, official Blender tarball runs unmodified since
it's glibc), but the size case for leaving Ubuntu was Alpine's alone.

Related to #4, #5, #6
2026-08-06 22:44:34 +02:00
haylan 2bfbffd5c5 docs(research): investigate mingw-w64 availability on Alpine
Answers wayfinder issue #6: Alpine's community/edge repo ships a
posix-threads-only mingw-w64-gcc cross toolchain, but it's edge-only
(not in a stable release) and no primary source confirms anyone has
built Godot Windows export templates with it.

Related to #6
2026-08-06 22:37:29 +02:00
haylanandClaude-Bot c33760b3af docs(research): investigate Godot musl/Alpine compatibility (#4)
Captures findings for wayfinder ticket #4: whether official Godot
Linux editor/export-template binaries run under Alpine/musl.

Throwaway research branch, not intended to merge.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 22:36:46 +02:00
haylanandClaude-Bot a6645add28 docs(research): investigate Blender under Alpine/musl (ticket #5)
Verdict: official glibc tarball does not run on Alpine as-is; gcompat
is not a credible fix for something Blender-sized (per gcompat's own
docs and one concrete failed community attempt). A musl-native
alternative exists (Alpine community's blender-headless package,
5.2.0-r0) but only on the edge branch, not a stable release — a real
argument against Alpine for this pipeline's pinned-version needs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 22:34:33 +02:00
8 changed files with 1280 additions and 1 deletions
+13 -1
View File
@@ -47,4 +47,16 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
&& mkdir -p /opt/blender \ && mkdir -p /opt/blender \
&& tar -xJf /tmp/blender.tar.xz -C /opt/blender --strip-components=1 \ && tar -xJf /tmp/blender.tar.xz -C /opt/blender --strip-components=1 \
&& ln -sf /opt/blender/blender /usr/local/bin/blender \ && 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"
+5
View File
@@ -0,0 +1,5 @@
{
"enabledPlugins": {
"mattpocock-skills@claude-plugins-official": true
}
}
+318
View File
@@ -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/)
+182
View File
@@ -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)
+282
View File
@@ -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 1962), 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 8689, 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 1962): 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
+208
View File
@@ -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&section=all
- https://packages.debian.org/search?keywords=libwayland-dev&searchon=names&suite=all&section=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
+212
View File
@@ -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.
+60
View File
@@ -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