diff --git a/.dockerignore b/.dockerignore
index 8b4f1dbc5..cae903c18 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -6,5 +6,7 @@ data
 *.db
 testDB
 navidrome
-navidrome.db
 navidrome.toml
+tmp
+!tmp/taglib
+dist
\ No newline at end of file
diff --git a/.github/actions/download-taglib/action.yml b/.github/actions/download-taglib/action.yml
new file mode 100644
index 000000000..ea6de8783
--- /dev/null
+++ b/.github/actions/download-taglib/action.yml
@@ -0,0 +1,23 @@
+name: 'Download TagLib'
+description: 'Downloads and extracts the TagLib library, adding it to PKG_CONFIG_PATH'
+inputs:
+  version:
+    description: 'Version of TagLib to download'
+    required: true
+  platform:
+    description: 'Platform to download TagLib for'
+    default: 'linux-amd64'
+runs:
+  using: 'composite'
+  steps:
+    - name: Download TagLib
+      shell: bash
+      run: |
+        mkdir -p /tmp/taglib
+        cd /tmp
+        FILE=taglib-${{ inputs.platform }}.tar.gz
+        wget https://github.com/navidrome/cross-taglib/releases/download/v${{ inputs.version }}/${FILE}
+        tar -xzf ${FILE} -C taglib
+        PKG_CONFIG_PREFIX=/tmp/taglib
+        echo "PKG_CONFIG_PREFIX=${PKG_CONFIG_PREFIX}" >> $GITHUB_ENV
+        echo "PKG_CONFIG_PATH=${PKG_CONFIG_PATH}:${PKG_CONFIG_PREFIX}/lib/pkgconfig" >> $GITHUB_ENV
diff --git a/.github/actions/prepare-docker/action.yml b/.github/actions/prepare-docker/action.yml
new file mode 100644
index 000000000..760a0528b
--- /dev/null
+++ b/.github/actions/prepare-docker/action.yml
@@ -0,0 +1,84 @@
+name: 'Prepare Docker Buildx environment'
+description: 'Downloads and extracts the TagLib library, adding it to PKG_CONFIG_PATH'
+inputs:
+  github_token:
+    description: 'GitHub token'
+    required: true
+    default: ''
+  hub_repository:
+    description: 'Docker Hub repository to push images to'
+    required: false
+    default: ''
+  hub_username:
+    description: 'Docker Hub username'
+    required: false
+    default: ''
+  hub_password:
+    description: 'Docker Hub password'
+    required: false
+    default: ''
+outputs:
+  tags:
+    description: 'Docker image tags'
+    value: ${{ steps.meta.outputs.tags }}
+  labels:
+    description: 'Docker image labels'
+    value: ${{ steps.meta.outputs.labels }}
+  annotations:
+    description: 'Docker image annotations'
+    value: ${{ steps.meta.outputs.annotations }}
+  version:
+    description: 'Docker image version'
+    value: ${{ steps.meta.outputs.version }}
+  hub_repository:
+    description: 'Docker Hub repository'
+    value: ${{ env.DOCKER_HUB_REPO }}
+  hub_enabled:
+    description: 'Is Docker Hub enabled'
+    value: ${{ env.DOCKER_HUB_ENABLED }}
+
+runs:
+  using: 'composite'
+  steps:
+    - name: Check Docker Hub configuration
+      shell: bash
+      run: |
+        if [ -z "${{inputs.hub_repository}}" ]; then
+          echo "DOCKER_HUB_REPO=none" >> $GITHUB_ENV
+          echo "DOCKER_HUB_ENABLED=false" >> $GITHUB_ENV
+        else
+          echo "DOCKER_HUB_REPO=${{inputs.hub_repository}}" >> $GITHUB_ENV
+          echo "DOCKER_HUB_ENABLED=true" >> $GITHUB_ENV
+        fi
+
+    - name: Login to Docker Hub
+      if: inputs.hub_username != '' && inputs.hub_password != ''
+      uses: docker/login-action@v3
+      with:
+        username: ${{ inputs.hub_username }}
+        password: ${{ inputs.hub_password }}
+
+    - name: Login to GitHub Container Registry
+      uses: docker/login-action@v3
+      with:
+        registry: ghcr.io
+        username: ${{ github.actor }}
+        password: ${{ inputs.github_token }}
+
+    - name: Set up Docker Buildx
+      id: buildx
+      uses: docker/setup-buildx-action@v3
+
+    - name: Extract metadata for Docker image
+      id: meta
+      uses: docker/metadata-action@v5
+      with:
+        labels: |
+          maintainer=deluan@navidrome.org
+        images: |
+          name=${{env.DOCKER_HUB_REPO}},enable=${{env.DOCKER_HUB_ENABLED}}
+          name=ghcr.io/${{ github.repository }}
+        tags: |
+          type=ref,event=pr
+          type=semver,pattern={{version}}
+          type=raw,value=develop,enable={{is_default_branch}}
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 948a94643..327ab0dfc 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -11,6 +11,11 @@ updates:
     interval: weekly
   open-pull-requests-limit: 10
 - package-ecosystem: docker
+  directory: "/"
+  schedule:
+    interval: weekly
+  open-pull-requests-limit: 10
+- package-ecosystem: github-actions
   directory: "/.github/workflows"
   schedule:
     interval: weekly
