Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Produce a distroless-based Docker image #18033

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ name: Build docker images

on:
push:
tags: ["v*"]
branches: [ master, main, develop ]
# TEMP
# tags: ["v*"]
# branches: [ master, main, develop ]
workflow_dispatch:

permissions:
Expand Down Expand Up @@ -44,12 +45,16 @@ jobs:

- name: Log in to DockerHub
uses: docker/login-action@v3
# TEMP
if: ${{ false }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Log in to GHCR
uses: docker/login-action@v3
# TEMP
if: ${{ false }}
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
Expand All @@ -74,7 +79,8 @@ jobs:
id: build-and-push
uses: docker/build-push-action@v6
with:
push: true
# TEMP
push: false
labels: |
gitsha1=${{ github.sha }}
org.opencontainers.image.version=${{ env.SYNAPSE_VERSION }}
Expand All @@ -88,6 +94,8 @@ jobs:
CARGO_NET_GIT_FETCH_WITH_CLI=true

- name: Sign the images with GitHub OIDC Token
# TEMP
if: ${{ false }}
env:
DIGEST: ${{ steps.build-and-push.outputs.digest }}
TAGS: ${{ steps.set-tag.outputs.tags }}
Expand Down
1 change: 1 addition & 0 deletions changelog.d/18033.docker
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Reduce size of the Docker image by using a base image based on [distroless](https://github.com/GoogleContainerTools/distroless).
158 changes: 74 additions & 84 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,44 +21,14 @@
# in `poetry export` in the past.

ARG PYTHON_VERSION=3.12
ARG POETRY_VERSION=1.8.3

###
### Stage 0: generate requirements.txt
###
# We hardcode the use of Debian bookworm here because this could change upstream
# and other Dockerfiles used for testing are expecting bookworm.
FROM docker.io/library/python:${PYTHON_VERSION}-slim-bookworm AS requirements

# RUN --mount is specific to buildkit and is documented at
# https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/syntax.md#build-mounts-run---mount.
# Here we use it to set up a cache for apt (and below for pip), to improve
# rebuild speeds on slow connections.
RUN \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update -qq && apt-get install -yqq \
build-essential curl git libffi-dev libssl-dev pkg-config \
&& rm -rf /var/lib/apt/lists/*

# Install rust and ensure its in the PATH.
# (Rust may be needed to compile `cryptography`---which is one of poetry's
# dependencies---on platforms that don't have a `cryptography` wheel.
ENV RUSTUP_HOME=/rust
ENV CARGO_HOME=/cargo
ENV PATH=/cargo/bin:/rust/bin:$PATH
RUN mkdir /rust /cargo

RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --default-toolchain stable --profile minimal

# arm64 builds consume a lot of memory if `CARGO_NET_GIT_FETCH_WITH_CLI` is not
# set to true, so we expose it as a build-arg.
ARG CARGO_NET_GIT_FETCH_WITH_CLI=false
ENV CARGO_NET_GIT_FETCH_WITH_CLI=$CARGO_NET_GIT_FETCH_WITH_CLI

# We install poetry in its own build stage to avoid its dependencies conflicting with
# synapse's dependencies.
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --user "poetry==1.3.2"
### This stage is platform-agnostic, so we can use the build platform in case of cross-compilation.
###
FROM --platform=$BUILDPLATFORM ghcr.io/astral-sh/uv:python${PYTHON_VERSION}-bookworm AS requirements

WORKDIR /synapse

Expand All @@ -78,38 +48,20 @@ ARG TEST_ONLY_IGNORE_POETRY_LOCKFILE
# Export the dependencies, but only if we're actually going to use the Poetry lockfile.
# Otherwise, just create an empty requirements file so that the Dockerfile can
# proceed.
RUN if [ -z "$TEST_ONLY_IGNORE_POETRY_LOCKFILE" ]; then \
/root/.local/bin/poetry export --extras all -o /synapse/requirements.txt ${TEST_ONLY_SKIP_DEP_HASH_VERIFICATION:+--without-hashes}; \
ARG POETRY_VERSION
RUN --mount=type=cache,target=/root/.cache/uv \
if [ -z "$TEST_ONLY_IGNORE_POETRY_LOCKFILE" ]; then \
uvx poetry@${POETRY_VERSION} export --extras all -o /synapse/requirements.txt ${TEST_ONLY_SKIP_DEP_HASH_VERIFICATION:+--without-hashes}; \
else \
touch /synapse/requirements.txt; \
touch /synapse/requirements.txt; \
fi

###
### Stage 1: builder
###
FROM docker.io/library/python:${PYTHON_VERSION}-slim-bookworm AS builder

# install the OS build deps
RUN \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update -qq && apt-get install -yqq \
build-essential \
libffi-dev \
libjpeg-dev \
libpq-dev \
libssl-dev \
libwebp-dev \
libxml++2.6-dev \
libxslt1-dev \
openssl \
zlib1g-dev \
git \
curl \
libicu-dev \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
FROM ghcr.io/astral-sh/uv:python${PYTHON_VERSION}-bookworm AS builder

ENV UV_LINK_MODE=copy

# Install rust and ensure its in the PATH
ENV RUSTUP_HOME=/rust
Expand All @@ -119,7 +71,6 @@ RUN mkdir /rust /cargo

RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --default-toolchain stable --profile minimal


# arm64 builds consume a lot of memory if `CARGO_NET_GIT_FETCH_WITH_CLI` is not
# set to true, so we expose it as a build-arg.
ARG CARGO_NET_GIT_FETCH_WITH_CLI=false
Expand All @@ -131,8 +82,8 @@ ENV CARGO_NET_GIT_FETCH_WITH_CLI=$CARGO_NET_GIT_FETCH_WITH_CLI
#
# This is aiming at installing the `[tool.poetry.depdendencies]` from pyproject.toml.
COPY --from=requirements /synapse/requirements.txt /synapse/
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --prefix="/install" --no-deps --no-warn-script-location -r /synapse/requirements.txt
RUN --mount=type=cache,target=/root/.cache/uv \
uv pip install --prefix="/install" --no-deps -r /synapse/requirements.txt

# Copy over the rest of the synapse source code.
COPY synapse /synapse/synapse/
Expand All @@ -146,41 +97,80 @@ ARG TEST_ONLY_IGNORE_POETRY_LOCKFILE
# Install the synapse package itself.
# If we have populated requirements.txt, we don't install any dependencies
# as we should already have those from the previous `pip install` step.
RUN --mount=type=cache,target=/synapse/target,sharing=locked \
RUN \
--mount=type=cache,target=/root/.cache/uv \
--mount=type=cache,target=/synapse/target,sharing=locked \
--mount=type=cache,target=${CARGO_HOME}/registry,sharing=locked \
if [ -z "$TEST_ONLY_IGNORE_POETRY_LOCKFILE" ]; then \
pip install --prefix="/install" --no-deps --no-warn-script-location /synapse[all]; \
uv pip install --prefix="/install" --no-deps /synapse[all]; \
else \
pip install --prefix="/install" --no-warn-script-location /synapse[all]; \
uv pip install --prefix="/install" /synapse[all]; \
fi

###
### Stage 2: runtime
## Stage 2: runtime dependencies download for ARM64 and AMD64
###
FROM --platform=$BUILDPLATFORM ghcr.io/astral-sh/uv:bookworm AS runtime-deps

FROM docker.io/library/python:${PYTHON_VERSION}-slim-bookworm
# 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

LABEL org.opencontainers.image.url='https://matrix.org/docs/projects/server/synapse'
LABEL org.opencontainers.image.documentation='https://github.com/element-hq/synapse/blob/master/docker/README.md'
LABEL org.opencontainers.image.source='https://github.com/element-hq/synapse.git'
LABEL org.opencontainers.image.licenses='AGPL-3.0-or-later'
# Add both target architectures
RUN dpkg --add-architecture arm64
RUN dpkg --add-architecture amd64

ARG PYTHON_VERSION

ENV UV_PYTHON_INSTALL_DIR=/tmp/uv-python-install
RUN uv python install \
cpython-${PYTHON_VERSION}-linux-aarch64-gnu \
cpython-${PYTHON_VERSION}-linux-x86_64_v2-gnu

RUN mkdir -p /install-amd64/usr /install-arm64/usr
RUN mv ${UV_PYTHON_INSTALL_DIR}/cpython-*-linux-aarch64-gnu/ /install-arm64/usr/local
RUN mv ${UV_PYTHON_INSTALL_DIR}/cpython-*-linux-x86_64_v2-gnu/ /install-amd64/usr/local

RUN \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update -qq && apt-get install -yqq \
curl \
gosu \
libjpeg62-turbo \
libpq5 \
libwebp7 \
xmlsec1 \
libjemalloc2 \
libicu72 \
libssl-dev \
openssl \
&& rm -rf /var/lib/apt/lists/*
apt-get update -qq && \
for arch in arm64 amd64; do \
mkdir -p /tmp/debs-${arch} && \
cd /tmp/debs-${arch} && \
apt-get download \
gosu:${arch} \
libjpeg62-turbo:${arch} \
libpq5:${arch} \
libwebp7:${arch} \
xmlsec1:${arch} \
libjemalloc2:${arch} \
libicu72:${arch} \
zlib1g:${arch} \
&& \
mkdir -p /install-${arch}/var/lib/dpkg/status.d/ && \
for deb in *.deb; do \
package_name=$(dpkg-deb -I ${deb} | awk '/^ Package: .*$/ {print $2}'); \
echo "Process: ${package_name}"; \
dpkg --ctrl-tarfile $deb | tar -Oxvf - ./control > /install-${arch}/var/lib/dpkg/status.d/${package_name}; \
dpkg --extract $deb /install-${arch} || exit 10; \
done \
done


###
### Stage 3: runtime
###

FROM gcr.io/distroless/cc-debian12:debug

ARG TARGETARCH

LABEL org.opencontainers.image.url='https://matrix.org/docs/projects/server/synapse'
LABEL org.opencontainers.image.documentation='https://github.com/element-hq/synapse/blob/master/docker/README.md'
LABEL org.opencontainers.image.source='https://github.com/element-hq/synapse.git'
LABEL org.opencontainers.image.licenses='AGPL-3.0-or-later'

COPY --from=runtime-deps /install-${TARGETARCH} /
COPY --from=builder /install /usr/local
COPY ./docker/start.py /start.py
COPY ./docker/conf /conf
Expand All @@ -190,4 +180,4 @@ EXPOSE 8008/tcp 8009/tcp 8448/tcp
ENTRYPOINT ["/start.py"]

HEALTHCHECK --start-period=5s --interval=15s --timeout=5s \
CMD curl -fSs http://localhost:8008/health || exit 1
CMD wget --no-verbose --tries=1 --spider http://localhost:8008/health
Loading
Loading