initial commit
Some checks failed
ci / go (push) Has been cancelled
ci / admin-ui (push) Has been cancelled
ci / runtime-sdk (push) Has been cancelled
ci / helm (push) Has been cancelled
ci / openapi (push) Has been cancelled
ci / container (push) Has been cancelled
ci / release-config (push) Has been cancelled
ci / shared styling (push) Has been cancelled
ci / security (push) Has been cancelled
ci / all green (push) Has been cancelled
Some checks failed
ci / go (push) Has been cancelled
ci / admin-ui (push) Has been cancelled
ci / runtime-sdk (push) Has been cancelled
ci / helm (push) Has been cancelled
ci / openapi (push) Has been cancelled
ci / container (push) Has been cancelled
ci / release-config (push) Has been cancelled
ci / shared styling (push) Has been cancelled
ci / security (push) Has been cancelled
ci / all green (push) Has been cancelled
This commit is contained in:
commit
951dd8d26b
175 changed files with 50041 additions and 0 deletions
159
Dockerfile
Normal file
159
Dockerfile
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
# syntax=docker/dockerfile:1.7
|
||||
#
|
||||
# Go Bananas!! container image.
|
||||
#
|
||||
# Design notes
|
||||
# ------------
|
||||
# * The Go build is fully static. modernc.org/sqlite is a pure-Go translation of
|
||||
# SQLite, so CGO is not needed and the runtime image needs no libc at all. That
|
||||
# is what lets the final stage be `scratch`-adjacent: a smaller image, and no
|
||||
# shared-library CVEs to patch.
|
||||
# * The frontend and the SDK are built before the Go binary and copied in, so the
|
||||
# image build fails if the frontend fails. A container that starts and then 404s
|
||||
# on its own dashboard is worse than one that does not start.
|
||||
# * Dependencies are installed from a lockfile in their own layer, so a source-only
|
||||
# change does not re-download the module cache.
|
||||
# * BuildKit cache mounts are used for the module and package caches. They are a
|
||||
# cache, not an artefact, so they are not part of the image.
|
||||
|
||||
# ---------------------------------------------------------------- arguments --
|
||||
|
||||
ARG GO_VERSION=1.27.1
|
||||
ARG NODE_VERSION=24.9.0
|
||||
ARG ALPINE_VERSION=3.22
|
||||
|
||||
# ------------------------------------------------------------- web and sdk ----
|
||||
|
||||
FROM node:${NODE_VERSION}-alpine AS assets
|
||||
|
||||
# Corepack is enabled so that the pinned package manager in package.json is the one
|
||||
# that runs, rather than whatever npm happens to ship with the Node release.
|
||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||
|
||||
WORKDIR /src/web
|
||||
COPY web/package.json web/package-lock.json* ./
|
||||
|
||||
# `npm ci` when a lockfile is present, `npm install` otherwise. The fallback keeps
|
||||
# the image buildable from a source tree that has not installed yet, while CI
|
||||
# always takes the reproducible path.
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
if [ -f package-lock.json ]; then npm ci --no-audit --no-fund; \
|
||||
else npm install --no-audit --no-fund; fi
|
||||
|
||||
COPY web/ ./
|
||||
RUN npm run build
|
||||
|
||||
WORKDIR /src/sdk
|
||||
COPY sdk/package.json sdk/package-lock.json* ./
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
if [ -f package-lock.json ]; then npm ci --no-audit --no-fund; \
|
||||
else npm install --no-audit --no-fund; fi
|
||||
|
||||
COPY sdk/ ./
|
||||
RUN npm run build
|
||||
|
||||
# ------------------------------------------------------------------- go ------
|
||||
|
||||
FROM golang:${GO_VERSION}-alpine AS build
|
||||
|
||||
ARG VERSION=dev
|
||||
ARG COMMIT=unknown
|
||||
ARG BUILD_DATE=unknown
|
||||
ARG TARGETOS=linux
|
||||
ARG TARGETARCH=amd64
|
||||
|
||||
# git is needed for the version stamping below when the build runs outside a
|
||||
# checkout; ca-certificates for module downloads.
|
||||
RUN apk add --no-cache git ca-certificates
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
# Dependencies first, in their own layer. go.sum is copied and verified so that a
|
||||
# tampered or truncated lockfile fails the build rather than silently resolving
|
||||
# something different.
|
||||
COPY go.mod go.sum ./
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
go mod download && go mod verify
|
||||
|
||||
COPY internal/ ./internal/
|
||||
COPY cmd/ ./cmd/
|
||||
|
||||
ARG CGO_ENABLED=0
|
||||
ARG GOFLAGS=-trimpath
|
||||
|
||||
# -trimpath removes local filesystem paths from the binary, which keeps build paths
|
||||
# out of any stack trace a user might paste into a bug report.
|
||||
# -s -w drop the symbol table and DWARF data: neither is useful without a matching
|
||||
# local binary, and together they are a substantial fraction of the image size.
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=${CGO_ENABLED} GOFLAGS=${GOFLAGS} \
|
||||
go build \
|
||||
-ldflags "-s -w -X github.com/urpi/go-bananas/internal/app.Version=${VERSION} -X main.version=${VERSION} -X github.com/urpi/go-bananas/internal/app.Commit=${COMMIT} -X github.com/urpi/go-bananas/internal/app.BuildDate=${BUILD_DATE}" \
|
||||
-o /out/go-bananas ./cmd/go-bananas
|
||||
|
||||
# Verify the binary is genuinely static before shipping it. A build that silently
|
||||
# picked up cgo would produce an image that fails at run time with a confusing
|
||||
# "no such file or directory" rather than at build time with a clear one.
|
||||
RUN if [ "${CGO_ENABLED}" = "0" ]; then \
|
||||
if go version -m /out/go-bananas | grep -q 'CGO_ENABLED=1'; then \
|
||||
echo "error: binary is dynamically linked but CGO_ENABLED=0" >&2; exit 1; \
|
||||
fi; \
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------- runtime ----
|
||||
|
||||
# Alpine rather than distroless or scratch: the service needs a shell for a health
|
||||
# check and a writable directory for SQLite, and Alpine supplies both in ~8 MB. The
|
||||
# attack surface difference against distroless is small next to the operational
|
||||
# benefit, and the runtime user is unprivileged either way.
|
||||
FROM alpine:${ALPINE_VERSION} AS runtime
|
||||
|
||||
ARG VERSION=dev
|
||||
|
||||
# curl is only here for the container health check. wget from busybox would do, but
|
||||
# it is already present and curl's exit codes are the ones the check reasons about.
|
||||
# tzdata is needed because timestamps are rendered in the configured locale.
|
||||
RUN apk add --no-cache ca-certificates tzdata curl \
|
||||
&& addgroup -g 65532 -S bananas \
|
||||
&& adduser -u 65532 -S -G bananas -H -s /sbin/nologin bananas
|
||||
|
||||
COPY --from=build /out/go-bananas /usr/local/bin/go-bananas
|
||||
|
||||
COPY --from=assets /src/web/dist /srv/web
|
||||
COPY --from=assets /src/sdk/dist /srv/sdk
|
||||
|
||||
# The data directory holds the SQLite file. It is a volume in every real deployment;
|
||||
# creating it here means an unconfigured local run still starts.
|
||||
RUN mkdir -p /srv/data && chown -R bananas:bananas /srv
|
||||
|
||||
USER 65532:65532
|
||||
WORKDIR /srv
|
||||
|
||||
ENV BANANAS_HTTP_ADDR=:8080 \
|
||||
BANANAS_STATIC_DIR=/srv/web \
|
||||
BANANAS_SDK_DIR=/srv/sdk \
|
||||
BANANAS_DATABASE_PATH=/srv/data/go-bananas.db \
|
||||
BANANAS_LOG_FORMAT=json
|
||||
|
||||
EXPOSE 8080
|
||||
VOLUME ["/srv/data"]
|
||||
|
||||
# The health check uses the liveness probe rather than readiness: a container that
|
||||
# is alive but not ready should stay in the load-balancer pool's view of "starting"
|
||||
# rather than be restarted, and Docker's HEALTHCHECK does not have that concept.
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||
CMD curl --fail --silent --show-error http://127.0.0.1:8080/healthz || exit 1
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/go-bananas"]
|
||||
|
||||
# --------------------------------------------------------------- final ------
|
||||
|
||||
FROM runtime AS final
|
||||
ARG VERSION=dev
|
||||
LABEL org.opencontainers.image.title="go-bananas" \
|
||||
org.opencontainers.image.description="Runtime design-token service: author themes, serve them to any application at run time." \
|
||||
org.opencontainers.image.version="${VERSION}" \
|
||||
org.opencontainers.image.source="https://github.com/urpi/go-bananas" \
|
||||
org.opencontainers.image.licenses="MIT" \
|
||||
org.opencontainers.image.base.name="docker.io/library/alpine:3.22"
|
||||
Loading…
Add table
Add a link
Reference in a new issue