Repository: gohugoio/hugo (language: go)

Issue:
release: Update to the latest Go version
For the release build + Docker file.

Current versions are:

* Release binaries: Go 1.23.0
* Docker: Go 1.22.6

Latest Go version is Go 1.23.2, see https://go.dev/doc/devel/release#go1.23.0. There have been some security fixes. I don't see how these could be exploited in Hugo, but I understand that many would want a clean security report.

```
go1.23.1 (released 2024-09-05) includes security fixes to the encoding/gob, go/build/constraint, and go/parser packages, as well as bug fixes to the compiler, the go command, the runtime, and the database/sql, go/types, os, runtime/trace, and unique packages. See the [Go 1.23.1 milestone](https://github.com/golang/go/issues?q=milestone%3AGo1.23.1+label%3ACherryPickApproved) on our issue tracker for details.

go1.23.2 (released 2024-10-01) includes fixes to the compiler, cgo, the runtime, and the maps, os, os/exec, time, and unique packages. See the [Go 1.23.2 milestone](https://github.com/golang/go/issues?q=milestone%3AGo1.23.2+label%3ACherryPickApproved) on our issue tracker for details.
```


Discussion / hints:

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 0ff9559365e..6a82e96b7d0 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -4,7 +4,7 @@ parameters:
 defaults: &defaults
   resource_class: large
   docker:
-      - image: bepsays/ci-hugoreleaser:1.22300.20000
+      - image: bepsays/ci-hugoreleaser:1.22300.20200
 environment: &buildenv
       GOMODCACHE: /root/project/gomodcache
 version: 2
@@ -60,7 +60,7 @@ jobs:
     environment:
         <<: [*buildenv]
     docker:
-    - image: bepsays/ci-hugoreleaser-linux-arm64:1.22300.20000
+    - image: bepsays/ci-hugoreleaser-linux-arm64:1.22300.20200
     steps:
       - *restore-cache
       - &attach-workspace
diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml
index be128d6fd32..95a4ad0b638 100644
--- a/.github/workflows/image.yml
+++ b/.github/workflows/image.yml
@@ -3,6 +3,7 @@ name: Build Docker image
 on:
   release:
     types: [published]
+  pull_request:
 permissions:
   packages: write
 
@@ -12,18 +13,8 @@ env:
 jobs:
   build:
     runs-on: ubuntu-latest
-    strategy:
-      fail-fast: false
-      matrix:
-        platform:
-          - linux/amd64
-          - linux/arm64
-    steps:
-      - name: Prepare
-        run: |
-          platform=${{ matrix.platform }}
-          echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
 
+    steps:
       - name: Checkout
         uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
 
@@ -33,9 +24,6 @@ jobs:
         with:
           images: ${{ env.REGISTRY_IMAGE }}
 
-      - name: Set up QEMU
-        uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
-
       - name: Set up Docker Buildx
         uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
 
@@ -47,69 +35,14 @@ jobs:
           username: ${{ github.repository_owner }}
           password: ${{ secrets.GITHUB_TOKEN }}
 
-      - name: Build and push by digest
+      - name: Build and push
         id: build
         uses: docker/build-push-action@16ebe778df0e7752d2cfcbd924afdbbd89c1a755 # v6.6.1
         with:
           context: .
-          push: ${{ startsWith(github.ref, 'refs/tags') }}
-          platforms: ${{ matrix.platform }}
+          provenance: mode=max
+          sbom: true
+          push: ${{ github.event_name != 'pull_request' }}
+          platforms: linux/amd64,linux/arm64
+          tags: ${{ steps.meta.outputs.tags }}
           labels: ${{ steps.meta.outputs.labels }}
