Files
fancy-dns/Dockerfile
T
mats 289a8cc2d8
build / build-push (push) Successful in 56s
Copy the fresh console bundle after the Docker build context
The source copy overwrote the generated index.html with a tracked stale version after the web stage had supplied fresh hashed assets. Browsers requested a missing script and received the SPA HTML fallback. Copy the web stage output last so the embedded entrypoint and assets come from the same build.
2026-10-16 11:51:03 +02:00

88 lines
3.1 KiB
Docker

# syntax=docker/dockerfile:1.7
#
# Two stages: build the console, then build a static Go binary with it
# embedded. The result is a single file with no runtime dependencies beyond
# libc, which is what makes a scratch/distroless base safe.
ARG NODE_IMAGE=node:22-alpine
ARG GO_IMAGE=golang:1.27-alpine
ARG RUNTIME_IMAGE=gcr.io/distroless/static-debian12:nonroot
# ---- stage 1: the admin console -------------------------------------------
FROM ${NODE_IMAGE} AS web
WORKDIR /src/web
# Dependencies first: this layer is only rebuilt when package-lock.json moves,
# not on every source edit.
COPY web/package.json web/package-lock.json* ./
RUN npm ci --no-audit --no-fund
COPY web/ ./
# vite.config.ts writes to ../internal/web/dist, which is where the Go embed
# directive looks.
RUN mkdir -p /src/internal/web && npm run build \
&& test -f /src/internal/web/dist/index.html
# ---- stage 2: the binary ---------------------------------------------------
FROM ${GO_IMAGE} AS build
ARG VERSION=dev
ARG TARGETOS=linux
ARG TARGETARCH=amd64
WORKDIR /src
# Download modules before copying the application sources.
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# Copy the fresh bundle last: the source tree contains a tracked index.html
# whose hashed asset references can be stale. It must never overwrite the
# index generated alongside the JavaScript and CSS in the web stage.
COPY --from=web /src/internal/web/dist/ internal/web/dist/
# CGO off: the resolver, bbolt and the TLS stack are all pure Go, and a static
# binary is what lets the runtime stage be distroless-static.
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -trimpath \
-ldflags "-s -w -X git.k8s-git-k6n.net/mats/fancy-dns/internal/app.Version=${VERSION}" \
-o /out/fancy-dns ./cmd/fancy-dns
# The console is a separate binary in the same image: run it with
# `command: ["/usr/local/bin/fancy-dns-console"]`. It shares the embedded UI.
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -trimpath \
-ldflags "-s -w -X git.k8s-git-k6n.net/mats/fancy-dns/internal/app.Version=${VERSION}" \
-o /out/fancy-dns-console ./cmd/fancy-dns-console
# ---- stage 3: runtime ------------------------------------------------------
FROM ${RUNTIME_IMAGE}
# 53 is the DNS service itself, 853 is DoT, 443 is DoH, 8080 is the console.
EXPOSE 53/udp 53/tcp
EXPOSE 853/tcp
EXPOSE 443/tcp
EXPOSE 8080/tcp
# The bbolt files and downloaded lists live here; mount a volume over it.
VOLUME ["/var/lib/fancy-dns"]
COPY --from=build /out/fancy-dns /usr/local/bin/fancy-dns
COPY --from=build /out/fancy-dns-console /usr/local/bin/fancy-dns-console
COPY --from=build /src/deploy/config.example.yaml /etc/fancy-dns/config.example.yaml
COPY --from=build /src/deploy/zones/ /etc/fancy-dns/zones/
USER nonroot:nonroot
# The liveness probe, not the readiness one: it answers 200 while the process
# is up, which is what a crash-loop detector wants.
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD ["/usr/local/bin/fancy-dns", "-version"]
ENTRYPOINT ["/usr/local/bin/fancy-dns"]
CMD ["-config", "/etc/fancy-dns/config.yaml"]