From 30b1ec9b2b543c7fe3e2c64cb04e5059bc56956c Mon Sep 17 00:00:00 2001 From: Alexander Tarasov Date: Thu, 15 Oct 2026 20:35:03 +0200 Subject: [PATCH 1/2] build(deps): Link OpenSSL and zlib statically into the binary Build rdkafka with ssl-vendored and libz-static, so the taskbroker binary only needs glibc at runtime. This lets it run on a glibc-only distroless base, like relay did in getsentry/relay#4978. The debian image runs the same binary, so it now uses OpenSSL 3.6 from openssl-src instead of the system libssl3. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 10 ++++++++++ Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 75c3392b..23db646b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2083,6 +2083,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "openssl-src" +version = "300.6.1+3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46eb8fb9fb3b61ce1c0f8a026c4c1a0714d3a9e138e7fbde78753ce2babc3846" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.116" @@ -2091,6 +2100,7 @@ checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] diff --git a/Cargo.toml b/Cargo.toml index b335ecc9..8ee3154e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ prost = "0.14" validator = { version = "0.20.0", features = ["derive"] } prost-types = "0.14" rand = "0.8.5" -rdkafka = { version = "0.37.0", features = ["cmake-build", "ssl"] } +rdkafka = { version = "0.37.0", features = ["cmake-build", "ssl-vendored", "libz-static"] } sentry_arroyo = { version = "=2.45.0", features = ["ssl"] } rmp-serde = "1.3" sentry = { version = "0.41.0", default-features = false, features = [ From 0fede703b862e6801aa450a2239323387f1d1100 Mon Sep 17 00:00:00 2001 From: Alexander Tarasov Date: Thu, 15 Oct 2026 20:35:09 +0200 Subject: [PATCH 2/2] feat(docker): Build a distroless image variant Add a taskbroker-distroless stage on the DHI static glibc Debian 13 base and publish it next to the current image as -distroless and nightly-distroless on GHCR, and -distroless in both production registries. The debian stage stays last, so builds without --target produce the same image as today. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/image.yml | 68 +++++++++++++++++++++++++++++-------- Dockerfile | 27 +++++++++++++-- 2 files changed, 78 insertions(+), 17 deletions(-) diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index 35d4b6a4..a9ee29d2 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -9,12 +9,27 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: + platform: [amd64, arm64] + variant: [default, distroless] include: - - os: ubuntu-24.04 - platform: amd64 - - os: ubuntu-24.04-arm - platform: arm64 - name: build-${{ matrix.platform }} + - platform: amd64 + os: ubuntu-24.04 + - platform: arm64 + os: ubuntu-24.04-arm + - platform: amd64 + variant: default + cache_to: type=gha,mode=max,scope=taskbroker-amd64 + - platform: arm64 + variant: default + cache_to: type=gha,mode=max,scope=taskbroker-arm64 + - variant: default + target: taskbroker + suffix: '' + - variant: distroless + target: taskbroker-distroless + suffix: '-distroless' + cache_to: '' + name: build-${{ matrix.platform }}${{ matrix.suffix }} permissions: contents: read packages: write @@ -27,22 +42,28 @@ jobs: image_name: 'taskbroker' platforms: linux/${{ matrix.platform }} dockerfile_path: './Dockerfile' + build_target: ${{ matrix.target }} build_args: TASKBROKER_GIT_REVISION=${{ github.sha }} ghcr: ${{ github.repository == 'getsentry/taskbroker' }} - tag_suffix: -${{ matrix.platform }} + tag_suffix: ${{ matrix.suffix }}-${{ matrix.platform }} publish_on_pr: ${{ github.event.pull_request.author_association == 'OWNER' || github.event.pull_request.author_association == 'MEMBER' }} google_ar: false tag_nightly: false tag_latest: false # One cache per architecture, so the amd64 and arm64 builds don't - # overwrite each other's cache index. + # overwrite each other's cache index. Both variants share the build + # stage, so only the default variant exports it. cache_from: type=gha,scope=taskbroker-${{ matrix.platform }} - cache_to: type=gha,mode=max,scope=taskbroker-${{ matrix.platform }} + cache_to: ${{ matrix.cache_to }} assemble-taskbroker-image: runs-on: ubuntu-latest needs: [build] if: ${{ github.repository == 'getsentry/taskbroker' && (github.ref_name == 'main' || startsWith(github.ref_name, 'release/')) && github.event_name != 'pull_request' }} + strategy: + matrix: + suffix: ['', '-distroless'] + name: assemble-taskbroker-image${{ matrix.suffix }} permissions: contents: read packages: write @@ -59,22 +80,32 @@ jobs: - name: Create multiplatform manifests run: | docker buildx imagetools create \ - --tag ghcr.io/getsentry/taskbroker:${{ github.event.pull_request.head.sha || github.sha }} \ - --tag ghcr.io/getsentry/taskbroker:nightly \ - ghcr.io/getsentry/taskbroker:${{ github.event.pull_request.head.sha || github.sha }}-amd64 \ - ghcr.io/getsentry/taskbroker:${{ github.event.pull_request.head.sha || github.sha }}-arm64 + --tag ghcr.io/getsentry/taskbroker:${{ github.event.pull_request.head.sha || github.sha }}${{ matrix.suffix }} \ + --tag ghcr.io/getsentry/taskbroker:nightly${{ matrix.suffix }} \ + ghcr.io/getsentry/taskbroker:${{ github.event.pull_request.head.sha || github.sha }}${{ matrix.suffix }}-amd64 \ + ghcr.io/getsentry/taskbroker:${{ github.event.pull_request.head.sha || github.sha }}${{ matrix.suffix }}-arm64 build-production: runs-on: ubuntu-24.04 if: ${{ github.repository == 'getsentry/taskbroker' && github.ref_name == github.event.repository.default_branch }} - name: Build and push production images to ${{ matrix.registry }} registry + name: Build and push production${{ matrix.suffix }} images to ${{ matrix.registry }} registry strategy: matrix: + registry: [single-region, multi-region] + variant: [default, distroless] include: - registry: single-region image: us-central1-docker.pkg.dev/sentryio/taskbroker/image - registry: multi-region image: us-docker.pkg.dev/sentryio/taskbroker-mr/image + - variant: default + target: taskbroker + suffix: '' + tag_latest: true + - variant: distroless + target: taskbroker-distroless + suffix: '-distroless' + tag_latest: false permissions: contents: read id-token: write @@ -87,11 +118,13 @@ jobs: image_name: 'taskbroker' platforms: linux/amd64 dockerfile_path: './Dockerfile' + build_target: ${{ matrix.target }} build_args: TASKBROKER_GIT_REVISION=${{ github.sha }} ghcr: false google_ar: true + tag_suffix: ${{ matrix.suffix }} tag_nightly: false - tag_latest: true + tag_latest: ${{ matrix.tag_latest }} google_ar_image_name: ${{ matrix.image }} google_workload_identity_provider: projects/868781662168/locations/global/workloadIdentityPools/prod-github/providers/github-oidc-pool google_service_account: gha-taskbroker-push@sac-prod-sa.iam.gserviceaccount.com @@ -105,11 +138,16 @@ jobs: needs: [assemble-taskbroker-image] runs-on: ubuntu-latest timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + suffix: ['', '-distroless'] + name: self-hosted-end-to-end${{ matrix.suffix }} steps: - name: Run Sentry self-hosted e2e CI uses: getsentry/self-hosted@master with: project_name: taskbroker - image_url: ghcr.io/getsentry/taskbroker:${{ github.sha }} + image_url: ghcr.io/getsentry/taskbroker:${{ github.sha }}${{ matrix.suffix }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/Dockerfile b/Dockerfile index d6ebe91c..2a61c274 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,8 +34,31 @@ ENV TASKBROKER_VERSION=$TASKBROKER_GIT_REVISION RUN echo "${TASKBROKER_VERSION}" > ./VERSION -# Runtime image -FROM debian:trixie-slim +# /opt for the distroless image. The base has no shell to mkdir or chown, and it +# already has a root-owned /opt, so we prepare the tree here and copy it onto /. +# The directories are owned by nonroot (uid 65532) so the default SQLite path, +# relative to /opt, is writable. +FROM build AS distroless-opt + +RUN mkdir -p /distroless/opt/sqlite \ + && cp ./VERSION ./target/release/taskbroker /distroless/opt/ \ + && chown 65532:65532 /distroless/opt /distroless/opt/sqlite + +# Distroless runtime: no shell or package manager. OpenSSL and zlib are linked +# statically and SQLite is bundled, so the binary only needs glibc. The base +# runs as nonroot (uid 65532) and ships the CA bundle. +FROM us-docker.pkg.dev/sentryio/dhi-mirror/static:20250419-glibc-debian13 AS taskbroker-distroless + +EXPOSE 50051 + +COPY --from=distroless-opt /distroless/ / + +WORKDIR /opt + +CMD ["/opt/taskbroker"] + +# Debian runtime. Keep it the last stage: builds without --target produce this image. +FROM debian:trixie-slim AS taskbroker RUN groupadd taskbroker --gid 1000 && useradd --gid taskbroker --uid 1000 taskbroker