diff --git a/.github/workflows/pipeline.dockerfile b/.github/workflows/pipeline.dockerfile
deleted file mode 100644
index 649d1f234..000000000
--- a/.github/workflows/pipeline.dockerfile
+++ /dev/null
@@ -1,40 +0,0 @@
-#####################################################
-### Copy platform specific binary
-FROM bash as copy-binary
-ARG TARGETPLATFORM
-
-RUN echo "Target Platform = ${TARGETPLATFORM}"
-
-COPY dist .
-RUN if [ "$TARGETPLATFORM" = "linux/amd64" ];  then cp navidrome_linux_amd64_linux_amd64_v1/navidrome /navidrome; fi
-RUN if [ "$TARGETPLATFORM" = "linux/386" ];  then cp navidrome_linux_386_linux_386/navidrome /navidrome; fi
-RUN if [ "$TARGETPLATFORM" = "linux/arm64" ];  then cp navidrome_linux_arm64_linux_arm64/navidrome /navidrome; fi
-RUN if [ "$TARGETPLATFORM" = "linux/arm/v6" ]; then cp navidrome_linux_arm_linux_arm_6/navidrome /navidrome; fi
-RUN if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then cp navidrome_linux_arm_linux_arm_7/navidrome /navidrome; fi
-RUN chmod +x /navidrome
-
-
-#####################################################
-### Build Final Image
-FROM alpine:3.20
-LABEL maintainer="deluan@navidrome.org"
-
-# Install ffmpeg and mpv
-RUN apk add -U --no-cache ffmpeg mpv
-
-# Show ffmpeg build info, for troubleshooting purposes
-RUN ffmpeg -buildconf
-
-COPY --from=copy-binary /navidrome /app/
-
-VOLUME ["/data", "/music"]
-ENV ND_MUSICFOLDER /music
-ENV ND_DATAFOLDER /data
-ENV ND_PORT 4533
-ENV GODEBUG "asyncpreemptoff=1"
-
-EXPOSE ${ND_PORT}
-HEALTHCHECK CMD wget -O- http://localhost:${ND_PORT}/ping || exit 1
-WORKDIR /app
-
-ENTRYPOINT ["/app/navidrome"]
diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml
index 82ec0d95f..f05c12d8d 100644
--- a/.github/workflows/pipeline.yml
+++ b/.github/workflows/pipeline.yml
@@ -9,16 +9,56 @@ on:
     branches:
       - master
 
+env:
+  CROSS_TAGLIB_VERSION: "2.0.2-1"
+  IS_RELEASE: ${{ startsWith(github.ref, 'refs/tags/') && 'true' || 'false' }}
+
 jobs:
+  git-version:
+    name: Get Version Info
+    runs-on: ubuntu-latest
+    outputs:
+      git_tag: ${{ steps.git-version.outputs.GIT_TAG }}
+      git_sha: ${{ steps.git-version.outputs.GIT_SHA }}
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+          fetch-tags: true
+
+      - name: Get Git SHA and last Tag
+        id: git-version
+        run: |
+          GIT_TAG=$(git tag --sort=-committerdate | head -n 1)
+          if [ -n "$GIT_TAG" ]; then
+            GIT_TAG=${GIT_TAG}-SNAPSHOT
+            echo "GIT_TAG=$GIT_TAG" >> $GITHUB_OUTPUT
+          fi
+          GIT_SHA=$(git rev-parse --short HEAD)
+          PR_NUM=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH")
+          if [[ $PR_NUM != "null" ]]; then
+            GIT_SHA="pr-${PR_NUM}/${GIT_SHA}"
+          fi
+          echo "GIT_SHA=$GIT_SHA" >> $GITHUB_OUTPUT
+          
+          echo "GIT_TAG=$GIT_TAG"
+          echo "GIT_SHA=$GIT_SHA"
+          
+          echo "git describe (dirty): $(git describe --dirty --always --tags)"
+          echo "git describe --tags: $(git describe --tags `git rev-list --tags --max-count=1`)"
+          echo "git tag: $(git tag --sort=-committerdate | head -n 1)"
+          echo "git tag -l: $(git tag -l)"
+
   go-lint:
     name: Lint Go code
     runs-on: ubuntu-latest
-    container: deluan/ci-goreleaser:1.23.2-1
     steps:
       - uses: actions/checkout@v4
 
-      - name: Config workspace folder as trusted
-        run: git config --global --add safe.directory $GITHUB_WORKSPACE;  git describe --dirty --always --tags
+      - name: Download TagLib
+        uses: ./.github/actions/download-taglib
+        with:
+          version: ${{ env.CROSS_TAGLIB_VERSION }}
 
       - name: golangci-lint
         uses: golangci/golangci-lint-action@v6
@@ -27,10 +67,8 @@ jobs:
           problem-matchers: true
           args: --timeout 2m
 
-      - name: Install goimports
-        run: go install golang.org/x/tools/cmd/goimports@latest
-
-      - run: goimports -w `find . -name '*.go' | grep -v '_gen.go$'`
+      - name: Run go goimports
+        run: go run golang.org/x/tools/cmd/goimports@latest -w `find . -name '*.go' | grep -v '_gen.go$'`
       - run: go mod tidy
       - name: Verify no changes from goimports and go mod tidy
         run: |
@@ -39,28 +77,29 @@ jobs:
             echo 'To fix this check, run "make format" and commit the changes'
             exit 1
           fi
+
   go:
     name: Test Go code
     runs-on: ubuntu-latest
-    container: deluan/ci-goreleaser:1.23.2-1
     steps:
       - name: Check out code into the Go module directory
         uses: actions/checkout@v4
 
-      - name: Config workspace folder as trusted
-        run: git config --global --add safe.directory $GITHUB_WORKSPACE;  git describe --dirty --always --tags
+      - name: Download TagLib
+        uses: ./.github/actions/download-taglib
+        with:
+          version: ${{ env.CROSS_TAGLIB_VERSION }}
 
       - name: Download dependencies
-        if: steps.cache-go.outputs.cache-hit != 'true'
-        continue-on-error: ${{contains(matrix.go_version, 'beta') || contains(matrix.go_version, 'rc')}}
         run: go mod download
 
       - name: Test
-        continue-on-error: ${{contains(matrix.go_version, 'beta') || contains(matrix.go_version, 'rc')}}
-        run: go test -shuffle=on -race -cover ./... -v
+        run: |
+          pkg-config --define-prefix --cflags --libs taglib # for debugging
+          go test -shuffle=on -race -cover ./... -v
 
   js:
-    name: Build JS bundle
+    name: Test JS code
     runs-on: ubuntu-latest
     env:
       NODE_OPTIONS: "--max_old_space_size=4096"
@@ -92,12 +131,6 @@ jobs:
           cd ui
           npm run build
 
