mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
CI builds were failing at random with:
buildx failed with: toomanyrequests: Rate exceeded
The 429 comes from public.ecr.aws, not Docker Hub. AWS caps unauthenticated
ECR Public pulls at 1 per second per source IP (authenticated: 10/s). The
build matrix starts 11 jobs at once and each resolves 4 base images, so
roughly 44 anonymous pulls land in a couple of seconds — from GitHub runner
IPs that are shared with every other GitHub customer.
Measured against public.ecr.aws with an anonymous token, 60 requests at
concurrency 30 returned 31x 429 in 0.48s.
The same probe against mirror.gcr.io (Google's Docker Hub pull-through
cache) returned zero errors: 750 manifest requests up to ~141 req/s, plus
120 layer blob requests at concurrency 60. Google publishes no rate limit
for it, so this is measured headroom, not a contract — but it is roughly
10x the pipeline's peak rate, and cached pulls do not count against Docker
Hub's limits either.
Authenticating to ECR Public was the alternative. It was rejected because
10 pulls/s is still under the ~44-pull burst, it needs an AWS account plus
a secret, and secrets never reach fork pull requests — so forks would keep
failing. The mirror fixes forks too.
Verified buildkit honours the mirror block by routing a build through a
local logging registry: all 6 requests (manifests and blobs) hit the mirror,
none went to Docker Hub directly. Confirmed mirror.gcr.io answers 200 for
buildkit's "?ns=docker.io" query form on all four images, for both GET and
HEAD. Confirmed buildkit falls back to Docker Hub when the mirror is
unreachable — the build still succeeds, but the resolve takes ~30s instead
of ~0.3s, so a mirror outage means slow builds, not broken ones. The
existing Docker Hub login covers that fallback on main-repo runs.
msitools.dockerfile is only used by the local `make docker-msi` target, but
is switched over too so no ECR Public reference is left behind.
185 lines
6.5 KiB
Docker
185 lines
6.5 KiB
Docker
FROM --platform=$BUILDPLATFORM ghcr.io/crazy-max/osxcross:14.5-debian AS osxcross
|
|
|
|
########################################################################################################################
|
|
### Build xx (original image: tonistiigi/xx)
|
|
FROM --platform=$BUILDPLATFORM alpine:3.20 AS xx-build
|
|
|
|
# v1.9.0
|
|
ENV XX_VERSION=a5592eab7a57895e8d385394ff12241bc65ecd50
|
|
|
|
RUN apk add -U --no-cache git
|
|
RUN git clone https://github.com/tonistiigi/xx && \
|
|
cd xx && \
|
|
git checkout ${XX_VERSION} && \
|
|
mkdir -p /out && \
|
|
cp src/xx-* /out/
|
|
|
|
RUN cd /out && \
|
|
ln -s xx-cc /out/xx-clang && \
|
|
ln -s xx-cc /out/xx-clang++ && \
|
|
ln -s xx-cc /out/xx-c++ && \
|
|
ln -s xx-apt /out/xx-apt-get
|
|
|
|
# xx mimics the original tonistiigi/xx image
|
|
FROM scratch AS xx
|
|
COPY --from=xx-build /out/ /usr/bin/
|
|
|
|
########################################################################################################################
|
|
### Build Navidrome UI
|
|
FROM --platform=$BUILDPLATFORM node:lts-alpine AS ui
|
|
WORKDIR /app
|
|
|
|
# Install node dependencies
|
|
COPY ui/package.json ui/package-lock.json ./
|
|
COPY ui/bin/ ./bin/
|
|
RUN npm ci
|
|
|
|
# Build bundle
|
|
COPY ui/ ./
|
|
RUN npm run build -- --outDir=/build
|
|
|
|
FROM scratch AS ui-bundle
|
|
COPY --from=ui /build /build
|
|
|
|
########################################################################################################################
|
|
### Build Navidrome binary for Docker image (dynamic musl, enables native libwebp via dlopen)
|
|
FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS build-alpine
|
|
COPY --from=xx / /
|
|
|
|
ARG TARGETPLATFORM
|
|
|
|
RUN apk add --no-cache clang lld file git
|
|
RUN xx-apk add --no-cache gcc musl-dev zlib-dev
|
|
RUN xx-verify --setup
|
|
|
|
WORKDIR /workspace
|
|
|
|
RUN --mount=type=bind,source=. \
|
|
--mount=type=cache,target=/root/.cache \
|
|
--mount=type=cache,target=/go/pkg/mod \
|
|
go mod download
|
|
|
|
ARG GIT_SHA
|
|
ARG GIT_TAG
|
|
|
|
RUN --mount=type=bind,source=. \
|
|
--mount=from=ui,source=/build,target=./ui/build,ro \
|
|
--mount=type=cache,target=/root/.cache \
|
|
--mount=type=cache,target=/go/pkg/mod <<EOT
|
|
set -e
|
|
xx-go --wrap
|
|
export CGO_ENABLED=1
|
|
BUILD_TAGS=$(./release/build-tags.sh)
|
|
# -latomic is required on 32-bit arm (arm/v6, arm/v7) so SQLite's 64-bit atomics resolve.
|
|
go build -tags="${BUILD_TAGS}" -ldflags="-w -s \
|
|
-linkmode=external -extldflags '-latomic' \
|
|
-X github.com/navidrome/navidrome/consts.gitSha=${GIT_SHA} \
|
|
-X github.com/navidrome/navidrome/consts.gitTag=${GIT_TAG}" \
|
|
-o /out/navidrome .
|
|
# Fail the build if native libwebp (purego) leaked into a 32-bit binary (issue #5738).
|
|
./release/verify-binary.sh /out/navidrome
|
|
# Fail the build if the binary is accidentally statically linked: dlopen (and
|
|
# therefore native libwebp detection) only works with a dynamic interpreter.
|
|
file /out/navidrome | grep -q "dynamically linked" || { echo "ERROR: /out/navidrome is not dynamically linked"; file /out/navidrome; exit 1; }
|
|
EOT
|
|
|
|
########################################################################################################################
|
|
### Build Navidrome binary for standalone distribution (static glibc, cross-compiled)
|
|
FROM --platform=$BUILDPLATFORM golang:1.26-trixie AS base
|
|
RUN apt-get update && apt-get install -y clang lld
|
|
COPY --from=xx / /
|
|
WORKDIR /workspace
|
|
|
|
FROM --platform=$BUILDPLATFORM base AS build
|
|
|
|
# Install build dependencies for the target platform
|
|
ARG TARGETPLATFORM
|
|
|
|
RUN xx-apt install -y binutils gcc g++ libc6-dev zlib1g-dev
|
|
RUN xx-verify --setup
|
|
|
|
RUN --mount=type=bind,source=. \
|
|
--mount=type=cache,target=/root/.cache \
|
|
--mount=type=cache,target=/go/pkg/mod \
|
|
go mod download
|
|
|
|
ARG GIT_SHA
|
|
ARG GIT_TAG
|
|
|
|
RUN --mount=type=bind,source=. \
|
|
--mount=from=ui,source=/build,target=./ui/build,ro \
|
|
--mount=from=osxcross,src=/osxcross/SDK,target=/xx-sdk,ro \
|
|
--mount=type=cache,target=/root/.cache \
|
|
--mount=type=cache,target=/go/pkg/mod <<EOT
|
|
set -e
|
|
|
|
# Setup CGO cross-compilation environment
|
|
xx-go --wrap
|
|
export CGO_ENABLED=1
|
|
cat "$(go env GOENV)" 2>/dev/null || true
|
|
|
|
# Only Darwin (macOS) requires clang (default), Windows requires gcc, everything else can use any compiler.
|
|
# So let's use gcc for everything except Darwin.
|
|
if [ "$(xx-info os)" != "darwin" ]; then
|
|
export CC=$(xx-info)-gcc
|
|
export CXX=$(xx-info)-g++
|
|
export LD_EXTRA="-extldflags '-static -latomic'"
|
|
fi
|
|
# GNU ld corrupts the R_ARM_IRELATIVE addends of libatomic's ifunc resolvers
|
|
# (wrong address, Thumb bit lost) once .text outgrows the 16MB Thumb branch
|
|
# range, making static arm binaries jump to garbage inside glibc's ifunc
|
|
# resolution and crash before main() (issue #5738). Link 32-bit arm with LLD,
|
|
# which emits correct addends.
|
|
if [ "$(xx-info arch)" = "arm" ]; then
|
|
export LD_EXTRA="-extldflags '-static -latomic -fuse-ld=lld'"
|
|
fi
|
|
if [ "$(xx-info os)" = "windows" ]; then
|
|
export EXT=".exe"
|
|
fi
|
|
|
|
BUILD_TAGS=$(./release/build-tags.sh)
|
|
go build -tags="${BUILD_TAGS}" -ldflags="${LD_EXTRA} -w -s \
|
|
-X github.com/navidrome/navidrome/consts.gitSha=${GIT_SHA} \
|
|
-X github.com/navidrome/navidrome/consts.gitTag=${GIT_TAG}" \
|
|
-o /out/navidrome${EXT} .
|
|
# Fail the build if native libwebp (purego) leaked into a 32-bit binary (issue #5738).
|
|
./release/verify-binary.sh /out/navidrome*
|
|
EOT
|
|
|
|
# Verify if the binary was built for the correct platform and it is statically linked
|
|
RUN xx-verify --static /out/navidrome*
|
|
|
|
FROM scratch AS binary
|
|
COPY --from=build /out /
|
|
|
|
########################################################################################################################
|
|
### Build Final Image
|
|
FROM alpine:3.20 AS final
|
|
LABEL maintainer="deluan@navidrome.org"
|
|
LABEL org.opencontainers.image.source="https://github.com/navidrome/navidrome"
|
|
|
|
# Install runtime dependencies
|
|
# - libwebp + symlinks: enables native WebP encoding via purego/dlopen
|
|
RUN apk add -U --no-cache ffmpeg mpv sqlite libwebp libwebpdemux libwebpmux && \
|
|
for lib in libwebp libwebpdemux libwebpmux; do \
|
|
target=$(ls /usr/lib/$lib.so.* 2>/dev/null | head -1) && \
|
|
[ -n "$target" ] && ln -sf "$target" /usr/lib/$lib.so; \
|
|
done
|
|
|
|
# Copy navidrome binary (musl build for Docker, enables native libwebp)
|
|
COPY --from=build-alpine /out/navidrome /app/
|
|
|
|
VOLUME ["/data", "/music"]
|
|
ENV ND_MUSICFOLDER=/music
|
|
ENV ND_DATAFOLDER=/data
|
|
ENV ND_CONFIGFILE=/data/navidrome.toml
|
|
ENV ND_PORT=4533
|
|
RUN touch /.nddockerenv
|
|
|
|
EXPOSE ${ND_PORT}
|
|
WORKDIR /app
|
|
ENV PATH="/app:${PATH}"
|
|
|
|
ENTRYPOINT ["/app/navidrome"]
|
|
|