mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-11-11 11:57:57 +00:00
Building the image natively on an arm64 machine (a Raspberry Pi 4 or 5 on 64-bit OS, Apple silicon) fails at the first `RUN` of the build stage: ``` => ERROR [builder 2/40] RUN apk add --no-cache curl xz && ... 0.841 exec /bin/sh: exec format error ``` **Cause:** the Dockerfile declares `ARG BUILDPLATFORM=linux/amd64`. A default on that ARG overrides the value BuildKit sets for the machine doing the build, so an arm64 host pulls the amd64 `golang` image and can't run it. CI didn't catch it because its arm64 image is cross-built on an amd64 runner, where `linux/amd64` was correct anyway. **Fix:** declare `ARG BUILDPLATFORM` without a default. BuildKit, the default builder since Docker 23 and what buildx uses, fills it in on every host. amd64 builds and the CI cross-build are unchanged. **Workaround on current master:** `docker build --build-arg BUILDPLATFORM=linux/arm64 .` **Test:** `tests/unit/test-dockerfile-buildplatform.js` fails on the old Dockerfile and passes on this one. It checks three things: - `BUILDPLATFORM` has no default. - The Go builder stays pinned to `$BUILDPLATFORM`. - No stage hard-codes a platform. It's wired into `test-all.sh`. Found while building the image on a Raspberry Pi running Raspberry Pi OS Trixie (arm64) with Docker 29. --- Co-authored-by: Claude <noreply@anthropic.com>
187 lines
8.1 KiB
Docker
187 lines
8.1 KiB
Docker
# syntax=docker/dockerfile:1
|
|
# Build stage always runs natively on the builder's arch ($BUILDPLATFORM) and
|
|
# cross-compiles to $TARGETOS/$TARGETARCH. No QEMU for compilation.
|
|
#
|
|
# The SQLite driver is github.com/mattn/go-sqlite3, which is cgo, so the Go
|
|
# toolchain alone can no longer cross-compile this: it needs a C compiler that
|
|
# can target the other architecture. `zig cc` is that compiler. Targeting musl
|
|
# makes the result fully static (see -extldflags below), so the runtime stage
|
|
# has no libc dependency on the base image at all.
|
|
#
|
|
# BUILDPLATFORM is set automatically by BuildKit (the default builder since
|
|
# Docker 23, and what buildx uses) to the machine doing the build. Declare it
|
|
# without a default: a default here overrides BuildKit's value, so a native
|
|
# build on an arm64 host (a Raspberry Pi, Apple silicon) pulled the amd64 Go
|
|
# image and failed with "exec format error" at the first RUN.
|
|
ARG BUILDPLATFORM
|
|
FROM --platform=$BUILDPLATFORM golang:1.27-alpine AS builder
|
|
|
|
ARG APP_VERSION=unknown
|
|
ARG GIT_COMMIT=unknown
|
|
ARG BUILD_TIME=unknown
|
|
# Provided by buildx for multi-arch builds
|
|
ARG TARGETOS
|
|
ARG TARGETARCH
|
|
|
|
# Keep these in step with the Makefile: netgo/osusergo preserve the pure-Go
|
|
# resolver and user lookup the binaries had under CGO_ENABLED=0, and
|
|
# sqlite_omit_load_extension drops the dlopen path so -static links cleanly.
|
|
ENV GO_BUILD_TAGS=netgo,osusergo,sqlite_omit_load_extension \
|
|
ZIG_GLOBAL_CACHE_DIR=/tmp/zig-cache
|
|
|
|
# Pinned zig, checksum-verified. Arch comes from `uname -m` rather than
|
|
# TARGETARCH because this stage is pinned to BUILDPLATFORM — it has to work when
|
|
# someone builds on an arm64 machine too.
|
|
ARG ZIG_VERSION=0.16.0
|
|
RUN apk add --no-cache curl xz && \
|
|
case "$(uname -m)" in \
|
|
x86_64) ZA=x86_64; ZSHA=70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00 ;; \
|
|
aarch64) ZA=aarch64; ZSHA=ea4b09bfb22ec6f6c6ceac57ab63efb6b46e17ab08d21f69f3a48b38e1534f17 ;; \
|
|
*) echo "unsupported builder arch $(uname -m)" >&2; exit 1 ;; \
|
|
esac && \
|
|
curl -sSLo /tmp/zig.tar.xz "https://ziglang.org/download/${ZIG_VERSION}/zig-${ZA}-linux-${ZIG_VERSION}.tar.xz" && \
|
|
echo "${ZSHA} /tmp/zig.tar.xz" | sha256sum -c - && \
|
|
mkdir -p /opt/zig && tar -xJf /tmp/zig.tar.xz -C /opt/zig --strip-components=1 && \
|
|
ln -s /opt/zig/zig /usr/local/bin/zig && rm /tmp/zig.tar.xz && \
|
|
zig version
|
|
|
|
# zigcc resolves TARGETARCH to a zig target triple once, so the three build
|
|
# steps below stay readable and cannot disagree with each other.
|
|
RUN printf '%s\n' '#!/bin/sh' \
|
|
'case "$TARGETARCH" in' \
|
|
' amd64) t=x86_64-linux-musl ;;' \
|
|
' arm64) t=aarch64-linux-musl ;;' \
|
|
' *) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 1 ;;' \
|
|
'esac' \
|
|
'exec zig cc -target "$t" "$@"' > /usr/local/bin/zigcc && chmod +x /usr/local/bin/zigcc
|
|
ENV CC=zigcc CGO_ENABLED=1
|
|
|
|
# Build server
|
|
WORKDIR /build/server
|
|
COPY cmd/server/go.mod cmd/server/go.sum ./
|
|
COPY internal/geofilter/ ../../internal/geofilter/
|
|
COPY internal/sigvalidate/ ../../internal/sigvalidate/
|
|
COPY internal/packetpath/ ../../internal/packetpath/
|
|
COPY internal/dbconfig/ ../../internal/dbconfig/
|
|
COPY internal/dbschema/ ../../internal/dbschema/
|
|
COPY internal/prunequeue/ ../../internal/prunequeue/
|
|
COPY internal/perfio/ ../../internal/perfio/
|
|
COPY internal/mbcapqueue/ ../../internal/mbcapqueue/
|
|
COPY internal/lora/ ../../internal/lora/
|
|
COPY internal/users/ ../../internal/users/
|
|
COPY internal/channel/ ../../internal/channel/
|
|
COPY internal/mailer/ ../../internal/mailer/
|
|
RUN --mount=type=cache,target=/root/.cache/go-build \
|
|
--mount=type=cache,target=/go/pkg/mod \
|
|
--mount=type=cache,target=/tmp/zig-cache \
|
|
go mod download
|
|
COPY cmd/server/ ./
|
|
RUN --mount=type=cache,target=/root/.cache/go-build \
|
|
--mount=type=cache,target=/go/pkg/mod \
|
|
--mount=type=cache,target=/tmp/zig-cache \
|
|
GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
|
|
go build -trimpath -tags ${GO_BUILD_TAGS} \
|
|
-ldflags "-s -w -extldflags '-static -Wl,-s' -X main.Version=${APP_VERSION} -X main.Commit=${GIT_COMMIT} -X main.BuildTime=${BUILD_TIME}" \
|
|
-o /corescope-server .
|
|
|
|
# Build ingestor
|
|
WORKDIR /build/ingestor
|
|
COPY cmd/ingestor/go.mod cmd/ingestor/go.sum ./
|
|
COPY internal/channel/ ../../internal/channel/
|
|
COPY internal/geofilter/ ../../internal/geofilter/
|
|
COPY internal/sigvalidate/ ../../internal/sigvalidate/
|
|
COPY internal/packetpath/ ../../internal/packetpath/
|
|
COPY internal/dbconfig/ ../../internal/dbconfig/
|
|
COPY internal/dbschema/ ../../internal/dbschema/
|
|
COPY internal/prunequeue/ ../../internal/prunequeue/
|
|
COPY internal/perfio/ ../../internal/perfio/
|
|
COPY internal/mbcapqueue/ ../../internal/mbcapqueue/
|
|
RUN --mount=type=cache,target=/root/.cache/go-build \
|
|
--mount=type=cache,target=/go/pkg/mod \
|
|
--mount=type=cache,target=/tmp/zig-cache \
|
|
go mod download
|
|
COPY cmd/ingestor/ ./
|
|
RUN --mount=type=cache,target=/root/.cache/go-build \
|
|
--mount=type=cache,target=/go/pkg/mod \
|
|
--mount=type=cache,target=/tmp/zig-cache \
|
|
GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
|
|
go build -trimpath -tags ${GO_BUILD_TAGS} \
|
|
-ldflags "-s -w -extldflags '-static -Wl,-s'" \
|
|
-o /corescope-ingestor .
|
|
|
|
# Build decrypt CLI
|
|
WORKDIR /build/decrypt
|
|
COPY cmd/decrypt/go.mod cmd/decrypt/go.sum ./
|
|
COPY internal/channel/ ../../internal/channel/
|
|
RUN --mount=type=cache,target=/root/.cache/go-build \
|
|
--mount=type=cache,target=/go/pkg/mod \
|
|
--mount=type=cache,target=/tmp/zig-cache \
|
|
go mod download
|
|
COPY cmd/decrypt/ ./
|
|
RUN --mount=type=cache,target=/root/.cache/go-build \
|
|
--mount=type=cache,target=/go/pkg/mod \
|
|
--mount=type=cache,target=/tmp/zig-cache \
|
|
GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
|
|
go build -trimpath -tags ${GO_BUILD_TAGS} \
|
|
-ldflags "-s -w -extldflags '-static -Wl,-s' -X main.version=${APP_VERSION}" \
|
|
-o /corescope-decrypt .
|
|
|
|
# Runtime image
|
|
FROM alpine:3.24
|
|
|
|
RUN apk add --no-cache mosquitto mosquitto-clients supervisor caddy wget
|
|
|
|
# Account for the Go services when a deployment opts into running them
|
|
# unprivileged with RUN_AS_UID=1000, the usual choice. Creating it costs
|
|
# nothing when it goes unused, and saves the entrypoint creating one at start.
|
|
#
|
|
# Nothing in the image is given to it here. -H matters for that: without it
|
|
# busybox adduser creates the home directory itself, and /app would be
|
|
# corescope-owned mode 2755 — the setgid bit then giving gid 1000 to /app/data
|
|
# and to every file written into it, which Docker copies into each new named
|
|
# volume. The home field still points at /app; only the directory is left to
|
|
# the WORKDIR below, root-owned as in earlier releases.
|
|
RUN addgroup -S -g 1000 corescope && \
|
|
adduser -S -u 1000 -G corescope -H -h /app -s /sbin/nologin corescope
|
|
|
|
# Default: the services run as root, exactly as in earlier releases. The
|
|
# entrypoint overrides this when RUN_AS_UID asks for an unprivileged account.
|
|
ENV CORESCOPE_SERVICE_USER=root
|
|
|
|
WORKDIR /app
|
|
|
|
# Go binaries (statically linked; they do not use this image's libc)
|
|
COPY --from=builder /corescope-server /corescope-ingestor /corescope-decrypt /app/
|
|
|
|
# Frontend assets + config
|
|
COPY public/ ./public/
|
|
COPY config.example.json channel-rainbow.json ./
|
|
|
|
# Bake git commit SHA — manage.sh and CI write .git-commit before build
|
|
# Default to "unknown" if not provided
|
|
RUN echo "unknown" > .git-commit
|
|
|
|
# Supervisor + Mosquitto + Caddy config
|
|
COPY docker/supervisord-go.conf /etc/supervisor/conf.d/supervisord.conf
|
|
COPY docker/supervisord-go-no-mosquitto.conf /etc/supervisor/conf.d/supervisord-no-mosquitto.conf
|
|
COPY docker/supervisord-go-no-caddy.conf /etc/supervisor/conf.d/supervisord-no-caddy.conf
|
|
COPY docker/supervisord-go-no-mosquitto-no-caddy.conf /etc/supervisor/conf.d/supervisord-no-mosquitto-no-caddy.conf
|
|
COPY docker/mosquitto.conf /etc/mosquitto/mosquitto.conf
|
|
COPY docker/Caddyfile /etc/caddy/Caddyfile
|
|
|
|
# Data directory
|
|
RUN mkdir -p /app/data /var/lib/mosquitto /data/caddy && \
|
|
chown -R mosquitto:mosquitto /var/lib/mosquitto
|
|
|
|
# Entrypoint
|
|
COPY docker/entrypoint-go.sh /entrypoint.sh
|
|
# Sourced by the entrypoint, so deliberately not executable.
|
|
COPY docker/select-service-user.sh /select-service-user.sh
|
|
RUN chmod +x /entrypoint.sh
|
|
|
|
EXPOSE 80 443 1883
|
|
|
|
VOLUME ["/app/data", "/data/caddy"]
|
|
|
|
ENTRYPOINT ["/entrypoint.sh"]
|