Repository navigation
Make Dockerfile cross-platform (ARM support) #1
Copy link
Copy link
Closed
Description
Hey, this is pretty solid and clean, and makes me less mad than cal dot com does. One issue I ran into however is that I run an ARM server and this doesn’t currently account for an ARM build (even though it is fully capable of compiling on ARM). Here’s the replacement Dockerfile I’m working with.
# syntax=docker/dockerfile:1
# ── Frontend build stage ───────────────────────────────────────────────────────
FROM node:22-alpine AS frontend-builder
# Pin pnpm to the version in package.json's `packageManager` field (not @latest)
# so CI builds are reproducible and match the committed lockfile.
RUN corepack enable && corepack prepare pnpm@10.32.1 --activate
WORKDIR /app
COPY frontend/package.json frontend/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY frontend/ .
RUN pnpm build
# ── Go build stage ─────────────────────────────────────────────────────────────
FROM golang:1.26-alpine AS builder
RUN apk add --no-cache ca-certificates wget
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
COPY --from=frontend-builder /app/build ./frontend/build
# Build natively for whatever architecture this stage is running on
# (TARGETARCH is auto-populated by Docker buildx; falls back correctly
# on ARM hosts where the original hardcoded amd64 would produce a
# binary that can't execute at all).
ARG TARGETARCH
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build \
-ldflags="-s -w" \
-o calnode ./cmd/calnode
# Download Litestream for the deployment target, matching TARGETARCH
ARG LITESTREAM_VERSION=0.3.13
RUN wget -qO- \
"https://github.com/benbjohnson/litestream/releases/download/v${LITESTREAM_VERSION}/litestream-v${LITESTREAM_VERSION}-linux-${TARGETARCH}.tar.gz" \
| tar -xz -C /usr/local/bin litestream
# ── Runtime stage ─────────────────────────────────────────────────────────────
# alpine (not scratch) — needed for the shell entrypoint and Litestream.
# No --platform pin here: inherits the build host's native architecture,
# matching whatever TARGETARCH the binary above was actually compiled for.
FROM alpine:3.21
RUN apk add --no-cache ca-certificates tzdata
COPY --from=builder /usr/local/bin/litestream /usr/local/bin/litestream
COPY --from=builder /build/calnode /calnode
COPY litestream.yml /etc/litestream.yml
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Note: no `VOLUME` directive — persistent storage is provided by the platform's
# managed volume mounted at /data (Railway rejects the Docker VOLUME instruction;
# Fly mounts via fly.toml). The dir is created at runtime by entrypoint.sh.
EXPOSE 3000
ENV PORT=3000 \
DATABASE_URL=sqlite:///data/calnode.db
ENTRYPOINT ["/entrypoint.sh"]
Happy to propose a merge, but given that you’re still in the throes of working on this I don’t want to cramp your style just yet.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
shockalotti commentedon Jun 29, 2026
Hey @readtedium — thanks, genuinely useful catch and exactly the kind of issue I like to get.
You're dead right: the Dockerfile hardcodes amd64 in three spots (the Go build, the Litestream download, and the runtime FROM --platform). The nice part is it'll cross-compile cleanly — Calnode uses pure-Go SQLite (modernc.org/sqlite), so with CGO_ENABLED=0 (already there) and GOARCH=${TARGETARCH} there's no C toolchain or qemu dance. ARM-everywhere (Graviton, Ampere, Apple Silicon, the Hetzner/Oracle ARM tiers) is squarely on-brand for a "runs anywhere, cheaply" tool, so this belongs in.
Please do send the PR — you're not cramping anything. Couple of small notes:
Keep it scoped to the Dockerfile; the hosted deploy uses Nixpacks, so this only touches the self-host Docker path (low risk).
If it's easy, a line in DEPLOY.md on building for arm64 (docker buildx build --platform linux/arm64 …) would help the next person.
Appreciate you testing on ARM and pinning the exact cause before opening — top-tier first issue 🙏
fix(security): close CalDAV and BYO-LLM SSRF gaps (audit findings #1, #2
fix(security): close LiveKit URL SSRF gap (audit finding #3)
readtedium commentedon Jul 7, 2026
@shockalotti Hey, I apologize that I never got back to this, but thanks again for working the fix in!
shockalotti commentedon Jul 7, 2026
Applied your fix as-is (thank you — saved a lot of digging):
TARGETARCHfor both the Go build and the Litestream download, plus dropping the--platform=linux/amd64pin on the runtime stage. Verified locally on bothlinux/amd64andlinux/arm64(QEMU) — built, booted, ran migrations, and served/admin/loginsuccessfully on each.Landed in
a8c7571. Also wired up CI (.github/workflows/docker-publish.yml) to build and push a multi-arch image toghcr.io/calnode/calnodeon every push tomain, so this doesn't just fix your local build — it becomes the actual published image going forward.Closing this out — shout if you hit anything on real ARM hardware that the QEMU emulation didn't catch.