20
20
# `poetry export | pip install -r /dev/stdin`, but beware: we have experienced bugs in
21
21
# in `poetry export` in the past.
22
22
23
+ ARG DEBIAN_VERSION=bookworm
23
24
ARG PYTHON_VERSION=3.12
25
+ ARG POETRY_VERSION=1.8.3
24
26
25
27
# ##
26
28
# ## Stage 0: generate requirements.txt
27
29
# ##
28
- # We hardcode the use of Debian bookworm here because this could change upstream
29
- # and other Dockerfiles used for testing are expecting bookworm.
30
- FROM docker.io/library/python:${PYTHON_VERSION}-slim-bookworm AS requirements
31
-
32
- # RUN --mount is specific to buildkit and is documented at
33
- # https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/syntax.md#build-mounts-run---mount.
34
- # Here we use it to set up a cache for apt (and below for pip), to improve
35
- # rebuild speeds on slow connections.
36
- RUN \
37
- --mount=type=cache,target=/var/cache/apt,sharing=locked \
38
- --mount=type=cache,target=/var/lib/apt,sharing=locked \
39
- apt-get update -qq && apt-get install -yqq \
40
- build-essential curl git libffi-dev libssl-dev pkg-config \
41
- && rm -rf /var/lib/apt/lists/*
42
-
43
- # Install rust and ensure its in the PATH.
44
- # (Rust may be needed to compile `cryptography`---which is one of poetry's
45
- # dependencies---on platforms that don't have a `cryptography` wheel.
46
- ENV RUSTUP_HOME=/rust
47
- ENV CARGO_HOME=/cargo
48
- ENV PATH=/cargo/bin:/rust/bin:$PATH
49
- RUN mkdir /rust /cargo
50
-
51
- RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --default-toolchain stable --profile minimal
52
-
53
- # arm64 builds consume a lot of memory if `CARGO_NET_GIT_FETCH_WITH_CLI` is not
54
- # set to true, so we expose it as a build-arg.
55
- ARG CARGO_NET_GIT_FETCH_WITH_CLI=false
56
- ENV CARGO_NET_GIT_FETCH_WITH_CLI=$CARGO_NET_GIT_FETCH_WITH_CLI
57
-
58
- # We install poetry in its own build stage to avoid its dependencies conflicting with
59
- # synapse's dependencies.
60
- RUN --mount=type=cache,target=/root/.cache/pip \
61
- pip install --user "poetry==1.3.2"
30
+ # ## This stage is platform-agnostic, so we can use the build platform in case of cross-compilation.
31
+ # ##
32
+ FROM --platform=$BUILDPLATFORM ghcr.io/astral-sh/uv:python${PYTHON_VERSION}-${DEBIAN_VERSION} AS requirements
62
33
63
34
WORKDIR /synapse
64
35
@@ -75,41 +46,30 @@ ARG TEST_ONLY_SKIP_DEP_HASH_VERIFICATION
75
46
# Instead, we'll just install what a regular `pip install` would from PyPI.
76
47
ARG TEST_ONLY_IGNORE_POETRY_LOCKFILE
77
48
49
+ # This silences a warning as uv isn't able to do hardlinks between its cache
50
+ # (mounted as --mount=type=cache) and the target directory.
51
+ ENV UV_LINK_MODE=copy
52
+
78
53
# Export the dependencies, but only if we're actually going to use the Poetry lockfile.
79
54
# Otherwise, just create an empty requirements file so that the Dockerfile can
80
55
# proceed.
81
- RUN if [ -z "$TEST_ONLY_IGNORE_POETRY_LOCKFILE" ]; then \
82
- /root/.local/bin/poetry export --extras all -o /synapse/requirements.txt ${TEST_ONLY_SKIP_DEP_HASH_VERIFICATION:+--without-hashes}; \
56
+ ARG POETRY_VERSION
57
+ RUN --mount=type=cache,target=/root/.cache/uv \
58
+ if [ -z "$TEST_ONLY_IGNORE_POETRY_LOCKFILE" ]; then \
59
+ uvx --with poetry-plugin-export==1.8.0 \
60
+ poetry@${POETRY_VERSION} export --extras all -o /synapse/requirements.txt ${TEST_ONLY_SKIP_DEP_HASH_VERIFICATION:+--without-hashes}; \
83
61
else \
84
- touch /synapse/requirements.txt; \
62
+ touch /synapse/requirements.txt; \
85
63
fi
86
64
87
65
# ##
88
66
# ## Stage 1: builder
89
67
# ##
90
- FROM docker.io/library/python:${PYTHON_VERSION}-slim-bookworm AS builder
91
-
92
- # install the OS build deps
93
- RUN \
94
- --mount=type=cache,target=/var/cache/apt,sharing=locked \
95
- --mount=type=cache,target=/var/lib/apt,sharing=locked \
96
- apt-get update -qq && apt-get install -yqq \
97
- build-essential \
98
- libffi-dev \
99
- libjpeg-dev \
100
- libpq-dev \
101
- libssl-dev \
102
- libwebp-dev \
103
- libxml++2.6-dev \
104
- libxslt1-dev \
105
- openssl \
106
- zlib1g-dev \
107
- git \
108
- curl \
109
- libicu-dev \
110
- pkg-config \
111
- && rm -rf /var/lib/apt/lists/*
68
+ FROM ghcr.io/astral-sh/uv:python${PYTHON_VERSION}-${DEBIAN_VERSION} AS builder
112
69
70
+ # This silences a warning as uv isn't able to do hardlinks between its cache
71
+ # (mounted as --mount=type=cache) and the target directory.
72
+ ENV UV_LINK_MODE=copy
113
73
114
74
# Install rust and ensure its in the PATH
115
75
ENV RUSTUP_HOME=/rust
@@ -119,7 +79,6 @@ RUN mkdir /rust /cargo
119
79
120
80
RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --default-toolchain stable --profile minimal
121
81
122
-
123
82
# arm64 builds consume a lot of memory if `CARGO_NET_GIT_FETCH_WITH_CLI` is not
124
83
# set to true, so we expose it as a build-arg.
125
84
ARG CARGO_NET_GIT_FETCH_WITH_CLI=false
@@ -131,8 +90,8 @@ ENV CARGO_NET_GIT_FETCH_WITH_CLI=$CARGO_NET_GIT_FETCH_WITH_CLI
131
90
#
132
91
# This is aiming at installing the `[tool.poetry.depdendencies]` from pyproject.toml.
133
92
COPY --from=requirements /synapse/requirements.txt /synapse/
134
- RUN --mount=type=cache,target=/root/.cache/pip \
135
- pip install --prefix="/install" --no-deps --no-warn-script-location -r /synapse/requirements.txt
93
+ RUN --mount=type=cache,target=/root/.cache/uv \
94
+ uv pip install --prefix="/install" --no-deps -r /synapse/requirements.txt
136
95
137
96
# Copy over the rest of the synapse source code.
138
97
COPY synapse /synapse/synapse/
@@ -146,41 +105,85 @@ ARG TEST_ONLY_IGNORE_POETRY_LOCKFILE
146
105
# Install the synapse package itself.
147
106
# If we have populated requirements.txt, we don't install any dependencies
148
107
# as we should already have those from the previous `pip install` step.
149
- RUN --mount=type=cache,target=/synapse/target,sharing=locked \
108
+ RUN \
109
+ --mount=type=cache,target=/root/.cache/uv \
110
+ --mount=type=cache,target=/synapse/target,sharing=locked \
150
111
--mount=type=cache,target=${CARGO_HOME}/registry,sharing=locked \
151
112
if [ -z "$TEST_ONLY_IGNORE_POETRY_LOCKFILE" ]; then \
152
- pip install --prefix="/install" --no-deps --no-warn-script-location /synapse[all]; \
113
+ uv pip install --prefix="/install" --no-deps /synapse[all]; \
153
114
else \
154
- pip install --prefix="/install" --no-warn-script-location /synapse[all]; \
115
+ uv pip install --prefix="/install" /synapse[all]; \
155
116
fi
156
117
157
118
# ##
158
- # ## Stage 2: runtime
119
+ # ## Stage 2: runtime dependencies download for ARM64 and AMD64
159
120
# ##
121
+ FROM --platform=$BUILDPLATFORM docker.io/library/debian:${DEBIAN_VERSION} AS runtime-deps
160
122
161
- FROM docker.io/library/python:${PYTHON_VERSION}-slim-bookworm
123
+ # Tell apt to keep downloaded package files, as we're using cache mounts.
124
+ 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
162
125
163
- LABEL org.opencontainers.image.url='https://matrix.org/docs/projects/server/synapse'
164
- LABEL org.opencontainers.image.documentation='https://github.com/element-hq/synapse/blob/master/docker/README.md'
165
- LABEL org.opencontainers.image.source='https://github.com/element-hq/synapse.git'
166
- LABEL org.opencontainers.image.licenses='AGPL-3.0-or-later'
126
+ # Add both target architectures
127
+ RUN dpkg --add-architecture arm64
128
+ RUN dpkg --add-architecture amd64
167
129
130
+ # Fetch the runtime dependencies debs for both architectures
131
+ # We do that by building a recursive list of packages we need to download with `apt-cache depends`
132
+ # and then downloading them with `apt-get download`.
168
133
RUN \
169
134
--mount=type=cache,target=/var/cache/apt,sharing=locked \
170
135
--mount=type=cache,target=/var/lib/apt,sharing=locked \
171
- apt-get update -qq && apt-get install -yqq \
172
- curl \
173
- gosu \
174
- libjpeg62-turbo \
175
- libpq5 \
176
- libwebp7 \
177
- xmlsec1 \
178
- libjemalloc2 \
179
- libicu72 \
180
- libssl-dev \
181
- openssl \
182
- && rm -rf /var/lib/apt/lists/*
136
+ apt-get update -qq && \
137
+ apt-get install -y --no-install-recommends rsync && \
138
+ apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances --no-pre-depends \
139
+ curl \
140
+ gosu \
141
+ libjpeg62-turbo \
142
+ libpq5 \
143
+ libwebp7 \
144
+ xmlsec1 \
145
+ libjemalloc2 \
146
+ libicu \
147
+ | grep '^\w ' > /tmp/pkg-list && \
148
+ for arch in arm64 amd64; do \
149
+ mkdir -p /tmp/debs-${arch} && \
150
+ cd /tmp/debs-${arch} && \
151
+ apt-get download $(sed "s/$/:${arch}/" /tmp/pkg-list); \
152
+ done
153
+
154
+ # Extract the debs for each architecture
155
+ # On the runtime image, /lib is a symlink to /usr/lib, so we need to copy the
156
+ # libraries to the right place, else the `COPY` won't work.
157
+ # On amd64, we'll also have a /lib64 folder with ld-linux-x86-64.so.2, which is
158
+ # already present in the runtime image.
159
+ RUN \
160
+ for arch in arm64 amd64; do \
161
+ mkdir -p /install-${arch}/var/lib/dpkg/status.d/ && \
162
+ for deb in /tmp/debs-${arch}/*.deb; do \
163
+ package_name=$(dpkg-deb -I ${deb} | awk '/^ Package: .*$/ {print $2}' ); \
164
+ echo "Extracting: ${package_name}" ; \
165
+ dpkg --ctrl-tarfile $deb | tar -Ox ./control > /install-${arch}/var/lib/dpkg/status.d/${package_name}; \
166
+ dpkg --extract $deb /install-${arch}; \
167
+ done; \
168
+ rsync -avr /install-${arch}/lib/ /install-${arch}/usr/lib; \
169
+ rm -rf /install-${arch}/lib /install-${arch}/lib64; \
170
+ done
171
+
172
+
173
+ # ##
174
+ # ## Stage 3: runtime
175
+ # ##
176
+
177
+ FROM docker.io/library/python:${PYTHON_VERSION}-slim-${DEBIAN_VERSION}
178
+
179
+ ARG TARGETARCH
180
+
181
+ LABEL org.opencontainers.image.url='https://matrix.org/docs/projects/server/synapse'
182
+ LABEL org.opencontainers.image.documentation='https://github.com/element-hq/synapse/blob/master/docker/README.md'
183
+ LABEL org.opencontainers.image.source='https://github.com/element-hq/synapse.git'
184
+ LABEL org.opencontainers.image.licenses='AGPL-3.0-or-later'
183
185
186
+ COPY --from=runtime-deps /install-${TARGETARCH} /
184
187
COPY --from=builder /install /usr/local
185
188
COPY ./docker/start.py /start.py
186
189
COPY ./docker/conf /conf
0 commit comments