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/testdata/build-release/modules/public/bindata.go b/.forgejo/testdata/build-release/modules/public/bindata.go
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/.forgejo/testdata/build-release/public/css/placeholder b/.forgejo/testdata/build-release/public/css/placeholder
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/.forgejo/testdata/build-release/public/fonts/placeholder b/.forgejo/testdata/build-release/public/fonts/placeholder
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/.forgejo/testdata/build-release/public/js/placeholder b/.forgejo/testdata/build-release/public/js/placeholder
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/.forgejo/workflows/build-release-integration.yml b/.forgejo/workflows/build-release-integration.yml
index 476ce93282..d893bd39af 100644
--- a/.forgejo/workflows/build-release-integration.yml
+++ b/.forgejo/workflows/build-release-integration.yml
@@ -49,17 +49,11 @@ jobs:
           #
           # 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
-          cat > $dir/Dockerfile <<EOF
-          FROM docker.io/library/alpine:3.18
-          RUN mkdir -p /app/gitea
-          RUN ( echo '#!/bin/sh' ; echo "echo forgejo v$version" ) > /app/gitea/gitea ; chmod +x /app/gitea/gitea
-          EOF
           cp $dir/Dockerfile $dir/Dockerfile.rootless
-          sources=forgejo-src-$version.tar.gz
-          ( echo 'sources-tarbal:' ; echo -e '\tmkdir -p dist/release ; cd dist/release ; sources=forgejo-src-$(VERSION).tar.gz ; echo sources > $$sources ; shasum -a 256 $$sources > $$sources.sha256' ) > $dir/Makefile
 
           forgejo-test-helper.sh push $dir $url root forgejo |& tee $dir/pushed
           eval $(grep '^sha=' < $dir/pushed)
@@ -96,6 +90,7 @@ jobs:
             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
diff --git a/.forgejo/workflows/build-release.yml b/.forgejo/workflows/build-release.yml
index ffae76f23c..f84aca6d28 100644
--- a/.forgejo/workflows/build-release.yml
+++ b/.forgejo/workflows/build-release.yml
@@ -66,10 +66,62 @@ jobs:
 
       - name: Build sources
         run: |
+          set -x
           apt-get -qq install -y make
-          make VERSION=${{ steps.tag-version.outputs.value }} sources-tarbal
+          version=${{ steps.tag-version.outputs.value }}
+          #
+          # Make sure all files are owned by the current user.
+          # When run as root `npx webpack` will assume the identity
+          # of the owner of the current working directory and may
+          # fail to create files if some sub-directories are not owned
+          # by the same user.
+          #
+          #   Binaries:
+          #   Node: 18.17.0 - /usr/local/node-v18.17.0-linux-x64/bin/node
+          #   npm: 9.6.7 - /usr/local/node-v18.17.0-linux-x64/bin/npm
+          # Packages:
+          #   add-asset-webpack-plugin: 2.0.1 => 2.0.1
+          #   css-loader: 6.8.1 => 6.8.1
+          #   esbuild-loader: 3.0.1 => 3.0.1
+          #   license-checker-webpack-plugin: 0.2.1 => 0.2.1
+          #   monaco-editor-webpack-plugin: 7.0.1 => 7.0.1
+          #   vue-loader: 17.2.2 => 17.2.2
+          #   webpack: 5.87.0 => 5.87.0
+          #   webpack-cli: 5.1.4 => 5.1.4
+          #
+          chown -R $(id -u) .
+          make VERSION=$version TAGS=bindata sources-tarbal
           mv dist/release release
-          find release | grep tar.gz # sanity check to fail fast
+
+          (
+            tmp=$(mktemp -d)
+            tar --directory $tmp -zxvf release/*$version*.tar.gz
+            cd $tmp/*
+            #
+            # Verify `make frontend` files are available
+            #
+            test -d public/css
+            test -d public/fonts
+            test -d public/js
+            #
+            # Verify `make generate` files are available
+            #
+            test -f modules/public/bindata.go
+            #
+            # 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.
+            #
+            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 == '' }}
diff --git a/Makefile b/Makefile
index 173b51bb7c..432e460faf 100644
--- a/Makefile
+++ b/Makefile
@@ -844,7 +844,8 @@ static-executable: $(GO_SOURCES) $(TAGS_PREREQ)
 .PHONY: release
 release: frontend generate release-linux release-copy release-compress vendor release-sources release-check
 
-sources-tarbal: vendor release-sources release-check
+# just the sources, with all assets builtin and frontend resources generated
+sources-tarbal: frontend generate vendor release-sources release-check
 
 $(DIST_DIRS):
 	mkdir -p $(DIST_DIRS)