Skip to content

Backend Service Release

This page describes how individual Appcircle backend services (ac-server-*, ac-service-*, ac-lib-*) are built and released. Each service is its own repository, its own Jenkins multibranch pipeline, and its own versioning track — independent from the self-hosted server release version.

Tags: backend-release docker-image jenkins artifact-registry

Service version ≠ Self-hosted version

Each backend service has its own semver, independent from the self-hosted server version. The mapping between a self-hosted version (e.g. 3.30.0) and the service versions it ships is tracked in the release manifest described in Patch Release → Source of Truth, not in this page. Always cross-reference that manifest when picking a service branch.

Repo name ≠ image name

The Git repo and the Docker image often have different names. For example, ac-server-apigateway ships as privateapigateway, ac-server-build as buildserver. Inside this page, <service> refers to the repo (Jenkins job folder) and <image> refers to the published image name in the registry — they may differ.

Scope

This process applies to backend services under appcircle-backend/ in Jenkins — for example:

  • ac-server-apigateway
  • ac-server-resource
  • ac-server-build
  • ac-server-license
  • ac-server-notification
  • ac-server-distribution-webapi
  • ac-service-publish
  • ac-service-agent-cache
  • (and their ac-lib-* shared libraries)

Each has the same shape: one Git repo, one Jenkins multibranch pipeline, one image in the registry.

Branch Strategy

Each service repo uses a four-track convention. The Jenkins multibranch pipeline picks up every branch matching these patterns:

Branch Purpose Image tag(s) produced
develop Active development; every push builds an alpha image <semver>-alphaNNNN, alpha-latest, develop-b.<build>, commit-<sha>
master Production-ready code <semver>, latest, commit-<sha>
release/<x.y.z> A specific service version, frozen for the self-hosted release that uses it <x.y.z>-betaNNNN, beta-latest, release-<x.y.z>-b.<build>, commit-<sha>

Patch / hotfix flow

Patch branches follow the policy in Patch Release. NuGet refs must be pinned, images must be manually versioned with -hotfixN suffixes, and the corresponding docker-images.txt entry in ac-script-self-hosted is updated by hand.

What the Pipeline Does

Each appcircle-backend/<service>/<branch> build runs the same declarative stages:

1. Checkout

Standard git checkout of the branch and commit.

2. Docker Build

Most services are .NET 8 web APIs built from a service-specific Dockerfile. They use a shared chiseled distroless base image from the Appcircle Artifact Registry:

europe-west1-docker.pkg.dev/appcircle/docker-registry/appcircle-dotnet-aspnet:8.0-noble-chiseled-extra

Typical steps inside the Dockerfile:

  1. dotnet restore using .nuget/nuget.config (authenticated GitHub Packages source)
  2. dotnet build -c Release -a <arch>
  3. dotnet publish -c Release -o /app/publish /p:UseAppHost=false
  4. Final stage copies the published output into the chiseled base

Some services (e.g. those that need to run on ARM hosts) produce a multi-arch image covering linux/amd64 and linux/arm64; others build a single-arch image. The build configuration is a per-service choice in its Dockerfile / Jenkinsfile.

3. Push to GCP Artifact Registry

The resulting image is pushed to europe-west1-docker.pkg.dev/appcircle/docker-registry/<image> with multiple tags in a single push, so downstream consumers can pin at the granularity they need:

Tag pattern Example Purpose
<semver>-alphaNNNN (develop) / <semver>-betaNNNN (release) / <semver> (master) buildserver:2.6.66-alpha0040 Immutable version-pinned build
alpha-latest / beta-latest / latest privateapigateway:alpha-latest Moving tag for the current branch class
<branch-class>-b.<build> buildserver:develop-b.852, resourceserver:release-1.9.10-b.3 Branch + Jenkins build number
commit-<sha12> resourceserver:commit-e5f2a51df9f4 Immutable commit pin
<semver>-<base-variant> (when applicable) resourceserver:1.9.10-beta0005-8.0-noble Build pinned to specific base image variant

The develop branch always produces alpha tags; release/* branches produce beta tags; master produces stable/release tags (latest, <semver>).

4. Docker Image Security Scan

A Trivy scan stage runs on most builds — pulls the freshly pushed image, downloads the Trivy vulnerability DB, and scans it for OS and language-level CVEs (ubuntu packages + dotnet-core dependencies). Results are archived as Jenkins artifacts.

The stage is gated by a when conditional and may be skipped on certain branches (observed: skipped on some release/* builds). When skipped, the log shows Stage "Docker Image Security Scan" skipped due to when conditional.

5. Cloud k8s Cluster Deployment Patch

After a successful image push, the pipeline auto-rolls the corresponding deployment in the appropriate cloud Kubernetes cluster so the new image is picked up immediately:

  • develop builds → patch the dev cluster (e.g. dev-appcircle in the dev-appcircle GCP project, region europe-west1-c)
  • release/* builds → patch the prep cluster (e.g. prep-appcircle in the dev-appcircle GCP project, region europe-west3-c)
  • master builds → patch the prod cluster

6. Post Actions

  • Local Docker image cleanup on the agent
  • slackSend to #pipeline with build status

How This Feeds the Self-Hosted Release

Backend service builds and self-hosted releases are decoupled:

  1. Service teams release their services on their own cadence by cutting a release/<service-semver> branch in each service repo.
  2. The resulting image sits in the Artifact Registry with all its tags, ready to be consumed.
  3. When a self-hosted release version is cut (e.g. 3.30.0), the corresponding release manifest captures which service version is included for each service.
  4. The ac-script-self-hosted pipeline (see Self-hosted Release) reads docker-images.txt, pulls each *:latest (or hotfix-tagged) image, retags them with the self-hosted version (v3.30.0), and bundles them into the offline package.

So a service's Jenkins job never produces a self-hosted artifact directly — it produces a Docker image that the self-hosted pipeline later consumes.

Rules (Critical)

  • ✅ Every service has its own semver, independent from the self-hosted release version.
  • ✅ Service release branches are named release/<service-semver> (not release/<self-hosted-version>).
  • ✅ NuGet refs must be pinned on release branches.
  • ✅ Image tags are immutable — fixes go into new tags.
  • ✅ The release manifest is the source of truth for which service version is part of which self-hosted release.
  • ❌ Do NOT build production images from feature branches.
  • ❌ Do NOT push images with mutable tags (latest, beta-latest) for self-hosted consumption — the self-hosted pipeline pins them at packaging time.
  • ❌ Do NOT roll back by force-pushing Git history; roll back by tag.

Principle

A backend service release is complete when the image is in the registry with immutable tags, the prep cluster is healthy, and the version is recorded in the corresponding release manifest.