Skip to content

Builder image pins GOTOOLCHAIN=local, so xcaddy fails when a module needs a newer Go #2674

Description

@soyuka

What happened?

The builder image sets GOTOOLCHAIN=local, so xcaddy cannot fetch a newer Go toolchain. Any
Caddy module that declares a higher Go version than the image ships fails the documented custom
build, even though Go can resolve this by itself.

Today, on dunglas/frankenphp:1.12.7-builder-php8.5:

$ docker run --rm --entrypoint sh dunglas/frankenphp:1.12.7-builder-php8.5 \
    -c 'go version; go env GOTOOLCHAIN'
go version go1.26.8 linux/amd64
local

Adding Mercure 1.0 to the recipe from docs/docker.md then fails:

go: downloading github.com/dunglas/mercure/caddy v1.0.2
go: github.com/dunglas/mercure/caddy@v1.0.2 requires go >= 1.27 (running go 1.26.8; GOTOOLCHAIN=local)
2026/09/27 06:29:14 [FATAL] exit status 1

Minimal reproduction, which is the documented recipe plus one --with:

FROM dunglas/frankenphp:1.12.7-builder-php8.5 AS builder
COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy
RUN CGO_ENABLED=1 \
    XCADDY_SETCAP=1 \
    XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
    CGO_CFLAGS=$(php-config --includes) \
    CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
    xcaddy build \
        --output /usr/local/bin/frankenphp \
        --with github.com/dunglas/frankenphp=./ \
        --with github.com/dunglas/frankenphp/caddy=./caddy/ \
        --with github.com/dunglas/caddy-cbrotli \
        --with github.com/dunglas/mercure/caddy@v1.0.2 \
        --with github.com/dunglas/vulcain/caddy

Adding GOTOOLCHAIN=auto to that RUN makes it succeed. The resulting binary is correct:

$ frankenphp list-modules | grep mercure
http.handlers.mercure

Why this is worth a structural fix

This is the third report of the same failure, and the first two were resolved by raising the Go
version in the image:

Raising the version fixes the module that prompted it and leaves the next one broken. The pin is
what causes the recurrence: local tells Go to refuse the very mechanism that exists to resolve
this. The reporter of #1828 already identified GOTOOLCHAIN=auto as the workaround.

Two options, either of which ends the cycle:

  1. Set GOTOOLCHAIN=go1.26+auto in the builder image. Builds keep using the shipped toolchain
    and step up only when a module asks for more, so nothing is downloaded in the common case.
  2. Keep the pin and document it. Add GOTOOLCHAIN=auto to the RUN in docs/docker.md, and say
    that a module declaring a newer Go needs it.

Option 1 means the documented recipe keeps working when a dependency moves. Option 2 at least
turns a [FATAL] into something the docs predict.

I am happy to send a PR for either. Tell me which you prefer.

Build Type

Custom (tell us more in the description)

Worker Mode

Yes

Operating System

Linux

FrankenPHP version

1.12.7 (dunglas/frankenphp:1.12.7-builder-php8.5)

PHP version

8.5

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions