diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml
index d208752..bfcac21 100644
--- a/.forgejo/workflows/build.yaml
+++ b/.forgejo/workflows/build.yaml
@@ -24,10 +24,6 @@ jobs:
       uses: actions/setup-go@v5
       with:
         go-version: ${{ steps.go-version.outputs.version }}
-    -
-      name: Build for host architecture
-      run: |
-        make build
     -
       name: Run tests
       run: |
diff --git a/.forgejo/workflows/release.yaml b/.forgejo/workflows/release.yaml
new file mode 100644
index 0000000..9283df6
--- /dev/null
+++ b/.forgejo/workflows/release.yaml
@@ -0,0 +1,41 @@
+name: Build and upload project release
+
+on:
+  create:
+    tags: [ v* ]
+
+jobs:
+
+  release:
+    runs-on: debian-12
+    if: startsWith(github.ref, 'refs/tags/v')
+    steps:
+    -
+      name: Checkout repo
+      uses: actions/checkout@v4
+    -
+      name: Extract Go version
+      id: go-version
+      run: |
+        sed '/^go /!d; s/go /version=/' go.mod >>"$GITHUB_OUTPUT"
+    -
+      name: Setup Go
+      uses: actions/setup-go@v5
+      with:
+        go-version: ${{ steps.go-version.outputs.version }}
+    -
+      name: Run tests
+      run: |
+        make test
+    -
+      name: Build binaries
+      run: |
+        make dist-all
+    -
+      name: Upload release
+      uses: actions/forgejo-release@v1
+      with:
+        direction: upload
+        name: binaries
+        release-dir: dist/release
+        token: ${{ secrets.FORGEJO_TOKEN }}
diff --git a/Makefile b/Makefile
index a26d1af..f79fb35 100644
--- a/Makefile
+++ b/Makefile
@@ -71,13 +71,16 @@ dist: dist-target
 
 .PHONY: dist-all
 dist-all: dist-source
-	@for target in $(TARGETS); do \
+	@set -eu -o pipefail; \
+	for target in $(TARGETS); do \
 		$(MAKE) dist-target GOOS=`echo $$target | cut -d / -f 1` GOARCH=`echo $$target | cut -d / -f 2`; \
-	done
+	done; \
+	mkdir -p $(DISTDIR)/release; \
+	mv $(DISTDIR)/$(BINNAME)-$(BINARY_VERSION)-*.tar.xz $(DISTDIR)/release
 
 .PHONY: dist-source
 dist-source:
-	@set -e; \
+	@set -eu -o pipefail; \
 	if [ -z "$(BINARY_VERSION)" ]; then \
 		echo "No version set"; \
 		exit 1; \
@@ -98,7 +101,7 @@ dist-source:
 
 .PHONY: dist-target
 dist-target: build-target
-	@set -e; \
+	@set -eu -o pipefail; \
 	if [ -z "$(BINARY_VERSION)" ]; then \
 		echo "No version set"; \
 		exit 1; \