mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
## Problem
A fresh `deploy/compose` install never starts. The relay exits during
config
validation and crash-loops under `restart: unless-stopped`:
```
Error: Configuration error: invalid config: BUZZ_GIT_PACK_CACHE_PATH=/data/git/.pack-cache could not be created: Permission denied (os error 13)
```
## Cause
The image runs as `buzz:buzz`. Docker seeds a volume's ownership from
the image
**only when the mount point already exists there** — otherwise it
creates the
mount point as `root:root`. `compose.yml` mounts `buzz-git-data` at
`/data/git`,
which the image doesn't create, so the relay can't write the pack cache.
`USER buzz:buzz` and `BUZZ_GIT_REPO_PATH=/data/git` arrived in the same
commit
(426497a), so there was never a working state to regress from.
The other deployment paths are unaffected, which is probably why this
went
unnoticed: the code default (`./repos`, under the `buzz`-owned home)
works
unmounted, and the Helm chart gets volume ownership from `fsGroup:
65532`.
## Fix
Create `/data/git` as `buzz:buzz` in the image, the way the official
`postgres`
image prepares `$PGDATA`.
`compose.yml`, `run.sh` and the volume layout are untouched, so existing
deployments need no migration and the `run.sh backup-hint` output stays
accurate.
## Testing
`ghcr.io/block/buzz@sha256:ec96e029` on arm64 with this change applied
as a
derived layer, running **unmodified** `deploy/compose/compose.yml`
against clean
volumes:
| Image | `/data/git` owner | Result |
|---|---|---|
| upstream | `root:root` | `Permission denied` — crash loop |
| patched | `buzz:buzz` | relay healthy |
```
relay health: healthy
/data/git = buzz:buzz (contains .pack-cache)
/_readiness -> 200
WS upgrade -> 101
```
Relay log shows migrations applied, the A3 git object-store conformance
probe
passing, and `buzz-relay TCP listening`.
I have not run `just ci` / `just test` — this touches no Rust, JS, or
mobile
code. Happy to run them if you'd like.
## Alternatives
- A `git-init` one-shot in `compose.yml` that chowns the volume. Works,
but adds
a container to every `up` and doesn't help `docker run` or Kubernetes
without
an `fsGroup`.
- Moving `BUZZ_GIT_REPO_PATH` to the code default `/var/lib/buzz/repos`
and
pre-creating that instead. Arguably tidier, since `/data/git` then only
lives
in the compose bundle — but it moves the volume mount point, so existing
installs would have to relocate their git data. Happy to do it that way
if you
prefer; it seemed the wrong trade for a bug fix.
Signed-off-by: Onno Klein Hofmeijer <onnokh@hotmail.com>
160 lines
7.6 KiB
Docker
160 lines
7.6 KiB
Docker
# syntax=docker/dockerfile:1.7
|
|
#
|
|
# Public Buzz relay image — published as ghcr.io/block/buzz:<tag>.
|
|
#
|
|
# Builds the `buzz-relay` binary (Rust 1.95) and the `buzz-web` static bundle
|
|
# (pnpm + vite), then assembles them into a small debian-slim runtime with
|
|
# `git` available (the relay shells out to git for repo hydrate / receive-pack
|
|
# / upload-pack — see crates/buzz-relay/src/api/git).
|
|
#
|
|
# Multi-arch is handled by running this same Dockerfile on native amd64 and
|
|
# native arm64 runners (see .github/workflows/docker.yml). The Dockerfile
|
|
# itself is platform-agnostic; do not add --platform pins.
|
|
|
|
ARG RUST_VERSION=1.95
|
|
ARG NODE_VERSION=24
|
|
ARG DEBIAN_VERSION=bookworm
|
|
|
|
# Optional extra CA bundle for builds behind a TLS-intercepting corporate proxy
|
|
# (e.g. a Cloudflare/Zscaler gateway that re-signs TLS). Empty by default, so
|
|
# public CI builds are unaffected. Point it at a PEM file in the build context:
|
|
# docker build --build-arg EXTRA_CA_CERTS=path/to/proxy-ca.pem ...
|
|
# Consumed by the network-touching stages below (cargo + pnpm).
|
|
ARG EXTRA_CA_CERTS=
|
|
|
|
# Optional npm registry for builds where the public registry is unreachable or
|
|
# policy-blocked (e.g. a corporate mirror / Artifactory). Empty default = public
|
|
# npmjs, so public CI builds are unaffected. Consumed by the web-builder stage.
|
|
ARG NPM_REGISTRY=
|
|
|
|
# ─── Stage 1: cargo-chef base ───────────────────────────────────────────────
|
|
FROM rust:${RUST_VERSION}-${DEBIAN_VERSION} AS chef
|
|
# Trust an optional corporate-proxy CA before any network fetch (no-op if unset).
|
|
ARG EXTRA_CA_CERTS
|
|
COPY --chmod=0644 ${EXTRA_CA_CERTS:-Dockerfile} /tmp/extra-ca/src
|
|
RUN if [ -n "${EXTRA_CA_CERTS}" ]; then \
|
|
cp /tmp/extra-ca/src /usr/local/share/ca-certificates/extra-proxy-ca.crt \
|
|
&& update-ca-certificates \
|
|
&& echo "CARGO_HTTP_CAINFO=/etc/ssl/certs/ca-certificates.crt" >> /etc/environment; \
|
|
fi
|
|
ENV CARGO_HTTP_CAINFO=/etc/ssl/certs/ca-certificates.crt
|
|
RUN cargo install cargo-chef --locked --version 0.1.71
|
|
WORKDIR /build
|
|
|
|
# ─── Stage 2: plan dependency graph ─────────────────────────────────────────
|
|
# Only the manifests are needed to compute the recipe; this layer rebuilds
|
|
# only when Cargo.{toml,lock} or crate manifests change, not on every source
|
|
# edit.
|
|
FROM chef AS planner
|
|
COPY . .
|
|
RUN cargo chef prepare --recipe-path recipe.json
|
|
|
|
# ─── Stage 3: cook dependencies, then build the binary ──────────────────────
|
|
FROM chef AS builder
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends \
|
|
build-essential \
|
|
pkg-config \
|
|
libssl-dev \
|
|
ca-certificates \
|
|
git \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
COPY --from=planner /build/recipe.json recipe.json
|
|
# Cook the full workspace recipe — relay deps include workspace siblings, so
|
|
# scoping to -p buzz-relay misses transitive deps and re-builds them later.
|
|
RUN cargo chef cook --release --recipe-path recipe.json
|
|
COPY . .
|
|
RUN cargo build --release --locked -p buzz-relay --bin buzz-relay \
|
|
-p buzz-admin --bin buzz-admin \
|
|
-p buzz-pair-relay --bin buzz-pair-relay \
|
|
&& strip target/release/buzz-relay \
|
|
&& strip target/release/buzz-admin \
|
|
&& strip target/release/buzz-pair-relay
|
|
|
|
# ─── Stage 4: web bundle (pnpm + vite) ──────────────────────────────────────
|
|
# Independent of the Rust layers so a CSS change doesn't bust Rust cache and
|
|
# vice versa.
|
|
FROM node:${NODE_VERSION}-${DEBIAN_VERSION}-slim AS web-builder
|
|
WORKDIR /build
|
|
# Trust an optional corporate-proxy CA so corepack + pnpm can fetch over an
|
|
# intercepting TLS gateway (no-op if EXTRA_CA_CERTS is unset).
|
|
ARG EXTRA_CA_CERTS
|
|
COPY --chmod=0644 ${EXTRA_CA_CERTS:-Dockerfile} /tmp/extra-ca/src
|
|
RUN if [ -n "${EXTRA_CA_CERTS}" ]; then \
|
|
apt-get update && apt-get install -y --no-install-recommends ca-certificates \
|
|
&& cp /tmp/extra-ca/src /usr/local/share/ca-certificates/extra-proxy-ca.crt \
|
|
&& update-ca-certificates \
|
|
&& rm -rf /var/lib/apt/lists/*; \
|
|
fi
|
|
ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
|
|
# Point npm + corepack at an optional mirror (no-op when NPM_REGISTRY is unset).
|
|
# corepack reads COREPACK_NPM_REGISTRY to fetch the pinned pnpm; pnpm/npm read
|
|
# the .npmrc registry for dependency installs.
|
|
ARG NPM_REGISTRY
|
|
ENV COREPACK_NPM_REGISTRY=${NPM_REGISTRY}
|
|
# When using a mirror, disable corepack's npmjs signature check: the mirror
|
|
# republishes tarballs without the public registry's provenance signatures, so
|
|
# strict verification fails ("No compatible signature found"). Only relaxed on
|
|
# the mirror path — public builds (NPM_REGISTRY unset) keep strict verification.
|
|
RUN if [ -n "${NPM_REGISTRY}" ]; then \
|
|
echo "registry=${NPM_REGISTRY}" > /build/.npmrc \
|
|
&& echo "COREPACK_INTEGRITY_KEYS=0" >> /etc/environment; \
|
|
fi
|
|
ENV COREPACK_INTEGRITY_KEYS=${NPM_REGISTRY:+0}
|
|
RUN corepack enable
|
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
COPY patches/ patches/
|
|
COPY web/package.json web/
|
|
COPY admin-web/package.json admin-web/
|
|
RUN pnpm install --frozen-lockfile --filter buzz-web --filter buzz-admin-web
|
|
COPY web/ web/
|
|
COPY admin-web/ admin-web/
|
|
RUN pnpm -C web build && pnpm -C admin-web build
|
|
|
|
# ─── Stage 5: runtime ───────────────────────────────────────────────────────
|
|
FROM debian:${DEBIAN_VERSION}-slim AS runtime
|
|
|
|
# OCI annotations: required for GHCR to auto-link the image to this repo and
|
|
# inherit its visibility. org.opencontainers.image.source is the load-bearing
|
|
# one — without it GHCR keeps the image private even when the repo is public.
|
|
LABEL org.opencontainers.image.title="Buzz" \
|
|
org.opencontainers.image.description="WebSocket relay server for the Buzz communications platform" \
|
|
org.opencontainers.image.source="https://github.com/block/buzz" \
|
|
org.opencontainers.image.url="https://github.com/block/buzz" \
|
|
org.opencontainers.image.documentation="https://github.com/block/buzz#readme" \
|
|
org.opencontainers.image.licenses="Apache-2.0"
|
|
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends \
|
|
ca-certificates \
|
|
curl \
|
|
git \
|
|
openssl \
|
|
&& rm -rf /var/lib/apt/lists/* \
|
|
&& groupadd --system --gid 1000 buzz \
|
|
&& useradd --system --uid 1000 --gid 1000 --home-dir /var/lib/buzz \
|
|
--create-home --shell /usr/sbin/nologin buzz
|
|
|
|
COPY --from=builder /build/target/release/buzz-relay /usr/local/bin/buzz-relay
|
|
COPY --from=builder /build/target/release/buzz-admin /usr/local/bin/buzz-admin
|
|
COPY --from=builder /build/target/release/buzz-pair-relay /usr/local/bin/buzz-pair-relay
|
|
COPY --from=web-builder /build/web/dist /srv/buzz/web
|
|
COPY --from=web-builder /build/admin-web/dist /srv/buzz/admin-web
|
|
|
|
# The invite landing page is always served from the bundled web UI. Repository
|
|
# browser routes require the separate BUZZ_SERVE_GIT_WEB_GUI=true opt-in. The
|
|
# admin bundle is inert until BUZZ_ADMIN_HOST is configured.
|
|
ENV BUZZ_WEB_DIR=/srv/buzz/web \
|
|
BUZZ_ADMIN_WEB_DIR=/srv/buzz/admin-web
|
|
|
|
# 3000: app (WS + REST) · 8080: /_liveness, /_readiness · 9102: /metrics
|
|
EXPOSE 3000 8080 9102
|
|
|
|
# deploy/compose mounts a volume here; pre-created so it inherits buzz:buzz.
|
|
RUN mkdir -p /data/git && chown buzz:buzz /data/git
|
|
|
|
USER buzz:buzz
|
|
WORKDIR /var/lib/buzz
|
|
|
|
ENTRYPOINT ["/usr/local/bin/buzz-relay"]
|