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-apigatewayac-server-resourceac-server-buildac-server-licenseac-server-notificationac-server-distribution-webapiac-service-publishac-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:
dotnet restoreusing.nuget/nuget.config(authenticated GitHub Packages source)dotnet build -c Release -a <arch>dotnet publish -c Release -o /app/publish /p:UseAppHost=false- 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:
developbuilds → patch the dev cluster (e.g.dev-appcirclein thedev-appcircleGCP project, regioneurope-west1-c)release/*builds → patch the prep cluster (e.g.prep-appcirclein thedev-appcircleGCP project, regioneurope-west3-c)masterbuilds → patch the prod cluster
6. Post Actions¶
- Local Docker image cleanup on the agent
slackSendto#pipelinewith build status
How This Feeds the Self-Hosted Release¶
Backend service builds and self-hosted releases are decoupled:
- Service teams release their services on their own cadence by cutting a
release/<service-semver>branch in each service repo. - The resulting image sits in the Artifact Registry with all its tags, ready to be consumed.
- 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. - The
ac-script-self-hostedpipeline (see Self-hosted Release) readsdocker-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>(notrelease/<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.