From bd2b270deeee40f8fe6af786777c7ab929b83eae Mon Sep 17 00:00:00 2001 From: jx12n Date: Fri, 19 Jun 2026 21:34:06 -0600 Subject: [PATCH] build(standalone): committed multi-arch pullable standalone dev image path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consumers (e.g. thepeach) need a PULLABLE tidaldb image — not every dev has the source for a compose `build:` context. Publishes registry.threesix.ai/tidal/standalone as a manifest list: linux/amd64 (Linux/CI) + linux/arm64 (Apple-silicon Macs), defaulting to STANDALONE mode (:9400, --data-dir /data) so it drops into a consumer's docker-compose like postgres. Hybrid build because `rustc` SIGSEGVs under QEMU (an in-container amd64 cross-build on an arm64 host fails): - amd64: docker/standalone/amd64.Dockerfile packages the HOST cross-compiled x86_64 binary (the proven build-release path; trixie-slim for glibc 2.41/libmvec) — apt+COPY only. - arm64: native in-container build from docker/standalone/Dockerfile. - scripts/build-standalone-image.sh stitches both into one manifest list (:m12 + :latest). Verified: pulled :m12 (arm64), booted standalone, GET /health -> {ok:true,mode:standalone}. --- docker/standalone/amd64.Dockerfile | 40 ++++++++++++++++ scripts/build-standalone-image.sh | 73 ++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 docker/standalone/amd64.Dockerfile create mode 100755 scripts/build-standalone-image.sh diff --git a/docker/standalone/amd64.Dockerfile b/docker/standalone/amd64.Dockerfile new file mode 100644 index 0000000..b1fcd98 --- /dev/null +++ b/docker/standalone/amd64.Dockerfile @@ -0,0 +1,40 @@ +# amd64 leg of the multi-arch standalone image — PACKAGING ONLY (no in-container +# compile). `rustc` SIGSEGVs under QEMU emulation, so we do NOT cross-build amd64 +# inside a container on an arm64 host; instead the linux/amd64 binary is the HOST +# cross-compile produced by scripts/build-release.sh (x86_64-unknown-linux-gnu, +# homebrew GCC -> glibc 2.41), packaged here. trixie-slim because that glibc and +# libmvec.so.1 are absent on bookworm. (The arm64 leg builds natively from +# docker/standalone/Dockerfile; scripts/build-standalone-image.sh stitches both +# into one manifest list.) +# +# Build context = a staging dir holding the prebuilt `tidal-server` binary + the +# `config/` dir (the script assembles it); NOT the repo root. +FROM debian:trixie-slim +ARG DEBIAN_FRONTEND=noninteractive + +# libstdc++6/libgcc-s1 for the usearch HNSW C++ + aws-lc runtime; ca-certificates +# for TLS; curl for the healthcheck. +RUN apt-get update && apt-get install -y --no-install-recommends \ + libstdc++6 libgcc-s1 ca-certificates curl && \ + rm -rf /var/lib/apt/lists/* && \ + useradd --system --home /srv tidal && \ + mkdir -p /data && chown tidal:tidal /data + +COPY tidal-server /usr/local/bin/tidal-server +COPY --chown=tidal:tidal config /etc/tidal-server + +ENV TIDAL_CONFIG=/etc/tidal-server +VOLUME ["/data"] +USER tidal +WORKDIR /srv +EXPOSE 9400 9091 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ + CMD curl -f -H "Authorization: Bearer ${TIDAL_API_KEY:-}" http://localhost:9400/health || exit 1 + +# Match the in-container standalone Dockerfile: bare-binary ENTRYPOINT + standalone CMD. +ENTRYPOINT ["tidal-server"] +CMD ["standalone", \ + "--listen", "0.0.0.0:9400", \ + "--data-dir", "/data", \ + "--metrics", "0.0.0.0:9091"] diff --git a/scripts/build-standalone-image.sh b/scripts/build-standalone-image.sh new file mode 100755 index 0000000..3270d04 --- /dev/null +++ b/scripts/build-standalone-image.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# build-standalone-image.sh — the committed, repeatable build path for the +# PULLABLE, MULTI-ARCH standalone dev image. +# +# ./scripts/build-standalone-image.sh [tag] # default tag: m12 (+ also tags :latest) +# +# WHY THIS IS SEPARATE FROM build-release.sh: +# build-release.sh produces the PRODUCTION images (server/dr/stress), amd64-ONLY, +# defaulting to CLUSTER mode — they will not run natively on an arm64 dev Mac. +# This publishes a manifest list with BOTH linux/amd64 (Linux devs / CI) and +# linux/arm64 (Apple-silicon Macs), defaulting to STANDALONE mode, so a consumer +# app (e.g. thepeach) can pull it as a docker-compose dependency like postgres — +# no source checkout required. +# +# WHY THE HYBRID (not a single `buildx --platform amd64,arm64`): +# `rustc` SIGSEGVs under QEMU emulation, so an in-container amd64 cross-build on +# an arm64 host fails. We therefore: +# - amd64: package the HOST cross-compiled x86_64 binary (the proven build-release +# path) via docker/standalone/amd64.Dockerfile — apt + COPY only, no compile. +# - arm64: build natively in-container from docker/standalone/Dockerfile. +# - stitch both into a manifest list with `imagetools create`. +# +# Registry creds come from the Docker daemon's existing login (zot accepts anonymous +# push for this registry); no secret is embedded. +set -euo pipefail + +TAG="${1:-m12}" +REGISTRY="${TIDAL_REGISTRY:-registry.threesix.ai/tidal}" +IMAGE="${REGISTRY}/standalone" +BUILDER="${TIDAL_BUILDX_BUILDER:-amd64builder}" +TARGET="x86_64-unknown-linux-gnu" + +die() { echo "build-standalone-image: $*" >&2; exit 1; } +command -v docker >/dev/null || die "docker not on PATH" +docker buildx inspect "$BUILDER" >/dev/null 2>&1 \ + || die "buildx builder '$BUILDER' not found (docker buildx create --name $BUILDER --driver docker-container --use)" + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$REPO_ROOT" +echo "==> multi-arch standalone image ${IMAGE}:${TAG} (+ :latest), source $(git rev-parse --short HEAD 2>/dev/null || echo unknown)" + +# 1. amd64 binary: HOST cross-compile if absent (QEMU rustc segfaults, so never in-container). +BIN="target/${TARGET}/release/tidal-server" +if [ ! -x "$BIN" ]; then + command -v "${TARGET}-gcc" >/dev/null || die "missing ${TARGET}-gcc (brew install ${TARGET}); needed to cross-compile the amd64 binary" + rustup target list --installed 2>/dev/null | grep -qx "$TARGET" || die "rust target $TARGET not installed (rustup target add $TARGET)" + echo "==> amd64 binary absent — host cross-compiling -p tidal-server for $TARGET" + export CC_x86_64_unknown_linux_gnu="${TARGET}-gcc" \ + CXX_x86_64_unknown_linux_gnu="${TARGET}-g++" \ + AR_x86_64_unknown_linux_gnu="${TARGET}-ar" \ + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER="${TARGET}-gcc" \ + PROTOC="${PROTOC:-$(command -v protoc || true)}" + [ -n "$PROTOC" ] || die "protoc not found (brew install protobuf), or set PROTOC" + cargo build -p tidal-server --release --target "$TARGET" --locked +fi + +# 2. amd64 image: package the prebuilt binary (no compile -> reliable under QEMU). +STAGE="tmp/standalone-amd64"; rm -rf "$STAGE"; mkdir -p "$STAGE" +cp "$BIN" "$STAGE/tidal-server" +cp -R tidal-server/config "$STAGE/config" +cp docker/standalone/amd64.Dockerfile "$STAGE/Dockerfile" +echo "==> building+pushing ${IMAGE}:${TAG}-amd64 (packaging)" +docker buildx build --builder "$BUILDER" --platform linux/amd64 -t "${IMAGE}:${TAG}-amd64" --push "$STAGE" + +# 3. arm64 image: native in-container build. +echo "==> building+pushing ${IMAGE}:${TAG}-arm64 (native in-container)" +docker buildx build --builder "$BUILDER" --platform linux/arm64 -f docker/standalone/Dockerfile -t "${IMAGE}:${TAG}-arm64" --push . + +# 4. stitch the two single-arch tags into one manifest list. +echo "==> assembling manifest list ${IMAGE}:${TAG} + :latest" +docker buildx imagetools create -t "${IMAGE}:${TAG}" -t "${IMAGE}:latest" "${IMAGE}:${TAG}-amd64" "${IMAGE}:${TAG}-arm64" + +echo "==> published. Manifest list arches:" +docker buildx imagetools inspect "${IMAGE}:${TAG}" 2>/dev/null | grep -E 'Name:|Platform:' || true