|
| 1 | +# syntax=docker/dockerfile:1 |
| 2 | + |
| 3 | +# ============================================================================= |
| 4 | +# chartplotter — a tiny multi-arch image built from a FULLY STATIC musl binary |
| 5 | +# (the "go-dims" pattern). The Go binary is already self-contained: the web |
| 6 | +# assets, the S-101 catalogue, and the native libtile57 engine are all embedded |
| 7 | +# / statically linked, so a static musl build dropped into `FROM scratch` yields |
| 8 | +# an image that is basically just the ~25 MB binary (+ a CA bundle). |
| 9 | +# |
| 10 | +# Multi-arch WITHOUT QEMU emulation: the builder runs on the native BUILDPLATFORM |
| 11 | +# and cross-links each TARGETARCH with `zig cc` (the same recipe as `make musl`), |
| 12 | +# so one fast builder produces both linux/amd64 and linux/arm64 by target swap. |
| 13 | +# docker buildx build --platform linux/amd64,linux/arm64 -t chartplotter . |
| 14 | +# A plain `docker build` builds only the host arch. |
| 15 | +# ============================================================================= |
| 16 | + |
| 17 | +# ---- builder: Go 1.26 + Zig 0.16, cross-links the static musl binary --------- |
| 18 | +# Pinned to BUILDPLATFORM so it stays native (no emulation) even when the target |
| 19 | +# is a different arch — Go + zig do the cross-compile. |
| 20 | +FROM --platform=$BUILDPLATFORM golang:1.26-bookworm AS builder |
| 21 | + |
| 22 | +# Zig 0.16 is the C toolchain for the CGO cross-link (see scripts/xbuild-tile57.sh). |
| 23 | +ARG ZIG_VERSION=0.16.0 |
| 24 | +# Predefined by buildx: BUILDARCH = the builder's own arch (fetch the matching |
| 25 | +# host Zig); TARGETARCH = the arch we cross-compile FOR. |
| 26 | +ARG BUILDARCH |
| 27 | +ARG TARGETARCH |
| 28 | +# Stamped into the binary (main.version). Pass --build-arg VERSION=v1.2.3 in CI. |
| 29 | +ARG VERSION=docker |
| 30 | +# Optional: pin the engine to a branch/tag for reproducible builds (default: the |
| 31 | +# repo's default branch). The engine ABI lives in include/ + bindings/go. |
| 32 | +ARG TILE57_REF= |
| 33 | + |
| 34 | +# Install Zig 0.16 (host arch) — the C cross-compiler `zig cc` links libtile57 + |
| 35 | +# the cgo objects against musl for any target triple from this one host. xz-utils |
| 36 | +# is needed to unpack Zig's .tar.xz (not in the golang image by default). |
| 37 | +RUN set -eux; \ |
| 38 | + apt-get update; \ |
| 39 | + apt-get install -y --no-install-recommends xz-utils; \ |
| 40 | + rm -rf /var/lib/apt/lists/*; \ |
| 41 | + case "$BUILDARCH" in \ |
| 42 | + amd64) zarch=x86_64 ;; \ |
| 43 | + arm64) zarch=aarch64 ;; \ |
| 44 | + *) echo "unsupported build arch: $BUILDARCH" >&2; exit 1 ;; \ |
| 45 | + esac; \ |
| 46 | + url="https://ziglang.org/download/${ZIG_VERSION}/zig-${zarch}-linux-${ZIG_VERSION}.tar.xz"; \ |
| 47 | + curl -fsSL "$url" -o /tmp/zig.tar.xz; \ |
| 48 | + mkdir -p /opt/zig; \ |
| 49 | + tar -xJf /tmp/zig.tar.xz -C /opt/zig --strip-components=1; \ |
| 50 | + rm /tmp/zig.tar.xz; \ |
| 51 | + ln -s /opt/zig/zig /usr/local/bin/zig; \ |
| 52 | + zig version |
| 53 | + |
| 54 | +# The engine is the PUBLIC github.com/beetlebugorg/tile57, cloned as a SIBLING of |
| 55 | +# the source tree so go.mod's `replace … => ../tile57/bindings/go` and the |
| 56 | +# binding's cgo LDFLAGS resolve; its submodules carry the IHO S-101 catalogues. |
| 57 | +WORKDIR /src |
| 58 | +RUN set -eux; \ |
| 59 | + if [ -n "$TILE57_REF" ]; then br="--branch $TILE57_REF"; else br=""; fi; \ |
| 60 | + git clone --depth 1 $br --recurse-submodules --shallow-submodules \ |
| 61 | + https://github.com/beetlebugorg/tile57 /src/tile57; \ |
| 62 | + git -C /src/tile57 rev-parse --short=9 HEAD |
| 63 | + |
| 64 | +# chartplotter source beside the engine. |
| 65 | +COPY . /src/chartplotter |
| 66 | +WORKDIR /src/chartplotter |
| 67 | + |
| 68 | +# Cross-link the fully-static musl binary for TARGETARCH. LIBC=musl makes zig's |
| 69 | +# lld emit a static ELF (`ldd` → "not a dynamic executable"); SKIP_RESTORE=1 skips |
| 70 | +# the throwaway host-lib rebuild (the tree is discarded after this stage). The Go |
| 71 | +# build cache + module cache are mounted so rebuilds are fast. |
| 72 | +RUN --mount=type=cache,target=/root/.cache/go-build \ |
| 73 | + --mount=type=cache,target=/go/pkg/mod \ |
| 74 | + set -eux; \ |
| 75 | + LIBC=musl SKIP_RESTORE=1 \ |
| 76 | + PLATFORMS="linux/${TARGETARCH}" \ |
| 77 | + OUT=/out \ |
| 78 | + VERSION="${VERSION}" \ |
| 79 | + TILE57=/src/tile57 \ |
| 80 | + ENGINE_COMMIT="$(git -C /src/tile57 rev-parse --short=9 HEAD)" \ |
| 81 | + scripts/xbuild-tile57.sh; \ |
| 82 | + cp "/out/chartplotter_linux_${TARGETARCH}_musl" /chartplotter; \ |
| 83 | + # Smoke-test only on a native build — a cross-built (e.g. arm64-on-amd64) |
| 84 | + # binary can't be executed by the amd64 builder (no emulation here). |
| 85 | + if [ "$TARGETARCH" = "$BUILDARCH" ]; then /chartplotter version; fi |
| 86 | + |
| 87 | +# An empty, world-writable /tmp to seed into the scratch image: `serve` uses |
| 88 | +# os.MkdirTemp for the regenerated S-101 client assets, and scratch has no /tmp. |
| 89 | +RUN mkdir -m 1777 -p /image-tmp |
| 90 | + |
| 91 | +# ---- final: FROM scratch — just the binary + a CA bundle -------------------- |
| 92 | +FROM scratch |
| 93 | + |
| 94 | +# A static musl binary has NO system trust store, and chartplotter makes outbound |
| 95 | +# HTTPS calls (charts.noaa.gov / ienccloud.us chart downloads). VERIFIED: without |
| 96 | +# a CA bundle those fetches fail with `x509: certificate signed by unknown |
| 97 | +# authority`. Bundle the roots from the builder and point Go's crypto/x509 at them. |
| 98 | +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt |
| 99 | +ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt |
| 100 | + |
| 101 | +COPY --from=builder /chartplotter /chartplotter |
| 102 | + |
| 103 | +# scratch ships no /tmp; `serve` needs one for the regenerated S-101 assets. |
| 104 | +COPY --from=builder /image-tmp /tmp |
| 105 | + |
| 106 | +# Persistent chart state (source ENC + baked tiles) — back it with a named volume. |
| 107 | +VOLUME ["/data"] |
| 108 | +EXPOSE 8080 |
| 109 | + |
| 110 | +# `serve` defaults to --host 127.0.0.1, which is unreachable from outside the |
| 111 | +# container, so bind 0.0.0.0. Source ENC + baked tiles land under the volume. |
| 112 | +ENTRYPOINT ["/chartplotter"] |
| 113 | +CMD ["serve", "--host", "0.0.0.0", "--data", "/data", "--cache", "/data/cache"] |
0 commit comments