diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
index 7c837d499cea..e38a26f30f32 100644
--- a/.github/workflows/publish.yaml
+++ b/.github/workflows/publish.yaml
@@ -183,7 +183,7 @@ jobs:
           TAG="${{ github.event.inputs.version || github.ref_name }}"
           echo "VERSION=${TAG#v}" >> $GITHUB_ENV
 
-      - name: Download release artifacts
+      - name: Download deb artifacts
         uses: robinraju/release-downloader@v1.9
         with:
           repository: "coder/code-server"
@@ -191,6 +191,14 @@ jobs:
           fileName: "*.deb"
           out-file-path: "release-packages"
 
+      - name: Download rpm artifacts
+        uses: robinraju/release-downloader@v1.9
+        with:
+          repository: "coder/code-server"
+          tag: v${{ env.VERSION }}
+          fileName: "*.rpm"
+          out-file-path: "release-packages"
+
       - name: Publish to Docker
         run: ./ci/steps/docker-buildx-push.sh
         env:
diff --git a/ci/release-image/Dockerfile.fedora b/ci/release-image/Dockerfile.fedora
new file mode 100644
index 000000000000..42866422abdb
--- /dev/null
+++ b/ci/release-image/Dockerfile.fedora
@@ -0,0 +1,51 @@
+# syntax=docker/dockerfile:experimental
+
+ARG BASE=fedora:39
+FROM scratch AS packages
+COPY release-packages/code-server*.rpm /tmp/
+
+FROM $BASE
+
+RUN dnf update -y \
+    && dnf install -y \
+    curl \
+    git \
+    git-lfs \
+    htop \
+    nano \
+    openssh-clients \
+    procps \
+    wget \
+    zsh \
+    dumb-init \
+    glibc-langpack-en \
+    && rm -rf /var/cache/dnf
+RUN git lfs install
+
+ENV LANG=en_US.UTF-8
+RUN echo 'LANG="en_US.UTF-8"' > /etc/locale.conf
+
+RUN useradd -u 1000 coder && echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
+
+RUN ARCH="$(uname -m | sed 's/x86_64/amd64/g' | sed 's/aarch64/arm64/g')" \
+  && curl -fsSL "https://github.com/boxboat/fixuid/releases/download/v0.6.0/fixuid-0.6.0-linux-$ARCH.tar.gz" | tar -C /usr/local/bin -xzf - \
+  && chown root:root /usr/local/bin/fixuid \
+  && chmod 4755 /usr/local/bin/fixuid \
+  && mkdir -p /etc/fixuid \
+  && printf "user: coder\ngroup: coder\n" > /etc/fixuid/config.yml
+
+COPY ci/release-image/entrypoint.sh /usr/bin/entrypoint.sh
+RUN --mount=from=packages,src=/tmp,dst=/tmp/packages dnf install -y /tmp/packages/code-server*$(uname -m | sed 's/x86_64/amd64/g' | sed 's/aarch64/arm64/g').rpm
+
+# Allow users to have scripts run on container startup to prepare workspace.
+# https://github.com/coder/code-server/issues/5177
+ENV ENTRYPOINTD=${HOME}/entrypoint.d
+
+EXPOSE 8080
+# This way, if someone sets $DOCKER_USER, docker-exec will still work as
+# the uid will remain the same. note: only relevant if -u isn't passed to
+# docker-run.
+USER 1000
+ENV USER=coder
+WORKDIR /home/coder
+ENTRYPOINT ["/usr/bin/entrypoint.sh", "--bind-addr", "0.0.0.0:8080", "."]
diff --git a/ci/release-image/Dockerfile.opensuse b/ci/release-image/Dockerfile.opensuse
new file mode 100644
index 000000000000..f445d45c27b1
--- /dev/null
+++ b/ci/release-image/Dockerfile.opensuse
@@ -0,0 +1,51 @@
+# syntax=docker/dockerfile:experimental
+
+ARG BASE=opensuse/tumbleweed
+FROM scratch AS packages
+COPY release-packages/code-server*.rpm /tmp/
+
+FROM $BASE
+
+RUN zypper dup -y \
+    && zypper in -y \
+    curl \
+    git \
+    git-lfs \
+    htop \
+    nano \
+    openssh-clients \
+    procps \
+    wget \
+    zsh \
+    sudo \
+    catatonit \
+    && rm -rf /var/cache/zypp /var/cache/zypper
+RUN git lfs install
+
+ENV LANG=en_US.UTF-8
+RUN echo 'LANG="en_US.UTF-8"' > /etc/locale.conf
+
+RUN useradd -u 1000 coder && echo "coder ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/nopasswd
+
+RUN ARCH="$(uname -m | sed 's/x86_64/amd64/g' | sed 's/aarch64/arm64/g')" \
+  && curl -fsSL "https://github.com/boxboat/fixuid/releases/download/v0.6.0/fixuid-0.6.0-linux-$ARCH.tar.gz" | tar -C /usr/local/bin -xzf - \
+  && chown root:root /usr/local/bin/fixuid \
+  && chmod 4755 /usr/local/bin/fixuid \
+  && mkdir -p /etc/fixuid \
+  && printf "user: coder\ngroup: coder\n" > /etc/fixuid/config.yml
+
+COPY ci/release-image/entrypoint-catatonit.sh /usr/bin/entrypoint-catatonit.sh
+RUN --mount=from=packages,src=/tmp,dst=/tmp/packages rpm -i /tmp/packages/code-server*$(uname -m | sed 's/x86_64/amd64/g' | sed 's/aarch64/arm64/g').rpm
+
+# Allow users to have scripts run on container startup to prepare workspace.
+# https://github.com/coder/code-server/issues/5177
+ENV ENTRYPOINTD=${HOME}/entrypoint.d
+
+EXPOSE 8080
+# This way, if someone sets $DOCKER_USER, docker-exec will still work as
+# the uid will remain the same. note: only relevant if -u isn't passed to
+# docker-run.
+USER 1000
+ENV USER=coder
+WORKDIR /home/coder
+ENTRYPOINT ["/usr/bin/entrypoint-catatonit.sh", "--bind-addr", "0.0.0.0:8080", "."]
diff --git a/ci/release-image/docker-bake.hcl b/ci/release-image/docker-bake.hcl
index b45d613fd8cc..90d228862c81 100644
--- a/ci/release-image/docker-bake.hcl
+++ b/ci/release-image/docker-bake.hcl
@@ -18,6 +18,8 @@ group "default" {
     targets = [
         "code-server-debian-12",
         "code-server-ubuntu-focal",
+        "code-server-fedora-39",
+        "code-server-opensuse-tumbleweed",
     ]
 }
 