-      - uses: actions/upload-artifact@v4
-        with:
-          name: js-bundle
-          path: ui/build
-          retention-days: 7
-
   i18n-lint:
     name: Lint i18n files
     runs-on: ubuntu-latest
@@ -115,63 +148,180 @@ jobs:
             fi
           done
 
-  binaries:
-    name: Build binaries
-    needs: [js, go, go-lint, i18n-lint]
+  build:
+    name: Build
+    needs: [js, go, go-lint, i18n-lint, git-version]
+    strategy:
+      matrix:
+        platform: [ linux/amd64, linux/arm64, linux/arm/v5, linux/arm/v6, linux/arm/v7, linux/386, darwin/amd64, darwin/arm64, windows/amd64, windows/386 ]
     runs-on: ubuntu-latest
-    container: deluan/ci-goreleaser:1.23.2-1
+    env:
+      IS_LINUX: ${{ startsWith(matrix.platform, 'linux/') && 'true' || 'false' }}
+      IS_DOCKER_PUSH_CONFIGURED: ${{ vars.DOCKER_HUB_REPO != '' && 'true' || 'false' }}
+      DOCKER_BUILD_SUMMARY: false
+      GIT_SHA: ${{ needs.git-version.outputs.git_sha }}
+      GIT_TAG: ${{ needs.git-version.outputs.git_tag }}
     steps:
-      - name: Checkout Code
-        uses: actions/checkout@v4
+      - name: Sanitize platform name
+        id: set-platform
+        run: |
+          PLATFORM=$(echo ${{ matrix.platform }} | tr '/' '_')
+          echo "PLATFORM=$PLATFORM" >> $GITHUB_ENV
+
+      - uses: actions/checkout@v4
+
+      - name: Prepare Docker Buildx
+        uses: ./.github/actions/prepare-docker
+        id: docker
         with:
-          fetch-depth: 0
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          hub_repository: ${{ vars.DOCKER_HUB_REPO }}
+          hub_username: ${{ secrets.DOCKER_HUB_USERNAME }}
+          hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }}
 
-      - name: Config workspace folder as trusted
-        run: git config --global --add safe.directory $GITHUB_WORKSPACE;  git describe --dirty --always --tags
-
-      - uses: actions/download-artifact@v4
+      - name: Build Binaries
+        uses: docker/build-push-action@v6
         with:
-          name: js-bundle
-          path: ui/build
+          context: .
+          file: Dockerfile
+          platforms: ${{ matrix.platform }}
+          outputs: |
+            type=local,dest=./output/${{ env.PLATFORM }}
+          target: binary
+          build-args: |
+            GIT_SHA=${{ env.GIT_SHA }}
+            GIT_TAG=${{ env.GIT_TAG }}
+            CROSS_TAGLIB_VERSION=${{ env.CROSS_TAGLIB_VERSION }}
 
-      - name: Run GoReleaser - SNAPSHOT
-        if: startsWith(github.ref, 'refs/tags/') != true
-        run: goreleaser release --clean --skip=publish --snapshot
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-      - name: Run GoReleaser - RELEASE
-        if: startsWith(github.ref, 'refs/tags/')
-        run: goreleaser release --clean
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-      - uses: actions/upload-artifact@v4
+      - name: Upload Binaries
+        uses: actions/upload-artifact@v4
         with:
