diff --git a/.forgejo/actions/build-release/action.yml b/.forgejo/actions/build-release/action.yml
new file mode 100644
index 0000000000..01fdfdedfc
--- /dev/null
+++ b/.forgejo/actions/build-release/action.yml
@@ -0,0 +1,154 @@
+name: 'Build release'
+author: 'Forgejo authors'
+description: |
+  Build release
+
+inputs:
+  forgejo:
+    description: 'URL of the Forgejo instance where the release is uploaded'
+    required: true
+  owner:
+    description: 'User or organization where the release is uploaded, relative to the Forgejo instance'
+    required: true
+  repository:
+    description: 'Repository where the release is uploaded, relative to the owner'
+    required: true
+  doer:
+    description: 'Name of the user authoring the release'
+    required: true
+  tag-version:
+    description: 'Version of the release derived from the tag withint the leading v'
+    required: true
+  suffix:
+    description: 'Suffix to add to the image tag'
+  token:
+    description: 'token'
+    required: true
+  dockerfile:
+    description: 'path to the dockerfile'
+    default: 'Dockerfile'
+  platforms:
+    description: 'Coma separated list of platforms'
+    default: 'linux/amd64,linux/arm64'
+  release-notes:
+    description: 'Full text of the release notes'
+    default: 'Release notes placeholder'
+  binary-name:
+    description: 'Name of the binary'
+  binary-path:
+    description: 'Path of the binary within the container to extract into binary-name'
+  verbose:
+    description: 'Increase the verbosity level'
+    default: 'false'
+
+runs:
+  using: "composite"
+  steps:
+    - run: echo "${{ github.action_path }}" >> $GITHUB_PATH
+      shell: bash
+
+    - name: Install dependencies
+      run: |
+        apt-get install -y -qq xz-utils
+
+    - name: set -x if verbose is required
+      id: verbose
+      run: |
+        if ${{ inputs.verbose }} ; then
+          echo "shell=set -x" >> "$GITHUB_OUTPUT"
+        fi
+
+    - name: Create the insecure and buildx-config variables for the container registry
+      id: registry
+      run: |
+        ${{ steps.verbose.outputs.shell }}
+        url="${{ inputs.forgejo }}"
+        hostport=${url##http*://}
+        hostport=${hostport%%/}
+        echo "host-port=${hostport}" >> "$GITHUB_OUTPUT"
+        if ! [[ $url =~ ^http:// ]] ; then
+           exit 0
+        fi
+        cat >> "$GITHUB_OUTPUT" <<EOF
+        insecure=true
+        buildx-config<<ENDVAR
+        [registry."${hostport}"]
+          http = true
+        ENDVAR
+        EOF
+
+    - name: Allow docker pull/push to forgejo
+      if: ${{ steps.registry.outputs.insecure }}
+      run: |-
+        mkdir -p /etc/docker
+        cat > /etc/docker/daemon.json <<EOF
+          {
+            "insecure-registries" : ["${{ steps.registry.outputs.host-port }}"],
+            "bip": "172.26.0.1/16"
+          }
+        EOF
+
+    - name: Install docker
+      run: |
+        echo deb http://deb.debian.org/debian bullseye-backports main | tee /etc/apt/sources.list.d/backports.list && apt-get -qq update
+        DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -qq -y -t bullseye-backports docker.io
+
+    - uses: https://github.com/docker/setup-buildx-action@v2
+      with:
+        config-inline: |
+         ${{ steps.registry.outputs.buildx-config }}
+
+    - name: Login to the container registry
+      run: |
+        BASE64_AUTH=`echo -n "${{ inputs.doer }}:${{ inputs.token }}" | base64 -w0`
+        mkdir -p ~/.docker
+        echo "{\"auths\": {\"$CI_REGISTRY\": {\"auth\": \"$BASE64_AUTH\"}}}" > ~/.docker/config.json
+      env:
+        CI_REGISTRY: "${{ steps.registry.outputs.host-port }}"
+
+    - name: Build the container image for each architecture
+      uses: https://github.com/docker/build-push-action@v4
+      # workaround until https://github.com/docker/build-push-action/commit/d8823bfaed2a82c6f5d4799a2f8e86173c461aba is in @v4 or @v5 is released
+      env:
+        ACTIONS_RUNTIME_TOKEN: ''
+      with:
+        context: .
+        push: true
+        file: ${{ inputs.dockerfile }}
+        platforms: ${{ inputs.platforms }}
+        tags: ${{ steps.registry.outputs.host-port }}/${{ inputs.owner }}/${{ inputs.repository }}:${{ inputs.tag-version }}${{ inputs.suffix }}
+
+    - name: Extract the binary from the container images into the release directory
+      if: inputs.binary-name != ''
+      run: |
+        ${{ steps.verbose.outputs.shell }}
+        mkdir -p release
+        cd release
+        for platform in $(echo ${{ inputs.platforms }} | tr ',' ' '); do
+          arch=$(echo $platform | sed -e 's|linux/||g' -e 's|arm/v6|arm-6|g')
+          docker create --platform $platform --name forgejo-$arch ${{ steps.registry.outputs.host-port }}/${{ inputs.owner }}/${{ inputs.repository }}:${{ inputs.tag-version }}${{ inputs.suffix }}
+          binary="${{ inputs.binary-name }}-${{ inputs.tag-version }}-linux"
+          docker cp forgejo-$arch:${{ inputs.binary-path }} $binary-$arch
+          chmod +x $binary-$arch
+          # the displayed version has a + instead of the first -, deal with it
+          pattern=$(echo "${{ inputs.tag-version }}" | tr - .)
+          if ! ./$binary-$arch --version | grep "$pattern" ; then
+            echo "ERROR: expected version pattern $pattern not found in the output of $binary-$arch --version"
+            ./$binary-$arch --version
+            exit 1
+          fi
+          xz --keep -9 $binary-$arch
+          shasum -a 256 $binary-$arch > $binary-$arch.sha256
+          shasum -a 256 $binary-$arch.xz > $binary-$arch.xz.sha256
+          docker rm forgejo-$arch
+        done
+
+    - name: publish release
+      if: inputs.binary-name != ''
+      uses: https://code.forgejo.org/actions/forgejo-release@v1
+      with:
+        direction: upload
+        release-dir: release
+        release-notes: "${{ inputs.release-notes }}"
+        token: ${{ inputs.token }}
+        verbose: ${{ steps.verbose.outputs.value }}
diff --git a/.forgejo/actions/publish-release/action.yml b/.forgejo/actions/publish-release/action.yml
new file mode 100644
index 0000000000..bafa473000
--- /dev/null
+++ b/.forgejo/actions/publish-release/action.yml
@@ -0,0 +1,93 @@
+name: 'Publish release'
+author: 'Forgejo authors'
+description: |
+  Publish release
+
+inputs:
+  forgejo:
+    description: 'URL of the Forgejo instance where the release is uploaded'
+    required: true
+  from-owner:
+    description: 'the owner from which a release is to be copied'
+    required: true
+  to-owner:
+    description: 'the owner to which a release is to be copied'
+    required: true
+  repo:
+    description: 'the repository from which a release is to be copied relative to from-owner and to-owner'
+    default: 'forgejo'
+  ref-name:
+    description: 'ref_name of the tag of the release to be copied'
+    required: true
+  doer:
+    description: 'Name of the user authoring the release'
+    required: true
+  token:
+    description: 'application token on FORGEJO with permission to the repository and the packages'
+    required: true
+  gpg-private-key:
+    description: 'GPG Private Key to sign the release artifacts'
+  gpg-passphrase:
+    description: 'Passphrase of the GPG Private Key'
+
+runs:
+  using: "composite"
+  steps:
+    - id: hostport
+      run: |
+         url="${{ inputs.forgejo }}"
+         hostport=${url##http*://}
+         hostport=${hostport%%/}
+         echo "value=$hostport" >> "$GITHUB_OUTPUT"    
+
+    - id: tag-version
+      run: |
+        version="${{ inputs.ref-name }}"
+        version=${version##*v}
+        echo "value=$version" >> "$GITHUB_OUTPUT"
+
+    - name: apt-get install docker.io
+      run: |
+        DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -qq -y docker.io
+
+    - name: download release
+      uses: https://code.forgejo.org/actions/forgejo-release@v1
+      with:
+        url: ${{ inputs.forgejo }}
+        repo: ${{ inputs.from-owner }}/${{ inputs.repo }}
+        direction: download
+        release-dir: release
+        download-retry: 60
+        token: ${{ inputs.token }}
+
+    - name: upload release
+      uses: https://code.forgejo.org/actions/forgejo-release@v1
+      with:
+        url: ${{ inputs.forgejo }}
+        repo: ${{ inputs.to-owner }}/${{ inputs.repo }}
+        direction: upload
+        release-dir: release
+        release-notes: "See https://codeberg.org/forgejo/forgejo/src/branch/forgejo/RELEASE-NOTES.md#${{ steps.tag-version.outputs.value }}"
+        token: ${{ inputs.token }}
+        gpg-private-key: ${{ inputs.gpg-private-key }}
+        gpg-passphrase: ${{ inputs.gpg-passphrase }}
+
+    - name: login to the registry
+      uses: https://github.com/docker/login-action@v2
+      with:
+          registry: ${{ steps.hostport.outputs.value }}
+          username: ${{ inputs.doer }}
+          password: ${{ inputs.token }}
+
+    - uses: https://code.forgejo.org/forgejo/forgejo-container-image@v1
+      env:
+        VERIFY: 'false'
+      with:
+        url: https://${{ steps.hostport.outputs.value }}
+        destination-owner: ${{ inputs.to-owner }}
+        owner: ${{ inputs.from-owner }}
+        suffixes: '-rootless'
+        project: ${{ inputs.repo }}
+        tag: ${{ steps.tag-version.outputs.value }}
+        doer: ${{ inputs.doer }}
+        token: ${{ inputs.token }}
diff --git a/.forgejo/testdata/build-release/Dockerfile b/.forgejo/testdata/build-release/Dockerfile
new file mode 100644
index 0000000000..4b6933845c
--- /dev/null
+++ b/.forgejo/testdata/build-release/Dockerfile
@@ -0,0 +1,3 @@
+FROM public.ecr.aws/docker/library/alpine:3.18
+RUN mkdir -p /app/gitea
+RUN ( echo '#!/bin/sh' ; echo "echo forgejo v1.2.3" ) > /app/gitea/gitea ; chmod +x /app/gitea/gitea
diff --git a/.forgejo/testdata/build-release/Makefile b/.forgejo/testdata/build-release/Makefile
new file mode 100644
index 0000000000..406acd06d2
--- /dev/null
+++ b/.forgejo/testdata/build-release/Makefile
@@ -0,0 +1,5 @@
+VERSION ?= $(shell cat VERSION 2>/dev/null)
+sources-tarbal:
+	mkdir -p dist/release
+	echo $(VERSION) > VERSION
+	sources=forgejo-src-$(VERSION).tar.gz ; tar --transform 's|^./|forgejo-src-$(VERSION)/|' -czf dist/release/forgejo-src-$(VERSION).tar.gz . ; cd dist/release ; shasum -a 256 $$sources > $$sources.sha256
diff --git a/.forgejo/workflows/build-release-integration.yml b/.forgejo/workflows/build-release-integration.yml
new file mode 100644
index 0000000000..d893bd39af
--- /dev/null
+++ b/.forgejo/workflows/build-release-integration.yml
@@ -0,0 +1,99 @@
+name: Integration tests for the release process
+
+on:
+  push:
+    paths:
+      - Makefile
+      - Dockerfile
+      - Dockerfile.rootless
+      - docker/**
+      - .forgejo/actions/build-release/action.yml
+      - .forgejo/workflows/build-release.yml
+      - .forgejo/workflows/build-release-integration.yml
+
+jobs:
+  release-simulation:
+    runs-on: self-hosted
+    if: secrets.ROLE != 'forgejo-integration' && secrets.ROLE != 'forgejo-experimental' && secrets.ROLE != 'forgejo-release'
+    steps:
+      - uses: actions/checkout@v3
+
+      - id: forgejo
+        uses: https://code.forgejo.org/actions/setup-forgejo@v1
+        with:
+          user: root
+          password: admin1234
+          image-version: 1.19
+          lxc-ip-prefix: 10.0.9
+
+      - name: publish the forgejo release
+        run: |
+          set -x
+
+          version=1.2.3
+          cat > /etc/docker/daemon.json <<EOF
+            {
+              "insecure-registries" : ["${{ steps.forgejo.outputs.host-port }}"]
+            }
+          EOF
+          systemctl restart docker
+
+          apt-get install -qq -y xz-utils
+
+          dir=$(mktemp -d)
+          trap "rm -fr $dir" EXIT
+
+          url=http://root:admin1234@${{ steps.forgejo.outputs.host-port }}
+          export FORGEJO_RUNNER_LOGS="${{ steps.forgejo.outputs.runner-logs }}"
+
+          #
+          # Create a new project with a fake forgejo and the release workflow only
+          #
+          cp -a .forgejo/testdata/build-release/* $dir
+          mkdir -p $dir/.forgejo/workflows
+          cp .forgejo/workflows/build-release.yml $dir/.forgejo/workflows
+          cp -a .forgejo/actions $dir/.forgejo/actions
+          cp $dir/Dockerfile $dir/Dockerfile.rootless
+
+          forgejo-test-helper.sh push $dir $url root forgejo |& tee $dir/pushed
+          eval $(grep '^sha=' < $dir/pushed)
+
+          #
+          # Push a tag to trigger the release workflow and wait for it to complete
+          #
+          forgejo-test-helper.sh api POST $url repos/root/forgejo/tags ${{ steps.forgejo.outputs.token }} --data-raw '{"tag_name": "v'$version'", "target": "'$sha'"}'
+          LOOPS=180 forgejo-test-helper.sh wait_success "$url" root/forgejo $sha
+
+          #
+          # uncomment to see the logs even when everything is reported to be working ok
+          #
+          #cat $FORGEJO_RUNNER_LOGS
+
+          #
+          # Minimal sanity checks. e2e test is for the setup-forgejo
+          # action and the infrastructure playbook. Since the binary
+          # is a script shell it does not test the sanity of the cross
+          # build, only the sanity of the naming of the binaries.
+          #
+          for arch in amd64 arm64 arm-6 ; do
+            binary=forgejo-$version-linux-$arch
+            for suffix in '' '.xz' ; do
+              curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$binary$suffix > $binary$suffix
+              if test "$suffix" = .xz ; then
+                 unxz --keep $binary$suffix
+              fi
+              chmod +x $binary
+              ./$binary --version | grep $version
+              curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$binary$suffix.sha256 > $binary$suffix.sha256
+              shasum -a 256 --check $binary$suffix.sha256
+              rm $binary$suffix
+            done
+          done
+
+          sources=forgejo-src-$version.tar.gz
+          curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$sources > $sources
+          curl --fail -L -sS $url/root/forgejo/releases/download/v$version/$sources.sha256 > $sources.sha256
+          shasum -a 256 --check $sources.sha256
+
+          docker pull ${{ steps.forgejo.outputs.host-port }}/root/forgejo:$version
+          docker pull ${{ steps.forgejo.outputs.host-port }}/root/forgejo:$version-rootless
diff --git a/.forgejo/workflows/build-release.yml b/.forgejo/workflows/build-release.yml
new file mode 100644
index 0000000000..80be728a8f
--- /dev/null
+++ b/.forgejo/workflows/build-release.yml
@@ -0,0 +1,155 @@
+name: Build release
+
+on:
+  push:
+    tags: 'v*'
+
+jobs:
+  release:
+    runs-on: self-hosted
+    # root is used for testing, allow it
+    if: secrets.ROLE == 'forgejo-integration' || github.repository_owner == 'root'
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Increase the verbosity when there are no secrets
+        id: verbose
+        run: |
+          if test -z "${{ secrets.TOKEN }}"; then
+            value=true
+          else
+            value=false
+          fi
+          echo "value=$value" >> "$GITHUB_OUTPUT"
+
+      - name: Sanitize the name of the repository
+        id: repository
+        run: |
+          set -x # comment out
+          repository="${{ github.repository }}"
+          echo "value=${repository##*/}" >> "$GITHUB_OUTPUT"
+
+      - name: When in a test environment, create a token
+        id: token
+        if: ${{ secrets.TOKEN == '' }}
+        run: |
+          apt-get -qq install -y jq
+          url="${{ env.GITHUB_SERVER_URL }}"
+          hostport=${url##http*://}
+          hostport=${hostport%%/}
+          doer=root
+          api=http://$doer:admin1234@$hostport/api/v1/users/$doer/tokens
+          curl -sS -X DELETE $api/release
+          token=$(curl -sS -X POST -H 'Content-Type: application/json' --data-raw '{"name": "release", "scopes": ["all"]}' $api | jq --raw-output .sha1)
+          echo "value=${token}" >> "$GITHUB_OUTPUT"
+
+      - uses: https://code.forgejo.org/actions/setup-go@v4
+        with:
+          go-version: ">=1.20"
+          check-latest: true
+
+      - name: Create the version from ref_name
+        id: tag-version
+        run: |
+          version="${{ github.ref_name }}"
+          version=${version##*v}
+          echo "value=$version" >> "$GITHUB_OUTPUT"
+
+      - name: Create the release notes
+        id: release-notes
+        run: |
+          cat >> "$GITHUB_OUTPUT" <<EOF
+          value<<ENDVAR
+          See https://codeberg.org/forgejo/forgejo/src/branch/forgejo/RELEASE-NOTES.md#${{ steps.tag-version.outputs.value }}
+          ENDVAR
+          EOF
+
+      - name: Build sources
+        run: |
+          set -x
+          apt-get -qq install -y make
+          version=${{ steps.tag-version.outputs.value }}
+          make VERSION=$version sources-tarbal
+          mv dist/release release
+
+          #
+          # Sanity check to verify that the source tarbal knows the
+          # version and is able to rebuild itself from it.
+          #
+          # When in sources the version is determined with git.
+          # When in the tarbal the version is determined from a VERSION file.
+          #
+          (
+            tmp=$(mktemp -d)
+            tar --directory $tmp -zxvf release/*$version*.tar.gz
+            cd $tmp/*
+            make sources-tarbal
+            tarbal=$(echo dist/release/*$version*.tar.gz)
+            if ! test -f $tarbal ; then
+              echo $tarbal does not exist
+              find dist release
+              exit 1
+            fi
+          )
+
+      - name: build container & release (when TOKEN secret is not set)
+        if: ${{ secrets.TOKEN == '' }}
+        uses: ./.forgejo/actions/build-release
+        with:
+          forgejo: "${{ env.GITHUB_SERVER_URL }}"
+          owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
+          repository: "${{ steps.repository.outputs.value }}"
+          doer: root
+          tag-version: "${{ steps.tag-version.outputs.value }}"
+          token: ${{ steps.token.outputs.value }}
+          platforms: linux/amd64,linux/arm64,linux/arm/v6
+          release-notes: "${{ steps.release-notes.outputs.value }}"
+          binary-name: forgejo
+          binary-path: /app/gitea/gitea
+          verbose: ${{ steps.verbose.outputs.value }}
+
+      - name: build rootless container (when TOKEN secret is not set)
+        if: ${{ secrets.TOKEN == '' }}
+        uses: ./.forgejo/actions/build-release
+        with:
+          forgejo: "${{ env.GITHUB_SERVER_URL }}"
+          owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
+          repository: "${{ steps.repository.outputs.value }}"
+          doer: root
+          tag-version: "${{ steps.tag-version.outputs.value }}"
+          token: ${{ steps.token.outputs.value }}
+          platforms: linux/amd64,linux/arm64,linux/arm/v6
+          suffix: -rootless
+          dockerfile: Dockerfile.rootless
+          verbose: ${{ steps.verbose.outputs.value }}
+
+      - name: build container & release (when TOKEN secret is set)
+        if: ${{ secrets.TOKEN != '' }}
+        uses: ./.forgejo/actions/build-release
+        with:
+          forgejo: "${{ env.GITHUB_SERVER_URL }}"
+          owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
+          repository: "${{ steps.repository.outputs.value }}"
+          doer: "${{ secrets.DOER }}"
+          tag-version: "${{ steps.tag-version.outputs.value }}"
+          token: "${{ secrets.TOKEN }}"
+          platforms: linux/amd64,linux/arm64,linux/arm/v6
+          release-notes: "${{ steps.release-notes.outputs.value }}"
+          binary-name: forgejo
+          binary-path: /app/gitea/gitea
+          verbose: ${{ steps.verbose.outputs.value }}
+
+      - name: build rootless container (when TOKEN secret is set)
+        if: ${{ secrets.TOKEN != '' }}
+        uses: ./.forgejo/actions/build-release
+        with:
+          forgejo: "${{ env.GITHUB_SERVER_URL }}"
+          owner: "${{ env.GITHUB_REPOSITORY_OWNER }}"
+          repository: "${{ steps.repository.outputs.value }}"
+          doer: "${{ secrets.DOER }}"
+          tag-version: "${{ steps.tag-version.outputs.value }}"
+          token: "${{ secrets.TOKEN }}"
+          platforms: linux/amd64,linux/arm64,linux/arm/v6
+          suffix: -rootless
+          dockerfile: Dockerfile.rootless
+          verbose: ${{ steps.verbose.outputs.value }}
diff --git a/.forgejo/workflows/publish-release.yml b/.forgejo/workflows/publish-release.yml
new file mode 100644
index 0000000000..d95c20a31f
--- /dev/null
+++ b/.forgejo/workflows/publish-release.yml
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: MIT
+name: Pubish release
+
+on: 
+  push:
+    tags: 'v*'
+
+jobs:
+  publish:
+    runs-on: self-hosted
+    if: secrets.DOER != '' && secrets.FORGEJO != '' && secrets.TO_OWNER != '' && secrets.FROM_OWNER != '' && secrets.TOKEN != ''
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: copy & sign binaries and container images from one owner to another
+        uses: ./.forgejo/actions/publish-release
+        with:
+          forgejo: ${{ secrets.FORGEJO }}
+          from-owner: ${{ secrets.FROM_OWNER }}
+          to-owner: ${{ secrets.TO_OWNER }}
+          ref-name: ${{ github.ref_name }}
+          doer: ${{ secrets.DOER }}
+          token: ${{ secrets.TOKEN }}
+          gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
+          gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }}
diff --git a/Dockerfile b/Dockerfile
index 730413ebb8..470ded4da1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,6 @@
-#Build stage
-FROM docker.io/library/golang:1.20-alpine3.18 AS build-env
+FROM --platform=$BUILDPLATFORM tonistiigi/xx AS xx
+
+FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.20-alpine3.18 as build-env
 
 ARG GOPROXY
 ENV GOPROXY ${GOPROXY:-direct}
@@ -9,19 +10,33 @@ ARG TAGS="sqlite sqlite_unlock_notify"
 ENV TAGS "bindata timetzdata $TAGS"
 ARG CGO_EXTRA_CFLAGS
 
-#Build deps
+#
+# Transparently cross compile for the target platform
+#
+COPY --from=xx / /
+ARG TARGETPLATFORM
+RUN apk --no-cache add clang lld
+RUN xx-apk --no-cache add gcc musl-dev
+ENV CGO_ENABLED=1
+RUN xx-go --wrap
+#
+# for go generate and binfmt to find
+# without it the generate phase will fail with
+# #19 25.04 modules/public/public_bindata.go:8: running "go": exit status 1
+# #19 25.39 aarch64-binfmt-P: Could not open '/lib/ld-musl-aarch64.so.1': No such file or directory
+# why exactly is it needed? where is binfmt involved?
+#
+RUN cp /*-alpine-linux-musl*/lib/ld-musl-*.so.1 /lib || true
+
 RUN apk --no-cache add build-base git nodejs npm
 
-#Setup repo
 COPY . ${GOPATH}/src/code.gitea.io/gitea
 WORKDIR ${GOPATH}/src/code.gitea.io/gitea
 
-#Checkout version if set
-RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
- && make clean-all build
-
-# Begin env-to-ini build
-RUN go build contrib/environment-to-ini/environment-to-ini.go
+RUN make clean-all
+RUN make frontend
+RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini
+RUN make go-check generate-backend static-executable && xx-verify gitea
 
 FROM docker.io/library/alpine:3.18
 LABEL maintainer="contact@forgejo.org"
diff --git a/Dockerfile.rootless b/Dockerfile.rootless
index 39c13fbb6d..78ee81b4f4 100644
--- a/Dockerfile.rootless
+++ b/Dockerfile.rootless
@@ -1,5 +1,6 @@
-#Build stage
-FROM docker.io/library/golang:1.20-alpine3.18 AS build-env
+FROM --platform=$BUILDPLATFORM tonistiigi/xx AS xx
+
+FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.20-alpine3.18 as build-env
 
 ARG GOPROXY
 ENV GOPROXY ${GOPROXY:-direct}
@@ -9,19 +10,33 @@ ARG TAGS="sqlite sqlite_unlock_notify"
 ENV TAGS "bindata timetzdata $TAGS"
 ARG CGO_EXTRA_CFLAGS
 
-#Build deps
+#
+# Transparently cross compile for the target platform
+#
+COPY --from=xx / /
+ARG TARGETPLATFORM
+RUN apk --no-cache add clang lld
+RUN xx-apk --no-cache add gcc musl-dev
+ENV CGO_ENABLED=1
+RUN xx-go --wrap
+#
+# for go generate and binfmt to find
+# without it the generate phase will fail with
+# #19 25.04 modules/public/public_bindata.go:8: running "go": exit status 1
+# #19 25.39 aarch64-binfmt-P: Could not open '/lib/ld-musl-aarch64.so.1': No such file or directory
+# why exactly is it needed? where is binfmt involved?
+#
+RUN cp /*-alpine-linux-musl*/lib/ld-musl-*.so.1 /lib || true
+
 RUN apk --no-cache add build-base git nodejs npm
 
-#Setup repo
 COPY . ${GOPATH}/src/code.gitea.io/gitea
 WORKDIR ${GOPATH}/src/code.gitea.io/gitea
 
-#Checkout version if set
-RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
- && make clean-all build
-
-# Begin env-to-ini build
-RUN go build contrib/environment-to-ini/environment-to-ini.go
+RUN make clean-all
+RUN make frontend
+RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini
+RUN make go-check generate-backend static-executable && xx-verify gitea
 
 FROM docker.io/library/alpine:3.18
 LABEL maintainer="contact@forgejo.org"
diff --git a/Makefile b/Makefile
index ecd20af24b..30b87ddb3f 100644
--- a/Makefile
+++ b/Makefile
@@ -79,31 +79,14 @@ endif
 STORED_VERSION_FILE := VERSION
 HUGO_VERSION ?= 0.111.3
 
-GITHUB_REF_TYPE ?= branch
-GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
 
-ifneq ($(GITHUB_REF_TYPE),branch)
-	VERSION ?= $(subst v,,$(GITHUB_REF_NAME))
-	GITEA_VERSION ?= $(VERSION)
+STORED_VERSION=$(shell cat $(STORED_VERSION_FILE) 2>/dev/null)
+ifneq ($(STORED_VERSION),)
+  GITEA_VERSION ?= $(STORED_VERSION)
 else
-	ifneq ($(GITHUB_REF_NAME),)
-		VERSION ?= $(subst release/v,,$(GITHUB_REF_NAME))
-	else
-		VERSION ?= main
-	endif
-
-	STORED_VERSION=$(shell cat $(STORED_VERSION_FILE) 2>/dev/null)
-	ifneq ($(STORED_VERSION),)
-		GITEA_VERSION ?= $(STORED_VERSION)
-	else
-		GITEA_VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
-	endif
-endif
-
-# if version = "main" then update version to "nightly"
-ifeq ($(VERSION),main)
-	VERSION := main-nightly
+  GITEA_VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
 endif
+VERSION = ${GITEA_VERSION}
 
 LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
 
@@ -829,9 +812,14 @@ security-check:
 $(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
 	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
 
+static-executable: $(GO_SOURCES) $(TAGS_PREREQ)
+	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags 'netgo osusergo $(TAGS)' -ldflags '-s -w -linkmode external -extldflags "-static" $(LDFLAGS)' -o $(EXECUTABLE)
+
 .PHONY: release
 release: frontend generate release-linux release-copy release-compress vendor release-sources release-check
 
+sources-tarbal: vendor release-sources release-check
+
 $(DIST_DIRS):
 	mkdir -p $(DIST_DIRS)