@@ -66,3 +68,27 @@ target "code-server-ubuntu-focal" {
     }
     platforms = ["linux/amd64", "linux/arm64"]
 }
+
+target "code-server-fedora-39" {
+    dockerfile = "ci/release-image/Dockerfile.fedora"
+    tags = concat(
+        gen_tags_for_docker_and_ghcr("fedora"),
+        gen_tags_for_docker_and_ghcr("39"),
+    )
+    args = {
+        BASE = "fedora:39"
+    }
+    platforms = ["linux/amd64", "linux/arm64"]
+}
+
+target "code-server-opensuse-tumbleweed" {
+    dockerfile = "ci/release-image/Dockerfile.opensuse"
+    tags = concat(
+        gen_tags_for_docker_and_ghcr("opensuse"),
+        gen_tags_for_docker_and_ghcr("tumbleweed"),
+    )
+    args = {
+        BASE = "opensuse/tumbleweed"
+    }
+    platforms = ["linux/amd64", "linux/arm64"]
+}
diff --git a/ci/release-image/entrypoint-catatonit.sh b/ci/release-image/entrypoint-catatonit.sh
new file mode 100755
index 000000000000..d22acc6d237b
--- /dev/null
+++ b/ci/release-image/entrypoint-catatonit.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+set -eu
+
+# We do this first to ensure sudo works below when renaming the user.
+# Otherwise the current container UID may not exist in the passwd database.
+eval "$(fixuid -q)"
+
+if [ "${DOCKER_USER-}" ]; then
+  USER="$DOCKER_USER"
+  if [ "$DOCKER_USER" != "$(whoami)" ]; then
+    echo "$DOCKER_USER ALL=(ALL) NOPASSWD:ALL" | sudo tee -a /etc/sudoers.d/nopasswd > /dev/null
+    # Unfortunately we cannot change $HOME as we cannot move any bind mounts
+    # nor can we bind mount $HOME into a new home as that requires a privileged container.
+    sudo usermod --login "$DOCKER_USER" coder
+    sudo groupmod -n "$DOCKER_USER" coder
+
+    sudo sed -i "/coder/d" /etc/sudoers.d/nopasswd
+  fi
+fi
+
+# Allow users to have scripts run on container startup to prepare workspace.
+# https://github.com/coder/code-server/issues/5177
+if [ -d "${ENTRYPOINTD}" ]; then
+  find "${ENTRYPOINTD}" -type f -executable -print -exec {} \;
+fi
+
+exec catatonit -- /usr/bin/code-server "$@"