1- FROM node:18 AS node
1+ # syntax=docker/dockerfile:1
2+ # ###########################################################
3+ # 1) Base Node environment
4+ # ###########################################################
5+ FROM node:18 AS node-base
26
7+ # Create non-root workspace owned by uid/gid 1000 (keeps permissions consistent across stages)
38RUN mkdir /app && chown 1000:1000 /app
49USER 1000
510WORKDIR /app
611
7- FROM node AS build-cli
12+ # ###########################################################
13+ # 2) Dependency layer (Yarn cache prefetch)
14+ # ###########################################################
15+ FROM node-base AS deps
816ARG TARGETARCH
917
10- COPY .yarnrc.yml yarn.lock ./
11- COPY --chown=1000:1000 .yarn .yarn
18+ # Copy only Yarn config + lockfile up front to maximize layer caching
19+ COPY --link --chown=1000:1000 .yarnrc.yml yarn.lock ./
20+ COPY --link --chown=1000:1000 .yarn .yarn
21+
22+ # Pre-fetch Yarn cache without installing (fast, deterministic, cacheable)
23+ # NOTE: BuildKit cache mount keeps the artifact cache across builds.
1224RUN yarn fetch
1325
14- RUN NODE_ARCH=$(if [ "$TARGETARCH" = "amd64" ]; then echo "x64" ; elif [ "$TARGETARCH" = "arm" ]; then echo "armv7" ; else echo "$TARGETARCH" ; fi) && \
15- echo $NODE_ARCH>.nodearch
26+ # Normalize Docker's TARGETARCH into Node's arch names and persist for later stages
27+ # amd64 -> x64, arm -> armv7, otherwise pass through (arm64, etc.)
28+ RUN bash -lc 'case "$TARGETARCH" in \
29+ amd64) echo x64 ;; \
30+ arm) echo armv7 ;; \
31+ *) echo "$TARGETARCH" ;; \
32+ esac > .nodearch'
33+
34+ # ###########################################################
35+ # 3) CLI build (bundle with ncc, pack with vercel/pkg)
36+ # ###########################################################
37+ FROM deps AS build-cli
1638
17- # prefetch node, see https://github.com/vercel/pkg/issues/292#issuecomment-401353635
39+ # Prefetch the Node runtime that "pkg" will embed so the next step is faster and stable
40+ # (See: https://github.com/vercel/pkg/issues/292#issuecomment-401353635)
1841RUN touch noop.js && \
19- yarn pkg -t node18-linuxstatic-$(cat .nodearch) noop.js --out=noop && rm -rf noop && \
20- rm noop.js
42+ yarn pkg -t node18-linuxstatic-$(cat .nodearch) noop.js --out=noop && \
43+ rm -rf noop noop.js
2144
22- COPY package.json index.js ./
23- COPY src src
24- RUN yarn build:ncc
25- RUN yarn pkg -t node18-linuxstatic-$(cat .nodearch) -o ./dist-bin/dockerfile-x --compress=GZip ./dist/index.js
45+ # Copy the minimum project sources necessary for building the CLI
46+ COPY --link --chown=1000:1000 src ./src
47+ COPY --link --chown=1000:1000 index.js package.json ./
2648
49+ # Build the CLI (ncc) and then produce a single-file static binary via pkg
50+ RUN yarn build:ncc && \
51+ yarn pkg -t node18-linuxstatic-$(cat .nodearch) \
52+ -o ./dist-bin/dockerfile-x --compress=GZip ./dist/index.js
53+
54+ # ###########################################################
55+ # 4) CA certificates for final scratch
56+ # ###########################################################
2757FROM alpine:3 AS certs
28- RUN apk --update add ca-certificates
58+ RUN apk --no-cache add ca-certificates
2959
60+ # ###########################################################
61+ # 5) Build Go frontend (static binary)
62+ # ###########################################################
3063FROM golang:1.25 AS build-frontend
3164ARG TARGETARCH
3265WORKDIR /app
33- COPY vendor vendor
34- COPY go.mod go.sum ./
35- COPY pkg pkg
36- COPY main.go ./
37- RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -mod vendor -ldflags '-w -extldflags "-static"' -o dist-bin/dockerfile-x-frontend .
3866
67+ # Copy modules first for better caching, then the sources
68+ COPY --link go.mod go.sum ./
69+ COPY --link vendor ./vendor
70+ COPY --link pkg ./pkg
71+ COPY --link main.go ./
72+
73+ # Produce a fully-static Linux binary (no CGO, strip symbols)
74+ # GOARCH is derived from Docker's --platform
75+ RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH \
76+ go build -mod=vendor -trimpath -buildvcs=false \
77+ -ldflags='-s -w -extldflags "-static"' \
78+ -o dist-bin/dockerfile-x-frontend .
79+
80+ # ###########################################################
81+ # 6) Minimal final image
82+ # ###########################################################
3983FROM scratch
4084
41- # see https://github.com/moby/buildkit/blob/master/frontend/dockerfile/cmd/dockerfile-frontend/Dockerfile
85+ # BuildKit frontend capabilities (mirrors upstream Dockerfile frontend)
86+ # Ref: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/cmd/dockerfile-frontend/Dockerfile
4287LABEL moby.buildkit.frontend.network.none="true"
4388LABEL moby.buildkit.frontend.caps="moby.buildkit.frontend.inputs,moby.buildkit.frontend.subrequests,moby.buildkit.frontend.contexts"
4489
45- ENTRYPOINT [ "/dockerfile-x-frontend" ]
46- ENV PATH=/
47- ENV DOCKERFILEX_TMPDIR=/workspace
90+ # Keep PATH minimal; put binaries at root so ENTRYPOINT can reference them directly
91+ ENV PATH=/ \
92+ DOCKERFILEX_TMPDIR=/workspace
93+
94+ # Run as non-root and default to a writeable working directory
4895USER 1000
4996WORKDIR /workspace
50- COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
51- COPY --from=build-frontend /app/dist-bin/ /
52- COPY --from=build-cli /app/dist-bin/ /
97+
98+ # Copy only the necessary runtime artifacts
99+ COPY --link --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
100+ COPY --link --from=build-frontend /app/dist-bin/ /
101+ COPY --link --from=build-cli /app/dist-bin/ /
102+
103+ # The Go frontend is the entrypoint; the CLI binary is also available in PATH
104+ ENTRYPOINT ["/dockerfile-x-frontend" ]
0 commit comments