diff --git a/changelog.d/18292.docker b/changelog.d/18292.docker
new file mode 100644
index 0000000000..cdb95b369b
--- /dev/null
+++ b/changelog.d/18292.docker
@@ -0,0 +1 @@
+Optimize the build of the workers image.
diff --git a/docker/Dockerfile-workers b/docker/Dockerfile-workers
index dd0bf59994..a7f576184d 100644
--- a/docker/Dockerfile-workers
+++ b/docker/Dockerfile-workers
@@ -3,18 +3,37 @@
ARG SYNAPSE_VERSION=latest
ARG FROM=matrixdotorg/synapse:$SYNAPSE_VERSION
ARG DEBIAN_VERSION=bookworm
+ARG PYTHON_VERSION=3.12
-# first of all, we create a base image with an nginx which we can copy into the
+# first of all, we create a base image with dependencies which we can copy into the
# target image. For repeated rebuilds, this is much faster than apt installing
# each time.
-FROM docker.io/library/debian:${DEBIAN_VERSION}-slim AS deps_base
+FROM ghcr.io/astral-sh/uv:python${PYTHON_VERSION}-${DEBIAN_VERSION} AS deps_base
+
+ # Tell apt to keep downloaded package files, as we're using cache mounts.
+ RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
+
RUN \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update -qq && \
DEBIAN_FRONTEND=noninteractive apt-get install -yqq --no-install-recommends \
- redis-server nginx-light
+ nginx-light
+
+ RUN \
+ # remove default page
+ rm /etc/nginx/sites-enabled/default && \
+ # have nginx log to stderr/out
+ ln -sf /dev/stdout /var/log/nginx/access.log && \
+ ln -sf /dev/stderr /var/log/nginx/error.log
+
+ # --link-mode=copy silences a warning as uv isn't able to do hardlinks between its cache
+ # (mounted as --mount=type=cache) and the target directory.
+ RUN --mount=type=cache,target=/root/.cache/uv \
+ uv pip install --link-mode=copy --prefix="/uv/usr/local" supervisor~=4.2
+
+ RUN mkdir -p /uv/etc/supervisor/conf.d
# Similarly, a base to copy the redis server from.
#
@@ -27,31 +46,16 @@ FROM docker.io/library/redis:7-${DEBIAN_VERSION} AS redis_base
# now build the final image, based on the the regular Synapse docker image
FROM $FROM
- # Install supervisord with uv pip instead of apt, to avoid installing a second
- # copy of python.
- # --link-mode=copy silences a warning as uv isn't able to do hardlinks between its cache
- # (mounted as --mount=type=cache) and the target directory.
- RUN \
- --mount=type=bind,from=ghcr.io/astral-sh/uv:0.6.8,source=/uv,target=/uv \
- --mount=type=cache,target=/root/.cache/uv \
- /uv pip install --link-mode=copy --prefix="/usr/local" supervisor~=4.2
-
- RUN mkdir -p /etc/supervisor/conf.d
-
- # Copy over redis and nginx
+ # Copy over dependencies
COPY --from=redis_base /usr/local/bin/redis-server /usr/local/bin
-
+ COPY --from=deps_base /uv /
COPY --from=deps_base /usr/sbin/nginx /usr/sbin
COPY --from=deps_base /usr/share/nginx /usr/share/nginx
COPY --from=deps_base /usr/lib/nginx /usr/lib/nginx
COPY --from=deps_base /etc/nginx /etc/nginx
- RUN rm /etc/nginx/sites-enabled/default
- RUN mkdir /var/log/nginx /var/lib/nginx
- RUN chown www-data /var/lib/nginx
-
- # have nginx log to stderr/out
- RUN ln -sf /dev/stdout /var/log/nginx/access.log
- RUN ln -sf /dev/stderr /var/log/nginx/error.log
+ COPY --from=deps_base /var/log/nginx /var/log/nginx
+ # chown to allow non-root user to write to http-*-temp-path dirs
+ COPY --from=deps_base --chown=www-data:root /var/lib/nginx /var/lib/nginx
# Copy Synapse worker, nginx and supervisord configuration template files
COPY ./docker/conf-workers/* /conf/
|