diff --git a/.github/workflows/docker-vulkan.yml b/.github/workflows/docker-vulkan.yml
new file mode 100644
index 000000000000..70637440bca8
--- /dev/null
+++ b/.github/workflows/docker-vulkan.yml
@@ -0,0 +1,130 @@
+name: Create and publish docker image (Vulkan)
+
+on:
+  workflow_dispatch:
+  schedule:
+    - cron: "0 20 */1 * *"
+  push:
+    tags:
+      - "v*"
+      - "!*-dev.*"
+      - "!vscode@*"
+      - '!vim@*'
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
+
+  # If this is enabled it will cancel current running and start latest
+  cancel-in-progress: true
+
+env:
+  RUST_TOOLCHAIN: 1.82.0
+
+jobs:
+  release-docker:
+    runs-on: buildjet-2vcpu-ubuntu-2204
+    permissions:
+      contents: read
+      packages: write
+      # This is used to complete the identity challenge
+      # with sigstore/fulcio when running outside of PRs.
+      id-token: write
+
+    strategy:
+      matrix:
+        device-type: [vulkan]
+        include:
+          - device-type: vulkan
+            image-suffix: "-vulkan"
+
+    steps:
+      - name: Free Disk Space (Ubuntu)
+        uses: jlumbroso/free-disk-space@main
+        with:
+          # this might remove tools that are actually needed,
+          # if set to "true" but frees about 6 GB
+          tool-cache: true
+
+          # all of these default to true, but feel free to set to
+          # "false" if necessary for your workflow
+          android: true
+          dotnet: true
+          haskell: true
+          large-packages: false
+          swap-storage: true
+
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          submodules: recursive
+
+      - name: Setup QEMU
+        uses: docker/setup-qemu-action@v3
+
+      # Workaround: https://github.com/docker/build-push-action/issues/461
+      - name: Setup Docker buildx
+        uses: docker/setup-buildx-action@v3
+
+      # Login against a Docker registry except on PR
+      # https://github.com/docker/login-action
+      - name: Log into GitHub Container registry
+        uses: docker/login-action@v3
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Log into Docker Hub
+        uses: docker/login-action@v3
+        with:
+          username: ${{ secrets.DOCKERHUB_USERNAME }}
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+      - name: Generate image name
+        env:
+          IMAGE_SUFFIX: ${{ matrix.image-suffix }}
+        run: |
+          echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}${IMAGE_SUFFIX}" >>${GITHUB_ENV}
+
+      - uses: int128/docker-build-cache-config-action@v1
+        id: cache
+        with:
+          image: ghcr.io/${{ env.IMAGE_NAME }}/cache
+
+      - name: Docker meta
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          # list of Docker images to use as base name for tags
+          images: |
+            ghcr.io/${{ env.IMAGE_NAME }}
+            ${{ env.IMAGE_NAME }}
+          # generate Docker tags based on the following events/attributes
+          tags: |
+            type=raw,value={{branch}}-{{sha}},enable=${{ startsWith(github.ref, 'refs/heads') }}
+            type=schedule,pattern=nightly
+            type=schedule,pattern={{date 'YYYYMMDD'}}
+            type=semver,pattern={{version}}
+
+      # Build and push Docker image with Buildx (don't push on PR)
+      # https://github.com/docker/build-push-action
+      - name: Build and push Docker image
+        id: build-and-push
+        uses: docker/build-push-action@v5
+        with:
+          file: docker/Dockerfile.${{ matrix.device-type }}
+          platforms: linux/amd64,linux/arm64
+          push: true
+          context: .
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
+          cache-from: ${{ steps.cache.outputs.cache-from }}
+          cache-to: ${{ steps.cache.outputs.cache-to }}
+          build-args: RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }}
+
+      - name: Docker Hub Description
+        uses: peter-evans/dockerhub-description@v4
+        with:
+          username: ${{ secrets.DOCKERHUB_USERNAME }}
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
+          repository: tabbyml/tabby
diff --git a/docker/Dockerfile.vulkan b/docker/Dockerfile.vulkan
new file mode 100644
index 000000000000..3527757f9cb0
--- /dev/null
+++ b/docker/Dockerfile.vulkan
@@ -0,0 +1,80 @@
+ARG UBUNTU_VERSION=24.04
+
+# Target the VULKAN build image
+ARG BASE_VULKAN_DEV_CONTAINER=ubuntu:${UBUNTU_VERSION}
+# Target the VULKAN runtime image
+ARG BASE_VULKAN_RUN_CONTAINER=ubuntu:${UBUNTU_VERSION}
+
+FROM ${BASE_VULKAN_DEV_CONTAINER} AS build
+
+# Rust toolchain version
+ARG RUST_TOOLCHAIN=stable
+
+ENV DEBIAN_FRONTEND=noninteractive
+RUN apt-get update && \
+    apt-get install -y --no-install-recommends \
+        curl \
+        ca-certificates \
+        pkg-config \
+        libssl-dev \
+        libvulkan-dev \
+        glslc \
+        protobuf-compiler \
+        git \
+        cmake \
+        build-essential \
+        && \
+    apt-get clean && \
+    rm -rf /var/lib/apt/lists/*
+
+# setup rust.
+RUN curl https://sh.rustup.rs -sSf | bash -s -- --default-toolchain ${RUST_TOOLCHAIN} -y
+ENV PATH="/root/.cargo/bin:${PATH}"
+
+WORKDIR /root/workspace
+
+RUN mkdir -p /opt/tabby/bin
+RUN mkdir -p /opt/tabby/lib
+RUN mkdir -p target
+
+COPY . .
+
+RUN --mount=type=cache,target=/usr/local/cargo/registry \
+    --mount=type=cache,target=/root/workspace/target \
+    cargo build --no-default-features --features vulkan,prod --release --package tabby && \
+    cp target/release/llama-server /opt/tabby/bin/ && \
+    cp target/release/tabby /opt/tabby/bin/
+
+# For compatibility with the legacy cpu build.
+RUN cp /opt/tabby/bin/tabby /opt/tabby/bin/tabby-cpu
+
+FROM ${BASE_VULKAN_RUN_CONTAINER} AS runtime
+
+ENV DEBIAN_FRONTEND=noninteractive
+RUN apt-get update && \
+    apt-get install -y --no-install-recommends \
+        git   \
+        curl  \
+        unzip \
+        openssh-client \
+        ca-certificates \
+        libgomp1 \
+        libvulkan1 \
+        && \
+    apt-get clean && \
+    rm -rf /var/lib/apt/lists/*
+
+# Disable safe directory in docker
+# Context: https://github.com/git/git/commit/8959555cee7ec045958f9b6dd62e541affb7e7d9
+RUN git config --system --add safe.directory "*"
+
+# Automatic platform ARGs in the global scope
+# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
+ARG TARGETARCH
+
+COPY --from=build /opt/tabby /opt/tabby
+
+ENV PATH="$PATH:/opt/tabby/bin"
+ENV TABBY_ROOT=/data
+
+ENTRYPOINT ["/opt/tabby/bin/tabby"]