Files
SRCmail/.gitlab-ci.yml
T
Stefan-Sanger dfa5667548 fix(ci): point DinD at the docker service alias, not localhost
The build job failed with:
  Cannot connect to the Docker daemon at tcp://localhost:2375.
  Is the docker daemon running?

Three things were wrong:

1. DOCKER_HOST was set to tcp://localhost:2375. The DinD daemon runs in
   the service sidecar container, not in the build container, so
   localhost was always going to refuse the connection. The correct host
   is the service alias 'docker'.

2. The docker:28.4.0-dind service was declared without an explicit
   alias. Without alias: docker, GitLab derives the hostname from the
   image string 'docker:28.4.0-dind', and since ':' is invalid in DNS,
   the 'docker' hostname never resolves. The explicit alias is required
   for tcp://docker:2375 to work at all.

3. docker:28.4.0-dind enables TLS by default and listens on 2376, but
   DOCKER_HOST points at 2375. Setting DOCKER_TLS_CERTDIR="" disables
   TLS so the daemon listens on plaintext 2375, matching DOCKER_HOST.

This mirrors the known-working pattern in the vnc-localidp pipeline
(docker:20.10.17-dind + alias: docker + DOCKER_HOST=tcp://docker:2375
+ DOCKER_TLS_CERTDIR=""). The TLS-defaults behavior has been unchanged
since docker 19.03, so the same pattern applies on 28.4.0.
2026-08-06 11:25:33 +02:00

164 lines
6.9 KiB
YAML

# GitLab-CI dev→prod pipeline for VNCmail+ — GitOps via ArgoCD.
#
# Design:
# - 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 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. 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).
#
# 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 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
# a fully manual, human-only runbook (see deploy/k8s/ca/README.md).
stages:
- verify
- build
- bump-dev
- bump-prod
variables:
IMAGE: $CI_REGISTRY_IMAGE
GIT_STRATEGY: clone
DOCKER_DRIVER: overlay2
# DinD service is reached at the `docker` alias (set explicitly on the
# service below), not localhost. TLS disabled so the daemon listens on
# plaintext 2375 — same pattern as the working vnc-localidp pipeline.
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
# ---------------------------------------------------------------------------
# verify — required check on every MR into dev. No registry, no cluster.
# ---------------------------------------------------------------------------
verify:
stage: verify
image: node:24-alpine
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
script:
- npm ci
- npm run typecheck
- npm run lint
- npm run test:translations
- npm run build
# test:integration is deliberately NOT here — it spins up a real Stalwart
# fixture via docker-compose, which needs an actual Docker daemon this
# runner's Kubernetes executor doesn't provide without privileged mode
# (see the build job below). Candidate for a separate scheduled job on a
# differently-configured runner, not a blocker on every MR.
# ---------------------------------------------------------------------------
# build — push to dev only. Builds once; main never rebuilds (see header).
# ---------------------------------------------------------------------------
build:
stage: build
image: docker:28.4.0
services:
- name: docker:28.4.0-dind
alias: docker
rules:
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "dev"'
before_script:
- until docker info; do sleep 1; done
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
script:
- >
docker build
--build-arg GIT_COMMIT=$CI_COMMIT_SHA
-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
# ArgoCD watches; ArgoCD's automated sync does the actual apply.
# ---------------------------------------------------------------------------
bump-dev:
stage: bump-dev
image: alpine/git:2.47.0
rules:
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "dev"'
script:
- TAG="sha-$CI_COMMIT_SHORT_SHA"
- |
cat > deploy/k8s/overlays/dev/image-tag/kustomization.yaml <<EOF
# Owned by CI (bump-dev job in .gitlab-ci.yml) - regenerated every
# push to dev. Do not hand-edit; edits here get overwritten.
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
images:
- name: vncmail-plus
newName: $IMAGE
newTag: $TAG
EOF
- git config user.name "vncmail-ci"
- git config user.email "ci@vnc.biz"
- git add deploy/k8s/overlays/dev/image-tag/kustomization.yaml
- |
if git diff --cached --quiet; then
echo "No change (tag already pinned) - nothing to commit"
else
git commit -m "chore(deploy): pin dev to $TAG [skip ci]"
git push "https://gitlab-ci-token:${GITLAB_PUSH_TOKEN:-$CI_JOB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" HEAD:dev
fi
# ---------------------------------------------------------------------------
# bump-prod — no cluster access, no rebuild. Points overlays/prod at the
# exact tag already running on dev. Does NOT deploy anything: vncmail-prod's
# ArgoCD Application has manual sync, so this only prepares what a human
# would be syncing, it doesn't sync it.
# ---------------------------------------------------------------------------
bump-prod:
stage: bump-prod
image: alpine/git:2.47.0
rules:
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "main"'
script:
- TAG="sha-$CI_COMMIT_SHORT_SHA"
- echo "main advanced to $CI_COMMIT_SHA (must be a dev commit, ff-only) - that image already exists as $IMAGE:$TAG"
- |
cat > deploy/k8s/overlays/prod/image-tag/kustomization.yaml <<EOF
# Owned by CI (bump-prod job in .gitlab-ci.yml) - regenerated every
# push to main. Do not hand-edit; edits here get overwritten. Bumping
# this is NOT the same as deploying it - vncmail-prod's ArgoCD
# Application has manual sync, see the note in the parent
# kustomization.yaml.
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
images:
- name: vncmail-plus
newName: $IMAGE
newTag: $TAG
EOF
- git config user.name "vncmail-ci"
- git config user.email "ci@vnc.biz"
- git add deploy/k8s/overlays/prod/image-tag/kustomization.yaml
- |
if git diff --cached --quiet; then
echo "No change (tag already pinned) - nothing to commit"
else
git commit -m "chore(deploy): point prod overlay at $TAG (not synced - manual gate in ArgoCD) [skip ci]"
git push "https://gitlab-ci-token:${GITLAB_PUSH_TOKEN:-$CI_JOB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" HEAD:main
fi