diff --git a/Dockerfile.runtime b/Dockerfile.runtime new file mode 100644 index 0000000..0cec287 --- /dev/null +++ b/Dockerfile.runtime @@ -0,0 +1,6 @@ +# Jenkins compiles on its scratch PVC; image assembly needs no toolchain layers. +FROM gcr.io/distroless/static-debian12:nonroot@sha256:afa5c872c891853ca7fcf1f12c3edb23f7eeef36189728842dd51042ff57f7ab +COPY soteria /soteria +USER 65532:65532 +EXPOSE 8080 +ENTRYPOINT ["/soteria"] diff --git a/Jenkinsfile b/Jenkinsfile index 78c4d09..1431dba 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -49,7 +49,7 @@ spec: resources: requests: cpu: 100m - memory: 1Gi + memory: 512Mi limits: cpu: 1500m memory: 2Gi @@ -63,6 +63,19 @@ spec: command: - cat tty: true + resources: + requests: {cpu: 200m, memory: 512Mi} + limits: {cpu: 1500m, memory: 2Gi} + volumeMounts: + - name: workspace-volume + mountPath: /home/jenkins/agent + - name: ui-builder + image: node:22-alpine@sha256:0a7108bf6c7bf5de370ffb1a3ed6be93d405b43ff159f681a8d18c0e2bc2e402 + command: [cat] + tty: true + resources: + requests: {cpu: 25m, memory: 64Mi} + limits: {cpu: 1000m, memory: 512Mi} volumeMounts: - name: workspace-volume mountPath: /home/jenkins/agent @@ -545,6 +558,26 @@ PY } } } + stage('Build release on scratch') { + when { + expression { return params.PUBLISH_IMAGES } + } + steps { + container('ui-builder') { + sh 'cd web && npm ci && npm run build' + } + container('tester') { + // Reuse the tested Go cache and put compiler writes on the workspace PVC. + sh ''' + set -eu + mkdir -p build/image internal/server/ui-dist + cp -R web/dist/. internal/server/ui-dist/ + CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -p 2 -buildvcs=false -trimpath -ldflags="-s -w" -o build/image/soteria ./cmd/soteria + cp Dockerfile.runtime build/image/Dockerfile + ''' + } + } + } stage('Build & push image') { when { expression { return params.PUBLISH_IMAGES } @@ -561,11 +594,8 @@ PY printf '{"auths":{"registry.bstein.dev":{"auth":"%s"}}}\\n' "${auth}" > /kaniko/.docker/config.json trap 'rm -f /kaniko/.docker/config.json' EXIT /kaniko/executor \ - --context "${WORKSPACE}" \ - --dockerfile "${WORKSPACE}/Dockerfile" \ - --build-arg BUILDPLATFORM=linux/arm64 \ - --build-arg TARGETOS=linux \ - --build-arg TARGETARCH=arm64 \ + --context "${WORKSPACE}/build/image" \ + --dockerfile "${WORKSPACE}/build/image/Dockerfile" \ --destination "registry.bstein.dev/bstein/soteria:${VERSION_TAG}" \ --destination registry.bstein.dev/bstein/soteria:latest ''' diff --git a/deploy/NOTES.md b/deploy/NOTES.md index 7ab35f2..322c873 100644 --- a/deploy/NOTES.md +++ b/deploy/NOTES.md @@ -14,3 +14,15 @@ RoleBindings in each backup/restore target namespace: Job creation and the credential-copy operations in `internal/k8s/jobs.go`. Those grants are not part of this Longhorn deployment. Review credential distribution before enabling restic; Kubernetes RBAC cannot restrict Secret creation by resource name. + +## CI image construction + +Jenkins compiles the UI and ARM64 Go binary in its `ci-scratch` workspace PVC. +Compiler and package caches also use that PVC. The final Kaniko step receives +only `build/image/` and `Dockerfile.runtime`, so it packages the binary without +unpacking Node/Go toolchains or compiling on the node's runtime filesystem. +The ordinary multistage `Dockerfile` remains available for workstation builds. + +Keep `CGO_ENABLED=0`, the UI embedding step and the runtime architecture aligned. +The runtime image stays nonroot and exposes the same port and entrypoint. Image +publication still requires all existing quality and supply-chain gates.