ci(deploy): migrate build to docker:28.4.0-dind + GitLab container registry

This commit is contained in:
2026-08-06 10:18:00 +02:00
parent 611ae0624e
commit 26e1f31945
6 changed files with 56 additions and 147 deletions
+29 -104
View File
@@ -1,80 +1,30 @@
# GitLab-CI dev→prod pipeline for VNCmail+ — GitOps via ArgoCD.
#
# Revised after direct inspection of the real infrastructure found ArgoCD
# already installed (idle, zero Applications) on the dev-k8s-1/2/3 cluster.
# That's more idiomatic than a runner-executes-kubectl design, and it means
# this pipeline needs ZERO cluster credentials — CI only ever talks to the
# container registry and to this git repo. ArgoCD (which already has
# whatever cluster access it needs, set up once when its Applications were
# registered — see deploy/argocd/) is what actually applies anything.
#
# Design:
# - One image name, environment lives only in the tag. No more -dev/-beta
# name confusion from the old GitHub Actions workflow.
# - MR into `dev`: verify only (typecheck/lint/unit test/build check). No
# push, no deploy — this is the multi-developer merge gate.
# - Push to `dev`: build+push an immutable `sha-<sha>` tag, then commit a
# one-line tag-bump into overlays/dev/image-tag/kustomization.yaml
# (`[skip ci]`, so this doesn't retrigger itself). ArgoCD's `vncmail-dev`
# Application has automated sync it notices the git change and applies
# it. No approval needed, dev always deploys, and this job never touches
# the cluster directly.
# - Push to `main`: NEVER rebuilds. `main` only ever advances via
# - Push to `dev`: build+push an immutable `sha-<sha>` tag with Docker +
# docker-in-docker, then commit a one-line tag-bump into
# overlays/dev/image-tag/kustomization.yaml (`[skip ci]`). ArgoCD's
# `vncmail-dev` Application syncs it automatically.
# - Push to `main`: NEVER rebuilds. `main` only advances via
# `git merge --ff-only dev`, so main's HEAD commit already has a built
# image (the same sha- tag dev already deployed). This job just bumps
# overlays/prod/image-tag/kustomization.yaml to point at that same tag.
# The actual promotion gate is a HUMAN clicking Sync on the `vncmail-prod` ArgoCD
# Application (deliberately NOT automated sync) — not a GitLab manual
# job, since ArgoCD already provides that exact gate more directly.
# Until prod Stalwart/hostname/secrets are real (see VNCMAIL-SETUP.md),
# nobody should click that Sync button — but nothing here does it for
# you either way.
# image. This job just bumps overlays/prod/image-tag/kustomization.yaml
# to point at that same tag. The actual promotion gate is a HUMAN
# clicking Sync on the `vncmail-prod` ArgoCD Application.
#
# Deliberately single-platform (linux/amd64) — this pipeline serves two
# known amd64 microk8s clusters, not public multi-arch distribution (that's
# what the GHCR release workflows are for, untouched by this file).
#
# Registry history (so nobody re-litigates this from scratch). GitLab's own
# Container Registry was tried twice and does not work on this server:
#
# Round 1: registry_external_url was unset, so $CI_REGISTRY was empty and
# docker login silently fell through to Docker Hub.
# Round 2: after the server-side config, the project's sidebar DID show
# "Container Registry" and docker login DID succeed - but the push
# failed 403. Diagnosed 2026-08-05 by curling the vhost directly:
#
# $ curl -i https://registry.gitlab.vnc.biz/v2/
# www-authenticate: Bearer realm="http://gitlab.vnc.biz/jwt/auth",
# service="dependency_proxy"
# x-runtime: 0.020470
# x-gitlab-meta: {"correlation_id":...}
#
# x-runtime/x-gitlab-meta are RAILS headers, and the service is
# "dependency_proxy" - so nginx routes that hostname to the GitLab
# Rails app, which reads /v2/ as the dependency proxy (a Docker Hub
# pull-through cache), NOT to the registry container. The registry
# service was never actually wired behind that vhost. That is why
# login worked (Rails issues an unscoped dependency-proxy token)
# while a scoped :push request 403'd - the dependency proxy has no
# push concept at all.
#
# Fixing that is an nginx/omnibus change on the GitLab server (registry
# service must actually listen behind registry.gitlab.vnc.biz), not something
# any .gitlab-ci.yml can reach. Until someone does that, GHCR it is - the
# same image the sandbox already pulls, and confirmed public so the cluster
# needs no imagePullSecrets (see deploy/k8s/base/deployment.yaml).
#
# Prerequisite this file assumes (documented in VNCMAIL-SETUP.md, not
# something this file can set up itself):
# - A GitHub PAT with `write:packages` for ghcr.io/brvncde-dotcom as
# $GITLAB_CI_GHCR_TOKEN, plus the matching GitHub username as
# $GITLAB_CI_GHCR_USER — both masked+protected CI/CD variables. A GitHub
# credential can only come from GitHub; nothing GitLab-native substitutes.
# - A GitLab Runner (any kind — no cluster access needed at all now).
# Prerequisite this file assumes:
# - A GitLab Runner with Docker-in-Docker service support (Kubernetes or
# Docker executor). The `docker:28.4.0-dind` service requires privileged
# mode on most Kubernetes executors.
# - Either "allow this job token to push to this project" enabled
# (Settings → CI/CD → Job token permissions), OR a project access token
# with `write_repository` scope in $GITLAB_PUSH_TOKEN. The job below
# tries CI_JOB_TOKEN first (see the script).
# with `write_repository` scope in $GITLAB_PUSH_TOKEN. The bump jobs
# try CI_JOB_TOKEN first (see the script).
#
# deploy/k8s/ca/ (the EJBCA internal CA) is never referenced anywhere below,
# and neither ArgoCD Application in deploy/argocd/ points at it — that stays
@@ -87,11 +37,10 @@ stages:
- bump-prod
variables:
# GHCR, not GitLab's own registry - see the "Registry history" note in the
# header for the curl output proving why. Same image the sandbox already
# pulls today.
IMAGE: ghcr.io/brvncde-dotcom/vncmail-plus-dev
IMAGE: $CI_REGISTRY_IMAGE
GIT_STRATEGY: clone
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
# ---------------------------------------------------------------------------
# verify — required check on every MR into dev. No registry, no cluster.
@@ -118,45 +67,21 @@ verify:
# ---------------------------------------------------------------------------
build:
stage: build
# Kaniko builds OCI images without a Docker daemon, so it needs neither a
# dind service nor a privileged pod — GitLab's own recommended approach
# for the Kubernetes executor specifically. The docker:27-cli + dind
# combination that was here before this got as far as a successful
# $CI_REGISTRY login, then failed every way it was pointed
# (unix:///var/run/docker.sock, tcp://docker:2375, tcp://localhost:2375):
# the dind container itself was never actually listening, which on this
# executor means it needs `privileged: true` in the runner's own
# config.toml — a cluster/GitLab-admin setting outside this file's
# control. Kaniko sidesteps that requirement entirely rather than chasing
# runner permissions further, and is also the safer default on a shared
# cluster (no privileged containers at all).
image:
name: gcr.io/kaniko-project/executor:v1.23.2-debug
entrypoint: [""]
image: docker:28.4.0
services:
- docker:28.4.0-dind
rules:
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "dev"'
script:
# Fail loudly and immediately if the GHCR credentials aren't configured,
# rather than letting kaniko get all the way through a full Next.js build
# and only then 403 on push (which is exactly how the GitLab-registry
# attempt burned several pipeline runs).
- |
if [ -z "$GITLAB_CI_GHCR_TOKEN" ] || [ -z "$GITLAB_CI_GHCR_USER" ]; then
echo "ERROR: \$GITLAB_CI_GHCR_TOKEN and/or \$GITLAB_CI_GHCR_USER are not set."
echo "Add both under Settings -> CI/CD -> Variables (masked + protected)."
echo "The token is a GitHub PAT with the write:packages scope."
exit 1
fi
- mkdir -p /kaniko/.docker
- |
echo "{\"auths\":{\"ghcr.io\":{\"auth\":\"$(printf '%s:%s' "$GITLAB_CI_GHCR_USER" "$GITLAB_CI_GHCR_TOKEN" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- >
/kaniko/executor
--context "$CI_PROJECT_DIR"
--dockerfile "$CI_PROJECT_DIR/Dockerfile"
docker build
--build-arg GIT_COMMIT=$CI_COMMIT_SHA
--destination "$IMAGE:sha-$CI_COMMIT_SHORT_SHA"
--destination "$IMAGE:dev-latest"
-t "$IMAGE:sha-$CI_COMMIT_SHORT_SHA"
-t "$IMAGE:dev-latest"
.
- docker push "$IMAGE:sha-$CI_COMMIT_SHORT_SHA"
- docker push "$IMAGE:dev-latest"
# ---------------------------------------------------------------------------
# bump-dev — no cluster access. Commits the just-built tag into the overlay
@@ -176,7 +101,7 @@ bump-dev:
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
images:
- name: ghcr.io/brvncde-dotcom/vncmail-plus-dev
- name: vncmail-plus
newName: $IMAGE
newTag: $TAG
EOF
@@ -215,7 +140,7 @@ bump-prod:
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
images:
- name: ghcr.io/brvncde-dotcom/vncmail-plus-dev
- name: vncmail-plus
newName: $IMAGE
newTag: $TAG
EOF