|
| 1 | +# syntax=docker/dockerfile:1 |
| 2 | +# Custom Dockerfile for Synapse — replaces ghcr.io/astral-sh/uv with pip |
| 3 | +# for riscv64 compatibility. Based on upstream synapse/docker/Dockerfile. |
| 4 | +# |
| 5 | +# Changes from upstream: |
| 6 | +# - Stage 0: Replace uv image with python-slim, use pip-installed poetry |
| 7 | +# - Stage 1: Replace uv image with python, use pip instead of uv |
| 8 | +# - Stage 2: Add riscv64 to runtime dependency architectures |
| 9 | + |
| 10 | +ARG DEBIAN_VERSION=trixie |
| 11 | +ARG PYTHON_VERSION=3.13 |
| 12 | +ARG POETRY_VERSION=2.1.1 |
| 13 | + |
| 14 | +### |
| 15 | +### Stage 0: generate requirements.txt |
| 16 | +### |
| 17 | +### This stage is platform-agnostic, so we can use the build platform in case of cross-compilation. |
| 18 | +### |
| 19 | +FROM --platform=$BUILDPLATFORM docker.io/library/python:${PYTHON_VERSION}-slim-${DEBIAN_VERSION} AS requirements |
| 20 | + |
| 21 | +WORKDIR /synapse |
| 22 | + |
| 23 | +# Copy just what we need to run `poetry export`... |
| 24 | +COPY pyproject.toml poetry.lock /synapse/ |
| 25 | + |
| 26 | +# If specified, we won't verify the hashes of dependencies. |
| 27 | +# This is only needed if the hashes of dependencies cannot be checked for some |
| 28 | +# reason, such as when a git repository is used directly as a dependency. |
| 29 | +ARG TEST_ONLY_SKIP_DEP_HASH_VERIFICATION |
| 30 | + |
| 31 | +# If specified, we won't use the Poetry lockfile. |
| 32 | +# Instead, we'll just install what a regular `pip install` would from PyPI. |
| 33 | +ARG TEST_ONLY_IGNORE_POETRY_LOCKFILE |
| 34 | + |
| 35 | +# Export the dependencies, but only if we're actually going to use the Poetry lockfile. |
| 36 | +# Otherwise, just create an empty requirements file so that the Dockerfile can |
| 37 | +# proceed. |
| 38 | +ARG POETRY_VERSION |
| 39 | +RUN --mount=type=cache,target=/root/.cache/pip \ |
| 40 | + if [ -z "$TEST_ONLY_IGNORE_POETRY_LOCKFILE" ]; then \ |
| 41 | + pip install poetry==${POETRY_VERSION} poetry-plugin-export==1.9.0 && \ |
| 42 | + poetry export --extras all -o /synapse/requirements.txt ${TEST_ONLY_SKIP_DEP_HASH_VERIFICATION:+--without-hashes}; \ |
| 43 | + else \ |
| 44 | + touch /synapse/requirements.txt; \ |
| 45 | + fi |
| 46 | + |
| 47 | +### |
| 48 | +### Stage 1: builder |
| 49 | +### |
| 50 | +FROM docker.io/library/python:${PYTHON_VERSION}-${DEBIAN_VERSION} AS builder |
| 51 | + |
| 52 | +# Install rust and ensure its in the PATH |
| 53 | +ENV RUSTUP_HOME=/rust |
| 54 | +ENV CARGO_HOME=/cargo |
| 55 | +ENV PATH=/cargo/bin:/rust/bin:$PATH |
| 56 | +RUN mkdir /rust /cargo |
| 57 | + |
| 58 | +RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --default-toolchain stable --profile minimal |
| 59 | + |
| 60 | +# arm64 builds consume a lot of memory if `CARGO_NET_GIT_FETCH_WITH_CLI` is not |
| 61 | +# set to true, so we expose it as a build-arg. |
| 62 | +ARG CARGO_NET_GIT_FETCH_WITH_CLI=false |
| 63 | +ENV CARGO_NET_GIT_FETCH_WITH_CLI=$CARGO_NET_GIT_FETCH_WITH_CLI |
| 64 | + |
| 65 | +# To speed up rebuilds, install all of the dependencies before we copy over |
| 66 | +# the whole synapse project, so that this layer in the Docker cache can be |
| 67 | +# used while you develop on the source |
| 68 | +# |
| 69 | +# This is aiming at installing the `[tool.poetry.depdendencies]` from pyproject.toml. |
| 70 | +COPY --from=requirements /synapse/requirements.txt /synapse/ |
| 71 | +RUN --mount=type=cache,target=/root/.cache/pip \ |
| 72 | + pip install --prefix="/install" --no-deps -r /synapse/requirements.txt |
| 73 | + |
| 74 | +# Copy over the rest of the synapse source code. |
| 75 | +COPY synapse /synapse/synapse/ |
| 76 | +COPY rust /synapse/rust/ |
| 77 | +# ... and what we need to `pip install`. |
| 78 | +COPY pyproject.toml README.rst build_rust.py Cargo.toml Cargo.lock /synapse/ |
| 79 | + |
| 80 | +# Repeat of earlier build argument declaration, as this is a new build stage. |
| 81 | +ARG TEST_ONLY_IGNORE_POETRY_LOCKFILE |
| 82 | + |
| 83 | +# Install the synapse package itself. |
| 84 | +# If we have populated requirements.txt, we don't install any dependencies |
| 85 | +# as we should already have those from the previous `pip install` step. |
| 86 | +RUN \ |
| 87 | + --mount=type=cache,target=/root/.cache/pip \ |
| 88 | + --mount=type=cache,target=/synapse/target,sharing=locked \ |
| 89 | + --mount=type=cache,target=${CARGO_HOME}/registry,sharing=locked \ |
| 90 | + if [ -z "$TEST_ONLY_IGNORE_POETRY_LOCKFILE" ]; then \ |
| 91 | + pip install --prefix="/install" --no-deps /synapse[all]; \ |
| 92 | + else \ |
| 93 | + pip install --prefix="/install" /synapse[all]; \ |
| 94 | + fi |
| 95 | + |
| 96 | +### |
| 97 | +### Stage 2: runtime dependencies download for ARM64, AMD64, and RISCV64 |
| 98 | +### |
| 99 | +FROM --platform=$BUILDPLATFORM docker.io/library/debian:${DEBIAN_VERSION} AS runtime-deps |
| 100 | + |
| 101 | +# Tell apt to keep downloaded package files, as we're using cache mounts. |
| 102 | +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 |
| 103 | + |
| 104 | +# Add all target architectures |
| 105 | +RUN dpkg --add-architecture arm64 |
| 106 | +RUN dpkg --add-architecture amd64 |
| 107 | +RUN dpkg --add-architecture riscv64 |
| 108 | + |
| 109 | +# Fetch the runtime dependencies debs for all architectures |
| 110 | +# We do that by building a recursive list of packages we need to download with `apt-cache depends` |
| 111 | +# and then downloading them with `apt-get download`. |
| 112 | +RUN \ |
| 113 | + --mount=type=cache,target=/var/cache/apt,sharing=locked \ |
| 114 | + --mount=type=cache,target=/var/lib/apt,sharing=locked \ |
| 115 | + apt-get update -qq && \ |
| 116 | + apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances --no-pre-depends \ |
| 117 | + curl \ |
| 118 | + gosu \ |
| 119 | + libjpeg62-turbo \ |
| 120 | + libpq5 \ |
| 121 | + libwebp7 \ |
| 122 | + xmlsec1 \ |
| 123 | + libjemalloc2 \ |
| 124 | + | grep '^\w' > /tmp/pkg-list && \ |
| 125 | + for arch in arm64 amd64 riscv64; do \ |
| 126 | + mkdir -p /tmp/debs-${arch} && \ |
| 127 | + chown _apt:root /tmp/debs-${arch} && \ |
| 128 | + cd /tmp/debs-${arch} && \ |
| 129 | + apt-get -o APT::Architecture="${arch}" download $(cat /tmp/pkg-list); \ |
| 130 | + done |
| 131 | + |
| 132 | +# Extract the debs for each architecture |
| 133 | +RUN \ |
| 134 | + for arch in arm64 amd64 riscv64; do \ |
| 135 | + mkdir -p /install-${arch}/var/lib/dpkg/status.d/ && \ |
| 136 | + for deb in /tmp/debs-${arch}/*.deb; do \ |
| 137 | + package_name=$(dpkg-deb -I ${deb} | awk '/^ Package: .*$/ {print $2}'); \ |
| 138 | + echo "Extracting: ${package_name}"; \ |
| 139 | + dpkg --ctrl-tarfile $deb | tar -Ox ./control > /install-${arch}/var/lib/dpkg/status.d/${package_name}; \ |
| 140 | + dpkg --extract $deb /install-${arch}; \ |
| 141 | + done; \ |
| 142 | + done |
| 143 | + |
| 144 | + |
| 145 | +### |
| 146 | +### Stage 3: runtime |
| 147 | +### |
| 148 | + |
| 149 | +FROM docker.io/library/python:${PYTHON_VERSION}-slim-${DEBIAN_VERSION} |
| 150 | + |
| 151 | +ARG TARGETARCH |
| 152 | + |
| 153 | +LABEL org.opencontainers.image.url='https://github.com/element-hq/synapse' |
| 154 | +LABEL org.opencontainers.image.documentation='https://element-hq.github.io/synapse/latest/' |
| 155 | +LABEL org.opencontainers.image.source='https://github.com/element-hq/synapse.git' |
| 156 | +LABEL org.opencontainers.image.licenses='AGPL-3.0-or-later OR LicenseRef-Element-Commercial' |
| 157 | + |
| 158 | +COPY --from=runtime-deps /install-${TARGETARCH}/etc /etc |
| 159 | +COPY --from=runtime-deps /install-${TARGETARCH}/usr /usr |
| 160 | +COPY --from=runtime-deps /install-${TARGETARCH}/var /var |
| 161 | + |
| 162 | +# Copy the installed python packages from the builder stage. |
| 163 | +# |
| 164 | +# uv will generate a `.lock` file when installing packages, which we don't want |
| 165 | +# to copy to the final image. |
| 166 | +COPY --from=builder --exclude=.lock /install /usr/local |
| 167 | +COPY ./docker/start.py /start.py |
| 168 | +COPY ./docker/conf /conf |
| 169 | + |
| 170 | +EXPOSE 8008/tcp 8009/tcp 8448/tcp |
| 171 | + |
| 172 | +ENTRYPOINT ["/start.py"] |
| 173 | + |
| 174 | +HEALTHCHECK --start-period=5s --interval=15s --timeout=5s \ |
| 175 | + CMD curl -fSs http://localhost:8008/health || exit 1 |
0 commit comments