-          name: binaries
-          path: |
-            dist
-            !dist/*.tar.gz
-            !dist/*.zip
+          name: navidrome-${{ env.PLATFORM }}
+          path: ./output
           retention-days: 7
 
+      - name: Build and push image by digest
+        id: push-image
+        if: env.IS_LINUX == 'true' && env.IS_DOCKER_PUSH_CONFIGURED == 'true'
+        uses: docker/build-push-action@v6
+        with:
+          context: .
+          file: Dockerfile
+          platforms: ${{ matrix.platform }}
+          labels: ${{ steps.docker.outputs.labels }}
+          build-args: |
+            GIT_SHA=${{ env.GIT_SHA }}
+            GIT_TAG=${{ env.GIT_TAG }}
+            CROSS_TAGLIB_VERSION=${{ env.CROSS_TAGLIB_VERSION }}
+          outputs: |
+            type=image,name=${{ steps.docker.outputs.hub_repository }},push-by-digest=true,name-canonical=true,push=${{ steps.docker.outputs.hub_enabled }}
+            type=image,name=ghcr.io/${{ github.repository }},push-by-digest=true,name-canonical=true,push=true
+
+      - name: Export digest
+        if: env.IS_LINUX == 'true' && env.IS_DOCKER_PUSH_CONFIGURED == 'true'
+        run: |
+          mkdir -p /tmp/digests
+          digest="${{ steps.push-image.outputs.digest }}"
+          touch "/tmp/digests/${digest#sha256:}"          
+
+      - name: Upload digest
+        uses: actions/upload-artifact@v4
+        if: env.IS_LINUX == 'true' && env.IS_DOCKER_PUSH_CONFIGURED == 'true'
+        with:
+          name: digests-${{ env.PLATFORM }}
+          path: /tmp/digests/*
+          if-no-files-found: error
+          retention-days: 1
+
+  check-push-enabled:
+    name: Check Docker push
+    runs-on: ubuntu-latest
+    outputs:
+      is_enabled: ${{ steps.check.outputs.is_enabled }}
+    steps:
+      - name: Check if Docker push is configured
+        id: check
+        run: echo "is_enabled=${{ vars.DOCKER_HUB_REPO != '' }}" >> $GITHUB_OUTPUT
+
+  push-manifest:
+    name: Push Docker manifest
+    runs-on: ubuntu-latest
+    needs: [build, check-push-enabled]
+    if: needs.check-push-enabled.outputs.is_enabled == 'true'
+    env:
+      REGISTRY_IMAGE: ghcr.io/${{ github.repository }}
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Download digests
+        uses: actions/download-artifact@v4
+        with:
+          path: /tmp/digests
+          pattern: digests-*
+          merge-multiple: true
+
+      - name: Prepare Docker Buildx
+        uses: ./.github/actions/prepare-docker
+        id: docker
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          hub_repository: ${{ vars.DOCKER_HUB_REPO }}
+          hub_username: ${{ secrets.DOCKER_HUB_USERNAME }}
+          hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }}
+
+      - name: Create manifest list and push to ghcr.io
+        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: Create manifest list and push to Docker Hub
+        working-directory: /tmp/digests
+        if: vars.DOCKER_HUB_REPO != ''
+        run: |
+          docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
+            $(printf '${{ vars.DOCKER_HUB_USERNAME }}@sha256:%s ' *)          
+
+      - name: Inspect image in ghcr.io
+        run: |
+          docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.docker.outputs.version }}
+
+      - name: Inspect image in Docker Hub
+        if: vars.DOCKER_HUB_REPO != ''
+        run: |
+          docker buildx imagetools inspect ${{ vars.DOCKER_HUB_REPO }}:${{ steps.docker.outputs.version }}
+
+      - name: Delete unnecessary digest artifacts
+        env:
+          GH_TOKEN: ${{ github.token }}
+        run: |
+          for artifact in $(gh api repos/${{ github.repository }}/actions/artifacts | jq -r '.artifacts[] | select(.name | startswith("digests-")) | .id'); do
+            gh api --method DELETE repos/${{ github.repository }}/actions/artifacts/$artifact
+          done
+
+
   msi:
-    name: Build and publish MSI files
-    needs: [binaries]
+    name: Build Windows Installers
+    needs: [build, git-version]
     runs-on: ubuntu-24.04
+    env:
+      GIT_SHA: ${{ needs.git-version.outputs.git_sha }}
+      GIT_TAG: ${{ needs.git-version.outputs.git_tag }}
     steps:
       - uses: actions/checkout@v4
 
       - uses: actions/download-artifact@v4
         with:
-          name: binaries
-          path: dist
+          path: ./binaries
+          pattern: navidrome-windows*
+          merge-multiple: true
 
-      - name: Build MSI
+      - name: Build MSI files
         run: |
           sudo apt-get install -y wixl jq
-          
-          NAVIDROME_BUILD_VERSION=$(jq -r '.version' < $GITHUB_WORKSPACE/dist/metadata.json | sed -e 's/^v//' -e 's/-SNAPSHOT/.1/' )
+
+          NAVIDROME_BUILD_VERSION=$(echo $GIT_TAG | sed -e 's/^v//' -e 's/-SNAPSHOT/.1/')
+          echo $NAVIDROME_BUILD_VERSION
 
           mkdir -p $GITHUB_WORKSPACE/wix/386
           cp $GITHUB_WORKSPACE/LICENSE $GITHUB_WORKSPACE/wix/386
@@ -179,8 +329,8 @@ jobs:
 
           cp -r $GITHUB_WORKSPACE/wix/386 $GITHUB_WORKSPACE/wix/amd64
 
-          cp $GITHUB_WORKSPACE/dist/navidrome_windows_386_windows_386/navidrome.exe $GITHUB_WORKSPACE/wix/386
-          cp $GITHUB_WORKSPACE/dist/navidrome_windows_amd64_windows_amd64_v1/navidrome.exe $GITHUB_WORKSPACE/wix/amd64
+          cp $GITHUB_WORKSPACE/binaries/windows_386/navidrome.exe $GITHUB_WORKSPACE/wix/386
+          cp $GITHUB_WORKSPACE/binaries/windows_amd64/navidrome.exe $GITHUB_WORKSPACE/wix/amd64
 
           # workaround for wixl WixVariable not working to override bmp locations
           sudo cp $GITHUB_WORKSPACE/wix/bmp/banner.bmp /usr/share/wixl-*/ext/ui/bitmaps/bannrbmp.bmp
@@ -191,77 +341,39 @@ jobs:
 
           cd $GITHUB_WORKSPACE/wix/amd64
           wixl ../navidrome.wxs -D Version=$NAVIDROME_BUILD_VERSION -D Platform=x64 --arch x64 --ext ui --output ../navidrome_amd64.msi
