Skip to content

Build and Deployment

This document covers the build pipeline for Kotlin-based components and the Helm-based deployment mechanism used to install components into Kubernetes environments.

The build and deployment pipeline is an architectural element of the platform, not merely an operational convenience. It bridges the Source/Development viewpoint (repositories, branches, PRs) and the Runtime viewpoint (environments, pods, services) through a deterministic sequence of transformations.

Four Purposes

Each deployment target serves a distinct role in the development lifecycle:

PurposeInfrastructureURLRole
PersonalLocal machine / Docker DesktoplocalhostShort development loop; no shared state
DevelopmentAlpha002/devdev.app.arda.cardsIntegration testing on production-like infrastructure
StageAlpha002/stagestage.app.arda.cardsRegression testing before production promotion
ProductionAlpha001/prodapp.arda.cardsBusiness operations

Pipeline Guarantees

The pipeline enforces these invariants across all purposes:

  • Reproducibility: Every artifact is built from a tagged commit; the same commit always produces the same artifact.
  • Immutability: Published artifacts (Docker images, Helm charts) are never overwritten; versions are monotonically increasing.
  • Traceability: Every deployed artifact can be traced back to its source commit, PR, and build run.
  • Isolation: Each purpose has its own infrastructure partition; a failed dev deployment cannot affect production.

Promotion Path

Artifacts flow through purposes in order: Personal → Development → Stage → Production. Promotion between purposes is gated by automated checks (tests, linting) and manual approval (deployment request workflows).

These guarantees are what make the pipeline an architectural element: they define the system’s change propagation model and are as load-bearing as the module boundaries or the persistence layer.

Each component repository uses Gradle with Kotlin DSL (build.gradle.kts).

TargetDescription
./gradlew clean buildCompile, run unit tests, assemble JARs
./gradlew clean helmInstallToLocalDeploy to local Kubernetes cluster (dev only)
./gradlew clean dockerBuildBuild Docker image
./gradlew clean dockerPushPush image to the artifact registry

The lintValues map in build.gradle.kts provides placeholder values for all application.conf keys that are injected at deploy time. This allows ./gradlew build to succeed without a live environment:

val lintValues = mapOf(
"path.to.key.extras.some.myKey" to "lint-value-placeholder"
)

Components are deployed as Helm charts. The helm-deploy-pipeline-action GitHub Action is the standard deployment mechanism.

src/helm/
├── Chart.yaml
├── values.yaml
├── read-cloudformation-values.cmd # Maps CFn exports to Helm values
└── templates/
├── deployment.yaml
├── service.yaml
├── ingress.yaml
└── configmap.yaml

At deploy time, read-cloudformation-values.cmd reads CloudFormation exports from the target Infrastructure and Partition and writes them into Helm values:

readExport path.to.key.extras.some.myKey ${PURPOSE}-API-MyKey

The configmap.yaml template injects the resolved values into the component’s application.conf at container start:

override.properties: |
path.to.key.extras.some.myKey={{ .Values.system.reference.item.extras.myKey | required "myKey" }}
SourceValues
values.yamlDefault / static configuration
read-cloudformation-values.cmdEnvironment-specific runtime values (DB endpoints, API URLs, Cognito config)
GitHub Actions secretsCredentials and sensitive values

To add a new configuration key from a CloudFormation export:

  1. Add readExport line in src/helm/read-cloudformation-values.cmd
  2. Add the key with empty default in application.conf
  3. Add the key injection in configmap.yaml
  4. Add the key with a placeholder to lintValues in build.gradle.kts
  5. Run ./gradlew clean build to verify lint passes
  6. Run ./gradlew clean helmInstallToLocal to verify local deployment
  • First API test run flakiness: The first run against a freshly deployed pod may fail due to startup timing. Retry after 5 seconds before investigating.
  • Upload test infrastructure: API tests for file upload features require S3/LocalStack. Pass --ignore Upload in environments without S3.
  • Mock cascade failures: Adding a method to a widely-mocked interface called from an init block causes widespread test failures. Fix by stubbing the new method in all mockk<ServiceType>() usages across the codebase.