Selfhosted Release
This section describes the Self-hosted release lifecycle managed via GitOps, covering both general availability and customer-specific deployments.
A self-hosted release always follows a cloud (SaaS) production release. The cloud release lands on master first; only after roughly one week of cloud production stability is the same version prepared for self-hosted distribution. All self-hosted release work then happens in the appcircleio/ac-script-self-hosted repository.
Release Types¶
Self-hosted releases are published in two main forms:
- General Availability (GA) Self-hosted Release
- Customer-specific Deployment Release
What does GA Self-hosted mean?
"GA" here refers to the release maturity of the self-hosted installer package — the production-grade artifact that customers download and deploy on their own infrastructure (on-prem or their own cloud account).
It is not related to my.appcircle.io, which is the dashboard URL of the SaaS (Appcircle Cloud) hosting model.
Internally, GA and RC self-hosted packages are validated on https://my.self.appcircle.io — an Appcircle-operated simulation of the self-hosted product, running on OVH as a Docker DMZ deployment (with a parallel K3s test cluster at k8s-self.appcircle.io for Kubernetes-specific behavior). See the Self-Hosted Server Release Runbook and OVH Infrastructure for details.
SaaS and Self-hosted are independent hosting options with independent release cadences — see Hosting Options.
The primary goal of the self-hosted release pipeline is to: - Take container images already tagged as latest - Retag them for a self-hosted release - Bundle them into a versioned self-hosted release package
Each package may include: - Keycloak migrations - Environment variable updates - Docker Compose files for DMZ and Single-Zone setups - Global variable structure changes - Installation and upgrade-related configuration updates
RC (Test) Release¶
(Must be completed before a GA Self-hosted Release)
Before a GA release, a Release Candidate (RC) is created for validation in self-hosted environments.
-
A release candidate branch is created from
develop
Branch convention:
release/3.29.8-rc1 -
Feature branches and pending PRs relevant to the release are merged into this RC branch.
-
A special commit message is used to trigger packaging and image tagging:
Commit message:
Bump version to 3.29.8-rc1 -
This commit triggers:
- Image retagging
-
Creation of a self-hosted RC package
-
The generated package is deployed and tested by the Self-hosted team.
If fixes or hotfixes are required:
- New RC branches are created sequentially:
release/3.29.8-rc2, release/3.29.8-rc3, ...
- Each RC is bumped, packaged, and retested until validation succeeds.
GA Self-hosted Release¶
Once RC testing is completed successfully, the GA self-hosted release can be started.
-
GitFlow is recommended for this step.
-
A release branch is created from
develop:
Branch convention:
release/3.29.8git flow release start 3.29.8 -
Release-related feature branches and PRs are merged into this branch.
-
A version bump commit is pushed to trigger packaging:
Commit message:
Bump version to 3.29.8 -
This commit triggers:
- Tagging container images with the GA version
-
Creation of the final self-hosted GA package
-
The release is finalized using:
git flow release finish 3.29.8 -
This merges the release into both:
developmaster
⚠️ Notes:
- Push permissions may be required after
git flow finish(default branch protections may block automatic pushes). - The
masterpipeline may be triggered but does not affect the release outcome. - Ensure tags are correctly pushed to
master. - The
developpipeline should also start for the released version (e.g.3.29.8).
Packages / Offline Installation¶
Develop Packages¶
Develop packages can be found in cloud storage under:
appcircle-dev-common/self-hosted/appcircle/
- These packages are identified by commit ID suffix.
- Example:
appcircle-server-linux-x64-3.29.8-a1fe25c-230.zip
Built by:
- Jenkins job: appcircle-backend/ac-script-self-hosted/develop
- Source repo / branch: appcircleio/ac-script-self-hosted — develop branch
- Shared pipeline lib: appcircleio/ac-lib-pipeline — develop branch
- Runs dev-deps/pipeline-scripts/create-self-hosted-package.sh, which reads the base version from ac-self-hosted.sh (e.g. 3.29.8), appends -<shortSha>-<buildNumber>, and re-zips the package. The "Docker Images Self Hosted Tags & Offline Packages" stage is skipped on develop.
Characteristics: - ❌ Does not contain offline installation packages - ✅ Contains only tagged images - ⚡ Completes faster than normal release pipelines
GA & RC Release Packages¶
GA and RC release packages can be found in cloud storage under:
appcircle-self-hosted/server-images/
- These packages include offline installation packages (zipped server images).
- Example directory:
appcircle-self-hosted/server-images/v3.29.6/
Built by:
- Jenkins job: appcircle-backend/ac-script-self-hosted/release/<version>[-rcN] (e.g. release/3.29.6)
- Source repo / branch: appcircleio/ac-script-self-hosted — release/<version> and release/<version>-rcN branches
- Shared pipeline lib: appcircleio/ac-lib-pipeline — develop branch
- On release branches the pipeline also runs the "Docker Images Self Hosted Tags & Offline Packages" stage: every image in docker-images.txt is pulled from europe-west1-docker.pkg.dev/appcircle/docker-registry/*, retagged with :v<version>, exported as a .tar.gz, and bundled into appcircle-server-services-v<version>-offline.zip alongside a -digests.txt file.
Characteristics: - ✅ Contains offline installation packages - ✅ Available for both RC and GA releases - ⏱️ Pipeline takes longer than develop releases
| Package Type | Location | Contains Offline Images | Pipeline Duration |
|---|---|---|---|
| Develop | appcircle-dev-common/self-hosted/appcircle/ |
❌ No | ⚡ Fast |
| RC / GA | appcircle-self-hosted/server-images/ |
✅ Yes | ⏱️ Longer |
Self-hosted Deployment Release (Per Customer)¶
This phase is managed by the Self-hosted DevOps team.
- Customer-specific differences are handled in separate branches.
One branch per customer¶
Each customer has a dedicated branch in appcircleio/ac-script-self-hosted using the convention deploy/<company-name>. Variants (per environment, per hotfix, per pinned version) can be created off of it as needed.
What the customer branch contains¶
Each branch carries the customer's deltas on top of release/<version>:
- License files
- Custom env-vars /
global.yamloverrides - DMZ / network templates tuned for their topology
- Patched
docker-images.txt(sometimes pointing at custom image tags only that customer uses — e.g.halkbankotpservice,ziraatsmsservice) - Customer-specific hotfix patches that haven't (or won't) be merged upstream
- A
.UUIDfile — this is the key that routes the build to the customer's private bucket (see below)
The .UUID file → private GCS folder per customer¶
The packaging script (dev-deps/pipeline-scripts/create-self-hosted-package.sh) checks for a .UUID file at the root of the branch.
When .UUID exists on the branch:
- The package is uploaded to
gs://appcircle-self-hosted/<UUID>/— a folder named after the customer's UUID, not their name (keeps the customer identity opaque in object paths). - The script fetches the existing
index.txtfrom that folder, appends the new package filename, and uploads it back. This produces a running ledger of every package shipped to that customer.
When .UUID is absent (e.g. develop, release/*), the script falls through to the public develop/prod common bucket instead.
Version handling on deploy/* branches¶
On customer branches the script does not rewrite the version= line inside ac-self-hosted.sh. The zip filename still carries a -<shortSha>-<buildNumber> suffix for traceability, but when the customer runs ./ac-self-hosted.sh --version on their host they see the clean base version (e.g. 3.29.4) — not the build-suffixed string.
Updating customer branches after a release¶
When a self-hosted release lands on master, every customer branch (deploy/<company-name>) needs to be updated. This is driven by a local helper script (run by Berk) that iterates over all customer branches and applies one of two flows depending on the branch state.
Flow A — straight master merge (the common case)
- The script inspects each customer branch's last commit.
- If the last commit is a master merge commit, the branch is clean — the script simply merges the latest
masterinto the customer branch.
Flow B — customer has a hotfix on a backend dependency
In some cases a customer-specific hotfix has been applied to one of the backend service dependencies (e.g. a patched image tag in docker-images.txt). For these branches the flow is manual:
- Manually check whether a newer release of that hotfixed dependency exists.
- If yes, remap the customer branch to point at the latest dependency version.
- Then merge the remaining
masterchanges into the customer branch.
After the merge (both flows)
- Pushing the updated customer branch triggers the Jenkins multibranch pipeline for that branch.
create-self-hosted-package.shruns inside the job and uploads the installer zip to the customer's private bucket (gs://appcircle-self-hosted/<UUID>/).- From this point on, deploying the package on the customer's infrastructure is the customer's responsibility.
This approach keeps the core release flow clean while allowing controlled, auditable customer-level customization.