-          
+
           ls -la $GITHUB_WORKSPACE/wix/*.msi
 
-      - uses: actions/upload-artifact@v4
+      - name: Upload MSI files
+        uses: actions/upload-artifact@v4
         with:
-          name: installers
+          name: navidrome-windows-installers
           path: wix/*.msi
           retention-days: 7
 
-  docker:
-    name: Build and publish Docker images
-    needs: [binaries]
+  release:
+    name: Release
+    needs: [build, msi, push-manifest]
     runs-on: ubuntu-latest
-    env:
-      DOCKER_IMAGE: ${{secrets.DOCKER_IMAGE}}
     steps:
-      - name: Set up QEMU
-        id: qemu
-        uses: docker/setup-qemu-action@v3
-        if: env.DOCKER_IMAGE != ''
-
-      - name: Set up Docker Buildx
-        id: buildx
-        uses: docker/setup-buildx-action@v3
-        if: env.DOCKER_IMAGE != ''
-
       - uses: actions/checkout@v4
-        if: env.DOCKER_IMAGE != ''
 
       - uses: actions/download-artifact@v4
-        if: env.DOCKER_IMAGE != ''
         with:
-          name: binaries
-          path: dist
+          path: ./binaries
+          pattern: navidrome-*
+          merge-multiple: true
 
-      - name: Login to Docker Hub
-        if: env.DOCKER_IMAGE != ''
-        uses: docker/login-action@v3
-        with:
-          username: ${{ secrets.DOCKER_USERNAME }}
-          password: ${{ secrets.DOCKER_PASSWORD }}
+      - run: ls -lR ./binaries
 
-      - name: Login to GitHub Container Registry
-        if: env.DOCKER_IMAGE != ''
-        uses: docker/login-action@v3
-        with:
-          registry: ghcr.io
-          username: ${{ github.actor }}
-          password: ${{ secrets.GITHUB_TOKEN }}
+      - name: Set RELEASE_FLAGS for snapshot releases
+        if: env.IS_RELEASE == 'false'
+        run: echo 'RELEASE_FLAGS=--skip=publish --snapshot' >> $GITHUB_ENV
 
-      - name: Extract metadata for Docker
-        if: env.DOCKER_IMAGE != ''
-        id: meta
-        uses: docker/metadata-action@v5
+      - name: Run GoReleaser
+        uses: goreleaser/goreleaser-action@v6
         with:
-          labels: |
-            maintainer=deluan
-          images: |
-            name=${{secrets.DOCKER_IMAGE}}
-            name=ghcr.io/${{ github.repository }}
-          tags: |
-            type=ref,event=pr
-            type=semver,pattern={{version}}
-            type=raw,value=develop,enable={{is_default_branch}}
-
-      - name: Build and Push
-        if: env.DOCKER_IMAGE != ''
-        uses: docker/build-push-action@v5
-        with:
-          context: .
-          file: .github/workflows/pipeline.dockerfile
-          platforms: linux/amd64,linux/386,linux/arm/v6,linux/arm/v7,linux/arm64
-          push: true
-          tags: ${{ steps.meta.outputs.tags }}
+          version: '~> v2'
+          args: "release --clean -f release/goreleaser.yml ${{ env.RELEASE_FLAGS }}"
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 7b97fcb2f..37999c3f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,16 +13,13 @@ var
 navidrome.toml
 master.zip
 testDB
-navidrome.db
 cache/*
 *.swp
-embedded_gen.go
 dist
 music
-navidrome.db-shm
-navidrome.db-wal
-tags
+*.db*
 .gitinfo
 docker-compose.yml
 !contrib/docker-compose.yml
-test-123.db
+binaries
+taglib
\ No newline at end of file
diff --git a/.goreleaser.yml b/.goreleaser.yml
deleted file mode 100644
index 000671390..000000000
--- a/.goreleaser.yml
+++ /dev/null
@@ -1,172 +0,0 @@
-# GoReleaser config
-project_name: navidrome
-version: 2
-
-builds:
-  - id: navidrome_linux_amd64
-    env:
-      - CGO_ENABLED=1
-    goos:
-      - linux
-    goarch:
-      - amd64
-    flags:
-      - -tags=netgo
-    ldflags:
-      - "-extldflags '-static -lz'"
-      - -s -w -X github.com/navidrome/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/navidrome/navidrome/consts.gitTag={{.Version}}
-
-  - id: navidrome_linux_386
-    env:
-      - CGO_ENABLED=1
-      - PKG_CONFIG_PATH=/i386/lib/pkgconfig
-    goos:
-      - linux
-    goarch:
-      - "386"
-    flags:
-      - -tags=netgo
-    ldflags:
-      - "-extldflags '-static'"
-      - -s -w -X github.com/navidrome/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/navidrome/navidrome/consts.gitTag={{.Version}}
-
-  - id: navidrome_linux_arm
-    env:
-      - CGO_ENABLED=1
-      - CC=arm-linux-gnueabi-gcc
-      - CXX=arm-linux-gnueabi-g++
-      - PKG_CONFIG_PATH=/arm/lib/pkgconfig
-    goos:
-      - linux
-    goarch:
-      - arm
-    goarm:
-      - "5"
-      - "6"
-      - "7"
-    flags:
-      - -tags=netgo
-    ldflags:
-      - "-extldflags '-static'"
-      - -s -w -X github.com/navidrome/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/navidrome/navidrome/consts.gitTag={{.Version}}
-
-  - id: navidrome_linux_arm64
-    env:
-      - CGO_ENABLED=1
-      - CC=aarch64-linux-gnu-gcc
-      - CXX=aarch64-linux-gnu-g++
-      - PKG_CONFIG_PATH=/arm64/lib/pkgconfig
-    goos:
-      - linux
-    goarch:
-      - arm64
-    flags:
-      - -tags=netgo
-    ldflags:
-      - "-extldflags '-static'"
-      - -s -w -X github.com/navidrome/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/navidrome/navidrome/consts.gitTag={{.Version}}
-
-  - id: navidrome_windows_386
-    env:
-      - CGO_ENABLED=1
-      - CC=i686-w64-mingw32-gcc
-      - CXX=i686-w64-mingw32-g++
-      - PKG_CONFIG_PATH=/mingw32/lib/pkgconfig
-    goos:
-      - windows
-    goarch:
-      - "386"
-    flags:
-      - -tags=netgo
-    ldflags:
-      - "-extldflags '-static'"
-      - -s -w -X github.com/navidrome/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/navidrome/navidrome/consts.gitTag={{.Version}}
-
-  - id: navidrome_windows_amd64
-    env:
-      - CGO_ENABLED=1
-      - CC=x86_64-w64-mingw32-gcc
-      - CXX=x86_64-w64-mingw32-g++
-      - PKG_CONFIG_PATH=/mingw64/lib/pkgconfig
-    goos:
-      - windows
-    goarch:
-      - amd64
-    flags:
-      - -tags=netgo
-    ldflags:
-      - "-extldflags '-static'"
-      - -s -w -X github.com/navidrome/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/navidrome/navidrome/consts.gitTag={{.Version}}
-
-  - id: navidrome_darwin_amd64
-    env:
-      - CGO_ENABLED=1
-      - CC=o64-clang
-      - CXX=o64-clang++
-      - PKG_CONFIG_PATH=/darwin/lib/pkgconfig
-    goos:
-      - darwin
-    goarch:
-      - amd64
-    flags:
-      - -tags=netgo
-    ldflags:
-      - -s -w -X github.com/navidrome/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/navidrome/navidrome/consts.gitTag={{.Version}}
-
-archives:
-  - format_overrides:
-      - goos: windows
-        format: zip
-
-checksum:
-  name_template: "{{ .ProjectName }}_checksums.txt"
-
-snapshot:
-  version_template: "{{ .Tag }}-SNAPSHOT"
-
-release:
-  draft: true
-  mode: append
-  footer: |
-    **Full Changelog**: https://github.com/navidrome/navidrome/compare/{{ .PreviousTag }}...{{ .Tag }}
-
-    ## Helping out
-
-    This release is only possible thanks to the support of some **awesome people**!
-
-    Want to be one of them?
-    You can [sponsor](https://github.com/sponsors/deluan), pay me a [Ko-fi](https://ko-fi.com/deluan) or [contribute with code](https://www.navidrome.org/docs/developers/).
-
-    ## Where to go next?
-
-    * Read installation instructions on our [website](https://www.navidrome.org/docs/installation/).
-    * Reach out on [Discord](https://discord.gg/xh7j7yF), [Reddit](https://www.reddit.com/r/navidrome/) and [Twitter](https://twitter.com/navidrome)!
-
-changelog:
-  sort: asc
-  use: github
-  filters:
-    exclude:
-      - "^test:"
-      - Merge pull request
-      - Merge remote-tracking branch
-      - Merge branch
-      - go mod tidy
-  groups:
-    - title: "New Features"
-      regexp: '^.*?feat(\(.+\))??!?:.+$'
-      order: 100
-    - title: "Security updates"
-      regexp: '^.*?sec(\(.+\))??!?:.+$'
-      order: 150
-    - title: "Bug fixes"
-      regexp: '^.*?(fix|refactor)(\(.+\))??!?:.+$'
-      order: 200
-    - title: "Documentation updates"
-      regexp: ^.*?docs?(\(.+\))??!?:.+$
-      order: 400
-    - title: "Build process updates"
-      regexp: ^.*?(build|ci)(\(.+\))??!?:.+$
-      order: 400
-    - title: Other work
-      order: 9999
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 000000000..60b0a6688
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,115 @@
+FROM --platform=$BUILDPLATFORM crazymax/osxcross:14.5-debian AS osxcross
+FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.5.0 AS xx
+
+#####################################################
+### Get TagLib
+FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/alpine:3.20 AS taglib-build
+ARG TARGETPLATFORM
+ARG CROSS_TAGLIB_VERSION=2.0.2-1
+ENV CROSS_TAGLIB_RELEASES_URL=https://github.com/navidrome/cross-taglib/releases/download/v${CROSS_TAGLIB_VERSION}/
+
+RUN PLATFORM=$(echo ${TARGETPLATFORM} | tr '/' '-') \
+    FILE=taglib-${PLATFORM}.tar.gz && \
+    DOWNLOAD_URL=${CROSS_TAGLIB_RELEASES_URL}${FILE} && \
+    wget ${DOWNLOAD_URL}; \
+    mkdir /taglib && \
+    tar -xzf ${FILE} -C /taglib
+
+#####################################################
+### Build Navidrome UI
+FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/node:lts-alpine3.20 AS ui
+WORKDIR /app
+
+# Install node dependencies
+COPY ui/package.json ui/package-lock.json ./
+RUN npm ci
+
+# Build bundle
+COPY ui/ ./
+RUN npm run build -- --outDir=/build
+
+FROM scratch AS ui-bundle
+COPY --from=ui /build /build
+
+#####################################################
+### Build Navidrome binary
+FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/golang:1.23-bookworm AS base
+RUN apt-get update && apt-get install -y clang lld
+COPY --from=xx / /
+WORKDIR /workspace
+
+FROM --platform=$BUILDPLATFORM base AS build
+
+# Install build dependencies for the target platform
+ARG TARGETPLATFORM
+ARG GIT_SHA
+ARG GIT_TAG
+
+RUN xx-apt install -y binutils gcc g++ libc6-dev zlib1g-dev
+RUN xx-verify --setup
+
+RUN --mount=type=bind,source=. \
+    --mount=type=cache,target=/root/.cache \
+    --mount=type=cache,target=/go/pkg/mod \
+    go mod download
+
+RUN --mount=type=bind,source=. \
+    --mount=from=ui,source=/build,target=./ui/build,ro \
+    --mount=from=osxcross,src=/osxcross/SDK,target=/xx-sdk,ro \
+    --mount=type=cache,target=/root/.cache \
+    --mount=type=cache,target=/go/pkg/mod \
+    --mount=from=taglib-build,target=/taglib,src=/taglib,ro <<EOT
+
+
+    # Setup CGO cross-compilation environment
+    xx-go --wrap
+    export CGO_ENABLED=1
+    export PKG_CONFIG_PATH=/taglib/lib/pkgconfig
+    cat $(go env GOENV)
+
+    # Only Darwin (macOS) requires clang (default), Windows requires gcc, everything else can use any compiler.
+    # So let's use gcc for everything except Darwin.
+    if [ "$(xx-info os)" != "darwin" ]; then
+        export CC=$(xx-info)-gcc
+        export CXX=$(xx-info)-g++
+        export LD_EXTRA="-extldflags '-static -latomic'"
+    fi
+    if [ "$(xx-info os)" = "windows" ]; then
+        export EXT=".exe"
+    fi
+
+    go build -tags=netgo -ldflags="${LD_EXTRA} -w -s \
+        -X github.com/navidrome/navidrome/consts.gitSha=${GIT_SHA} \
+        -X github.com/navidrome/navidrome/consts.gitTag=${GIT_TAG}" \
+        -o /out/navidrome${EXT} .
+EOT
+
+# Verify if the binary was built for the correct platform and it is statically linked
+RUN xx-verify --static /out/navidrome*
+
+FROM scratch AS binary
+COPY --from=build /out /
+
+#####################################################
+### Build Final Image
+FROM --platform=$BUILDPLATFORM public.ecr.aws/docker/library/alpine:3.20 AS final
+LABEL maintainer="deluan@navidrome.org"
+
+# Install ffmpeg and mpv
+RUN apk add -U --no-cache ffmpeg mpv
+
+# Copy navidrome binary
+COPY --from=build /out/navidrome /app/
+
+VOLUME ["/data", "/music"]
+ENV ND_MUSICFOLDER=/music
+ENV ND_DATAFOLDER=/data
+ENV ND_PORT=4533
+ENV GODEBUG="asyncpreemptoff=1"
+
+EXPOSE ${ND_PORT}
+HEALTHCHECK CMD wget -O- http://localhost:${ND_PORT}/ping || exit 1
+WORKDIR /app
+
+ENTRYPOINT ["/app/navidrome"]
+
diff --git a/Makefile b/Makefile
index 7698df6c8..d6506ffd6 100644
--- a/Makefile
+++ b/Makefile
@@ -3,13 +3,18 @@ NODE_VERSION=$(shell cat .nvmrc)
 
 ifneq ("$(wildcard .git/HEAD)","")
 GIT_SHA=$(shell git rev-parse --short HEAD)
-GIT_TAG=$(shell git describe --tags `git rev-list --tags --max-count=1`)
+GIT_TAG=$(shell git describe --tags `git rev-list --tags --max-count=1`)-SNAPSHOT
 else
 GIT_SHA=source_archive
-GIT_TAG=$(patsubst navidrome-%,v%,$(notdir $(PWD)))
+GIT_TAG=$(patsubst navidrome-%,v%,$(notdir $(PWD)))-SNAPSHOT
 endif
 
-CI_RELEASER_VERSION ?= 1.23.2-1 ## https://github.com/navidrome/ci-goreleaser
+SUPPORTED_PLATFORMS ?= linux/amd64,linux/arm64,linux/arm/v5,linux/arm/v6,linux/arm/v7,linux/386,darwin/amd64,darwin/arm64,windows/amd64,windows/386
+IMAGE_PLATFORMS ?= $(shell echo $(SUPPORTED_PLATFORMS) | tr ',' '\n' | grep "linux" | tr '\n' ',' | sed 's/,$$//')
+PLATFORMS ?= $(SUPPORTED_PLATFORMS)
+DOCKER_TAG ?= deluan/navidrome:develop
+
+CROSS_TAGLIB_VERSION ?= 2.0.2-1
 
 UI_SRC_FILES := $(shell find ui -type f -not -path "ui/build/*" -not -path "ui/node_modules/*")
 
@@ -81,45 +86,52 @@ setup-git: ##@Development Setup Git hooks (pre-commit and pre-push)
 .PHONY: setup-git
 
 build: check_go_env buildjs ##@Build Build the project
-	go build -ldflags="-X github.com/navidrome/navidrome/consts.gitSha=$(GIT_SHA) -X github.com/navidrome/navidrome/consts.gitTag=$(GIT_TAG)-SNAPSHOT" -tags=netgo
+	go build -ldflags="-X github.com/navidrome/navidrome/consts.gitSha=$(GIT_SHA) -X github.com/navidrome/navidrome/consts.gitTag=$(GIT_TAG)" -tags=netgo
 .PHONY: build
 
 buildall: deprecated build
 .PHONY: buildall
 
 debug-build: check_go_env buildjs ##@Build Build the project (with remote debug on)
-	go build -gcflags="all=-N -l" -ldflags="-X github.com/navidrome/navidrome/consts.gitSha=$(GIT_SHA) -X github.com/navidrome/navidrome/consts.gitTag=$(GIT_TAG)-SNAPSHOT" -tags=netgo
+	go build -gcflags="all=-N -l" -ldflags="-X github.com/navidrome/navidrome/consts.gitSha=$(GIT_SHA) -X github.com/navidrome/navidrome/consts.gitTag=$(GIT_TAG)" -tags=netgo
 .PHONY: debug-build
 
 buildjs: check_node_env ui/build/index.html ##@Build Build only frontend
 .PHONY: buildjs
 
+docker-buildjs: ##@Build Build only frontend using Docker
+	docker build --output "./ui" --target ui-bundle .
+.PHONY: docker-buildjs
+
 ui/build/index.html: $(UI_SRC_FILES)
 	@(cd ./ui && npm run build)
 
-all: buildjs ##@Cross_Compilation Build binaries for all supported platforms.
-	@echo "Building binaries for all platforms using builder ${CI_RELEASER_VERSION}"
-	docker run -t -v $(PWD):/workspace -w /workspace deluan/ci-goreleaser:$(CI_RELEASER_VERSION) \
- 		goreleaser release --clean --skip=publish --snapshot
-.PHONY: all
+list-platforms: ##@Cross_Compilation List supported platforms
+	@echo "Supported platforms:"
+	@echo "$(SUPPORTED_PLATFORMS)" | tr ',' '\n' | sort | sed 's/^/    /'
+	@echo "\nUsage: make PLATFORMS=\"linux/amd64\" docker-build"
+	@echo "       make IMAGE_PLATFORMS=\"linux/amd64\" docker-image"
+.PHONY: list-platforms
 
-single: buildjs ##@Cross_Compilation Build binaries for a single supported platforms.
-	@if [ -z "${GOOS}" -o -z "${GOARCH}" ]; then \
-		echo "Usage: GOOS=<os> GOARCH=<arch> make single"; \
-		echo "Options:"; \
-		grep -- "- id: navidrome_" .goreleaser.yml | sed 's/- id: navidrome_//g'; \
-		exit 1; \
-	fi
-	@echo "Building binaries for ${GOOS}/${GOARCH} using builder ${CI_RELEASER_VERSION}"
-	docker run -t -v $(PWD):/workspace -e GOOS -e GOARCH -w /workspace deluan/ci-goreleaser:$(CI_RELEASER_VERSION) \
- 		goreleaser build --clean --snapshot -p 2 --single-target --id navidrome_${GOOS}_${GOARCH}
-.PHONY: single
+docker-build: ##@Cross_Compilation Cross-compile for any supported platform (check `make list-platforms`)
+	docker build \
+		--platform $(PLATFORMS) \
+		--build-arg GIT_TAG=${GIT_TAG} \
+		--build-arg GIT_SHA=${GIT_SHA} \
+		--build-arg CROSS_TAGLIB_VERSION=${CROSS_TAGLIB_VERSION} \
+		--output "./dist" --target binary .
+.PHONY: docker-build
 
-docker: buildjs ##@Build Build Docker linux/amd64 image (tagged as `deluan/navidrome:develop`)
-	GOOS=linux GOARCH=amd64 make single
-	@echo "Building Docker image"
-	docker build . --platform linux/amd64 -t deluan/navidrome:develop -f .github/workflows/pipeline.dockerfile
-.PHONY: docker
+docker-image: ##@Cross_Compilation Build Docker image, tagged as `deluan/navidrome:develop`, override with DOCKER_TAG var. Use IMAGE_PLATFORMS to specify target platforms
+	@echo $(IMAGE_PLATFORMS) | grep -q "windows" && echo "ERROR: Windows is not supported for Docker builds" && exit 1 || true
+	@echo $(IMAGE_PLATFORMS) | grep -q "darwin" && echo "ERROR: macOS is not supported for Docker builds" && exit 1 || true
+	docker build \
+		--platform $(IMAGE_PLATFORMS) \
+		--build-arg GIT_TAG=${GIT_TAG} \
+		--build-arg GIT_SHA=${GIT_SHA} \
+		--build-arg CROSS_TAGLIB_VERSION=${CROSS_TAGLIB_VERSION} \
+		--tag $(DOCKER_TAG) .
+.PHONY: docker-image
 
 get-music: ##@Development Download some free music from Navidrome's demo instance
 	mkdir -p music
diff --git a/release/goreleaser.yml b/release/goreleaser.yml
new file mode 100644
index 000000000..91f9b98bb
--- /dev/null
+++ b/release/goreleaser.yml
@@ -0,0 +1,88 @@
+# GoReleaser config
+project_name: navidrome
+version: 2
+
+builds:
+  - id: navidrome
+    # Instead of compiling the binary with goreleaser, we just copy it from `binaries` folder
+    # This is because we need to compile the binaries with our Dockerfile, and to avoid having to
+    # compile it twice, we just copy the docker build output.env. The xxgo script handles this for us
+    gobinary: "./release/xxgo"
+
+    # All available targets compiled by the Dockerfile
+    targets:
+      - darwin_amd64
+      - darwin_arm64
+      - linux_386
+      - linux_amd64
+      - linux_arm_v5
+      - linux_arm_v6
+      - linux_arm_v7
+      - linux_arm64
+      - windows_386
+      - windows_amd64
+
+archives:
+  - format_overrides:
+      - goos: windows
+        format: zip
+
+checksum:
+  name_template: "{{ .ProjectName }}_checksums.txt"
+
+snapshot:
+  version_template: "{{ .Tag }}-SNAPSHOT"
+
+release:
+  draft: true
+  mode: append
+  footer: |
+    **Full Changelog**: https://github.com/navidrome/navidrome/compare/{{ .PreviousTag }}...{{ .Tag }}
+
+    ## Helping out
+
+    This release is only possible thanks to the support of some **awesome people**!
+
+    Want to be one of them?
+    You can [sponsor](https://github.com/sponsors/deluan), pay me a [Ko-fi](https://ko-fi.com/deluan) or [contribute with code](https://www.navidrome.org/docs/developers/).
+
+    ## Where to go next?
+
+    * Read installation instructions on our [website](https://www.navidrome.org/docs/installation/).
+    * Reach out on [Discord](https://discord.gg/xh7j7yF), [Reddit](https://www.reddit.com/r/navidrome/) and [Twitter](https://twitter.com/navidrome)!
+
+  # Add the MSI installers to the release
+  extra_files:
+    - glob: binaries/navidrome_386.msi
+      name_template: navidrome_{{.Version}}_windows_386_installer.msi
+    - glob: binaries/navidrome_amd64.msi
+      name_template: navidrome_{{.Version}}_windows_amd64_installer.msi
+
+changelog:
+  sort: asc
+  use: github
+  filters:
+    exclude:
+      - "^test:"
+      - Merge pull request
+      - Merge remote-tracking branch
+      - Merge branch
+      - go mod tidy
+  groups:
+    - title: "New Features"
+      regexp: '^.*?feat(\(.+\))??!?:.+$'
+      order: 100
+    - title: "Security updates"
+      regexp: '^.*?sec(\(.+\))??!?:.+$'
+      order: 150
+    - title: "Bug fixes"
+      regexp: '^.*?(fix|refactor)(\(.+\))??!?:.+$'
+      order: 200
+    - title: "Documentation updates"
+      regexp: ^.*?docs?(\(.+\))??!?:.+$
+      order: 400
+    - title: "Build process updates"
+      regexp: ^.*?(build|ci)(\(.+\))??!?:.+$
+      order: 400
+    - title: Other work
+      order: 9999
diff --git a/release/xxgo b/release/xxgo
new file mode 100755
index 000000000..15a537f24
--- /dev/null
+++ b/release/xxgo
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+# Use sed to extract the value of the -o parameter
+output=$(echo "$@" | sed -n 's/.*-o \([^ ]*\).*/\1/p')
+
+# Ensure the directory part of the output exists
+mkdir -p "$(dirname "$output")"
+
+# Build the source folder name based on GOOS, GOARCH and GOARM.
+source="${GOOS}_${GOARCH}"
+if [ "$GOARCH" = "arm" ]; then
+    source="${source}_${GOARM}"
+fi
+
+# Copy the output to the desired location
+cp binaries/"${source}"/navidrome* "$output"
\ No newline at end of file
diff --git a/scanner/metadata/taglib/taglib_wrapper.go b/scanner/metadata/taglib/taglib_wrapper.go
index 888c6ac8c..01fea25ef 100644
--- a/scanner/metadata/taglib/taglib_wrapper.go
+++ b/scanner/metadata/taglib/taglib_wrapper.go
@@ -1,7 +1,7 @@
 package taglib
 
 /*
-#cgo pkg-config: taglib
+#cgo pkg-config: --define-prefix taglib
 #cgo illumos LDFLAGS: -lstdc++ -lsendfile
 #cgo linux darwin CXXFLAGS: -std=c++11
 #cgo darwin LDFLAGS: -L/opt/homebrew/opt/taglib/lib