mirror of
https://github.com/hanzoai/ingress.git
synced 2026-10-20 15:07:40 +00:00
CI/CD / cicd (push) Failing after 1m48s
Hanzo CI/CD / cicd (push) Failing after 1m47s
Co-authored-by: Hanzo Dev <[email protected]>
120 lines
5.0 KiB
Docker
120 lines
5.0 KiB
Docker
# syntax=docker/dockerfile:1
|
|
|
|
# ---- Stage 1: Build WebUI dashboard ----
|
|
FROM --platform=$BUILDPLATFORM node:24-alpine3.22 AS webui
|
|
|
|
RUN corepack enable && corepack prepare [email protected] --activate
|
|
|
|
WORKDIR /src/webui
|
|
COPY webui/package.json webui/pnpm-lock.yaml ./
|
|
RUN pnpm install --frozen-lockfile
|
|
COPY webui/ ./
|
|
RUN pnpm build
|
|
|
|
# ---- Stage 2: Build Go binary ----
|
|
FROM --platform=$BUILDPLATFORM golang:1.26.8-alpine AS builder
|
|
# go.mod pins the toolchain. The golang base image sets GOTOOLCHAIN=local,
|
|
# which turns a `go` directive newer than the image into a hard build
|
|
# failure instead of a download.
|
|
ENV GOTOOLCHAIN=auto
|
|
|
|
RUN apk add --no-cache git tzdata ca-certificates
|
|
|
|
ARG TARGETOS=linux
|
|
ARG TARGETARCH=amd64
|
|
ARG VERSION=v0.1.0
|
|
|
|
WORKDIR /src
|
|
# hanzoai/* is private, so it is fetched via authenticated git, bypassing the
|
|
# public proxy. gh_token is the shared docker-build.yml BuildKit secret; no-op
|
|
# when absent (local/dev).
|
|
#
|
|
# zap-proto/* is NOT private — the repos are public and proxy.golang.org serves
|
|
# them. Listing it here sent go to direct git for a module the proxy already
|
|
# had, and that path is the fragile one:
|
|
#
|
|
# reading github.com/zap-proto/go/go.mod at revision v1.3.0: git ls-remote …
|
|
# fatal: unable to access 'https://github.com/zap-proto/go/': SSL: certificate
|
|
# subject name 'dotcom.glb' does not match target hostname 'github.com'
|
|
#
|
|
# GOPRIVATE names what is actually private. Anything public goes through the
|
|
# proxy, which is faster and checksum-verified.
|
|
ENV GOPRIVATE=github.com/hanzoai/*
|
|
# Copy go.mod, go.sum, and the local replace target first for layer caching.
|
|
COPY go.mod go.sum ./
|
|
COPY pkg/config/dynamic/ext/ ./pkg/config/dynamic/ext/
|
|
RUN --mount=type=secret,id=gh_token \
|
|
if [ -s /run/secrets/gh_token ]; then \
|
|
export GIT_CONFIG_COUNT=1 \
|
|
GIT_CONFIG_KEY_0="url.https://x-access-token:$(cat /run/secrets/gh_token)@github.com/.insteadOf" \
|
|
GIT_CONFIG_VALUE_0="https://github.com/"; \
|
|
fi; \
|
|
go mod download
|
|
|
|
# Copy full source.
|
|
COPY . .
|
|
# Overlay built webui assets.
|
|
COPY --from=webui /src/webui/static ./webui/static
|
|
|
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
|
--mount=type=cache,target=/root/.cache/go-build \
|
|
GOEXPERIMENT=jsonv2 CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \
|
|
-ldflags="-w -s \
|
|
-X github.com/hanzoai/ingress/v3/pkg/version.Version=${VERSION} \
|
|
-X github.com/hanzoai/ingress/v3/pkg/version.Codename=hanzo \
|
|
-X github.com/hanzoai/ingress/v3/pkg/version.BuildDate=$(date -u '+%Y-%m-%d_%I:%M:%S%p')" \
|
|
-o /hanzo-ingress ./cmd/ingress
|
|
|
|
# ---- Stage 3: Runtime ----
|
|
|
|
# What the scratch stage copies in place of adduser and mkdir, which it lacks.
|
|
RUN printf 'ingress:x:1000:1000::/:/sbin/nologin\n' > /etc/passwd.ingress && \
|
|
printf 'ingress:x:1000:\n' > /etc/group.ingress && \
|
|
mkdir -p /emptytmp && chmod 1777 /emptytmp
|
|
|
|
# THE IMAGE IS THE BINARY.
|
|
#
|
|
# hanzo-ingress is CGO_ENABLED=0 and statically linked, so it takes nothing from
|
|
# a host. Alpine was supplying three things and none of them survives inspection:
|
|
#
|
|
# ca-certificates and tzdata are DATA the binary reads, and they copy.
|
|
# The account is two files the kernel reads to name a uid it already enforces.
|
|
# libcap-utils existed for ONE line, `setcap cap_net_bind_service=+ep`.
|
|
#
|
|
# THE FILE CAPABILITY IS REDUNDANT AND THAT IS MEASURED, not assumed. The live
|
|
# deployment runs with securityContext capabilities `add: [NET_BIND_SERVICE],
|
|
# drop: [ALL]` — the orchestrator grants the capability to the process, so a
|
|
# capability baked into the file grants nothing it does not already have. The
|
|
# image's own ports are unprivileged anyway (8080/8443, mapped by the Service),
|
|
# which is the arrangement the line below the EXPOSE has always described.
|
|
#
|
|
# The one posture this changes is `docker run` as a non-root user binding :80
|
|
# directly, with no --cap-add. That is not how this is deployed, and the remedy
|
|
# there is --cap-add=NET_BIND_SERVICE rather than a capability in the artifact.
|
|
FROM scratch
|
|
|
|
LABEL org.opencontainers.image.source="https://github.com/hanzoai/ingress"
|
|
LABEL org.opencontainers.image.title="Hanzo Ingress"
|
|
LABEL org.opencontainers.image.description="Cloud-native reverse proxy and load balancer for Hanzo infrastructure"
|
|
|
|
# Data, read by the binary, executed by nothing.
|
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
|
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
|
|
|
# The account. scratch has no adduser, so the two files it would have written are
|
|
# written in the builder and copied.
|
|
COPY --from=builder /etc/passwd.ingress /etc/passwd
|
|
COPY --from=builder /etc/group.ingress /etc/group
|
|
|
|
# /tmp, owned by the account. The VOLUME below replaces it at run time; this is
|
|
# what the image holds when nothing is mounted, and scratch has no mkdir.
|
|
COPY --from=builder --chown=1000:1000 /emptytmp /tmp
|
|
|
|
COPY --from=builder /hanzo-ingress /hanzo-ingress
|
|
|
|
# Bind to unprivileged ports; K8s Service/DaemonSet maps 80->8080, 443->8443
|
|
EXPOSE 8080 8443
|
|
VOLUME ["/tmp"]
|
|
USER 1000:1000
|
|
ENTRYPOINT ["/hanzo-ingress"]
|