-          outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
-
-      - name: Export digest
-        run: |
-          mkdir -p /tmp/digests
-          digest="${{ steps.build.outputs.digest }}"
-          touch "/tmp/digests/${digest#sha256:}"
-
-      - name: Upload digest
-        uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
-        with:
-          name: digests-${{ env.PLATFORM_PAIR }}
-          path: /tmp/digests/*
-          if-no-files-found: error
-          retention-days: 1
-
-  merge:
-    runs-on: ubuntu-latest
-    needs:
-      - build
-    steps:
-      - name: Download digests
-        uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
-        with:
-          path: /tmp/digests
-          pattern: digests-*
-          merge-multiple: true
-
-      - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
-
-      - name: Docker meta
-        id: meta
-        uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
-        with:
-          images: ${{ env.REGISTRY_IMAGE }}
-
-          flavor: |
-            latest=false
-
-      - name: Login to GHCR
-        uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
-        with:
-          registry: ghcr.io
-          username: ${{ github.repository_owner }}
-          password: ${{ secrets.GITHUB_TOKEN }}
-
-      - name: Create manifest list and push
-        if: ${{ startsWith(github.ref, 'refs/tags') }}
-        working-directory: /tmp/digests
-        run: |
-          docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-            $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
-
-      - name: Inspect image
-        if: ${{ startsWith(github.ref, 'refs/tags') }}
-        run: |
-          docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
diff --git a/Dockerfile b/Dockerfile
index fb72b77f18c..394133aed9e 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,44 +2,94 @@
 # Twitter:      https://twitter.com/gohugoio
 # Website:      https://gohugo.io/
 
-FROM golang:1.22.6-alpine AS build
+ARG GO_VERSION="1.23.2"
+ARG ALPINE_VERSION=3.20
 
-# Optionally set HUGO_BUILD_TAGS to "extended" or "nodeploy" when building like so:
-#   docker build --build-arg HUGO_BUILD_TAGS=extended .
-ARG HUGO_BUILD_TAGS
+FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.5.0 AS xx
+FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS gobuild
+FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS gorun
 
-ARG CGO=1
-ENV CGO_ENABLED=${CGO}
-ENV GOOS=linux
-ENV GO111MODULE=on
+
+FROM gobuild AS build
+
+RUN apk add clang lld
+
+# Set up cross-compilation helpers
+COPY --from=xx / /
+
+ARG TARGETPLATFORM
+RUN xx-apk add musl-dev gcc g++ 
+
+# Optionally set HUGO_BUILD_TAGS to "none" or "nodeploy" when building like so:
+# docker build --build-arg HUGO_BUILD_TAGS=nodeploy .
+#
+# We build the extended version by default.
+ARG HUGO_BUILD_TAGS="extended"
+ENV CGO_ENABLED=1
+ENV GOPROXY=https://proxy.golang.org
+ENV GOCACHE=/root/.cache/go-build
+ENV GOMODCACHE=/go/pkg/mod
+ARG TARGETPLATFORM
 
 WORKDIR /go/src/github.com/gohugoio/hugo
 
-COPY . /go/src/github.com/gohugoio/hugo/
+# For  --mount=type=cache the value of target is the default cache id, so
+# for the go mod cache it would be good if we could share it with other Go images using the same setup,
+# but the go build cache needs to be per platform.
+# See this comment: https://github.com/moby/buildkit/issues/1706#issuecomment-702238282
+RUN --mount=target=. \
+    --mount=type=cache,target=/go/pkg/mod \
+    --mount=type=cache,target=/root/.cache/go-build,id=go-build-$TARGETPLATFORM <<EOT
+    set -ex
+    xx-go build -tags "$HUGO_BUILD_TAGS" -ldflags "-s -w -X github.com/gohugoio/hugo/common/hugo.vendorInfo=docker" -o /usr/bin/hugo
+    xx-verify /usr/bin/hugo
+EOT
 
-# gcc/g++ are required to build SASS libraries for extended version
-RUN apk update && \
-    apk add --no-cache gcc g++ musl-dev git && \
-    go install github.com/magefile/mage
+FROM gorun AS final
 
-RUN mage hugo && mage install
+COPY --from=build /usr/bin/hugo /usr/bin/hugo
 
-# ---
+# libc6-compat  are required for extended libraries (libsass, libwebp).
+RUN apk add --no-cache \
+    libc6-compat \
+    git \
+    runuser \
+    curl \
+    nodejs \
+    npm
 
-FROM alpine:3.18
+RUN mkdir -p /var/hugo/bin && \
+    addgroup -Sg 1000 hugo && \
+    adduser -Sg hugo -u 1000 -h /var/hugo hugo && \
+    chown -R hugo: /var/hugo && \
+    # For the Hugo's Git integration to work.
+    runuser -u hugo -- git config --global --add safe.directory /project && \ 
+    # See https://github.com/gohugoio/hugo/issues/9810
+    runuser -u hugo -- git config --global core.quotepath false
 
-COPY --from=build /go/bin/hugo /usr/bin/hugo
+VOLUME /project
+WORKDIR /project
+USER hugo:hugo
+ENV HUGO_CACHEDIR=/cache
+ARG BUILDARCH
+ENV BUILDARCH=${BUILDARCH}
+ENV PATH="/var/hugo/bin:$PATH"
 
-# libc6-compat & libstdc++ are required for extended SASS libraries
-# ca-certificates are required to fetch outside resources (like Twitter oEmbeds)
-RUN apk update && \
-    apk add --no-cache ca-certificates libc6-compat libstdc++ git
+COPY scripts/docker scripts/docker
+COPY scripts/docker/entrypoint.sh /entrypoint.sh
 
-VOLUME /site
-WORKDIR /site
+# Install default dependencies.
+RUN scripts/docker/install_runtimedeps_default.sh
+# Update PATH to reflect the new dependencies.
+# For more complex setups, we should probably find a way to
+# delegate this to the script itself, but this will have to do for now.
+# Also, the dart-sass binary is a little special, other binaries can be put/linked
+# directly in /var/hugo/bin.
+ENV PATH="/var/hugo/bin/dart-sass:$PATH"
 
 # Expose port for live server
 EXPOSE 1313
 
-ENTRYPOINT ["hugo"]
+ENTRYPOINT ["/entrypoint.sh"]
 CMD ["--help"]
+
diff --git a/scripts/docker/entrypoint.sh b/scripts/docker/entrypoint.sh
new file mode 100755
index 00000000000..20ffbe5f76d
--- /dev/null
+++ b/scripts/docker/entrypoint.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+# Check if a custom hugo-docker-entrypoint.sh file exists.
+if [ -f hugo-docker-entrypoint.sh ]; then
+  # Execute the custom entrypoint file.
+  sh hugo-docker-entrypoint.sh "$@"
+  exit $?
+fi
+
+# Check if a package.json file exists.
+if [ -f package.json ]; then
+  # Check if node_modules exists.
+  if [ ! -d node_modules ]; then
+    # Install npm packages.
+    # Note that we deliberately do not use `npm ci` here, as it would fail if the package-lock.json file is not up-to-date,
+    # which would be the case if you run the container with a different OS or architecture than the one used to create the package-lock.json file.
+    npm i
+  fi
+fi
+
+exec "hugo" "$@"
\ No newline at end of file
diff --git a/scripts/docker/install_runtimedeps_default.sh b/scripts/docker/install_runtimedeps_default.sh
new file mode 100755
index 00000000000..0b6c2c61708
--- /dev/null
+++ b/scripts/docker/install_runtimedeps_default.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+set -ex
+
+export DART_SASS_VERSION=1.79.3
+
+# If $BUILDARCH=arm64, then we need to install the arm64 version of Dart Sass,
+# otherwise we install the x64 version.
+ARCH="x64"
+if [ "$BUILDARCH" = "arm64" ]; then
+    ARCH="arm64"
+fi
+
+cd /tmp
+curl -LJO https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-${ARCH}.tar.gz 
+ls -ltr
+tar -xf dart-sass-${DART_SASS_VERSION}-linux-${ARCH}.tar.gz
+rm dart-sass-${DART_SASS_VERSION}-linux-${ARCH}.tar.gz && \
+# The dart-sass folder is added to the PATH by the caller.
+mv dart-sass /var/hugo/bin
\ No newline at end of file


Test patch: