Calq Relay
Calq Relay is a fully managed enterprise serverless platform. Designed for multi-cloud, multi-environment application delivery without platform engineering.
Comparison
Deployment & Delivery
| Feature | Calq Relay | Internal Developer Platforms | GitOps Controllers | Managed Container Platforms | Continuous Delivery Platforms | CI/CD Pipelines (general) | Infrastructure-as-Code Platforms |
|---|---|---|---|---|---|---|---|
| Single-command deploy (source → running) | ✅ build + push + sync | ❌ platform setup first | ❌ commit + wait for sync | ✅ source deploy | ❌ pipeline authoring | ❌ pipeline authoring | ❌ infra only |
| Zero-config for .NET projects | ✅ auto Dockerfile + manifests | ❌ | ❌ | ⚠️ buildpacks (not .NET) | ❌ | ❌ | ❌ |
| No Git commits for image updates | ✅ Application spec override | ✅ API-driven | ❌ Git commit by design | ✅ API-driven | ✅ artifact-based | ✅ artifact-based | ❌ |
| Artifact promotion (cross-cloud, no rebuild) | ✅ cross-registry import + sync | ❌ | ❌ | ❌ single cloud | ⚠️ no cross-registry import | ⚠️ custom scripting | ❌ |
| PR preview environments (full stack) | ✅ namespace clone, all services | ✅ ephemeral environments | ❌ | ⚠️ per-service only | ❌ | ⚠️ custom scripting | ❌ |
| Blue-green switchover | ✅ Service selector patch | ❌ | ❌ | ✅ revision routing | ✅ native | ❌ | ❌ |
| Canary without service mesh | ✅ replica ratio + enforcement | ❌ | ❌ | ✅ managed traffic split | ✅ replica-based | ❌ | ❌ |
| Canary drift protection | ✅ continuous enforcement | ❌ | ❌ | ✅ managed split persists | ❌ one-time stage | ❌ | ❌ |
| AI-operability | ✅ minimal CLI, JSON output | ⚠️ complex API | ⚠️ YAML + Git | ✅ documented CLI | ⚠️ complex API + DSL | ⚠️ pipeline DSL | ⚠️ HCL/DSL |
| No dedicated infrastructure | ✅ CLI tool only | ⚠️ SaaS / self-hosted split | ❌ controller pods | ✅ fully managed | ⚠️ SaaS + delegate / cluster | ✅ fully managed | ⚠️ state backend |
| Cluster-wide instant rolling updates | ✅ anti-affinity + maxSurge 100% | ❌ | ⚠️ user-configured | ❌ platform-managed | ⚠️ k8s default | ❌ | ❌ |
| Idempotent operations | ✅ all commands re-runnable | ⚠️ varies | ✅ reconciliation loop | ✅ declarative | ⚠️ may duplicate | ⚠️ depends on pipeline | ✅ plan + apply |
Platform Provisioning & Multi-Cloud
| Feature | Calq Relay | Internal Developer Platforms | GitOps Controllers | Managed Container Platforms | Continuous Delivery Platforms | Infrastructure-as-Code Platforms |
|---|---|---|---|---|---|---|
| Single-command cluster provisioning | ✅ cluster + registry + DNS + TLS + GitOps | ❌ separate provisioning | ❌ not in scope | ✅ no cluster needed | ❌ assumes infra exists | ⚠️ multi-resource modules |
| Multi-cloud support (built-in) | ✅ GCP + Azure + any via config | ⚠️ vendor-specific | ✅ cloud-agnostic | ❌ single cloud | ✅ multi-cloud native | ✅ multi-provider |
| Custom cloud provider integration | ✅ AuthCommand + templates | ⚠️ plugin system | ✅ k8s-agnostic | ❌ | ✅ provider accounts | ✅ provider plugins |
| Cross-cloud resilience (same environment) | ✅ multiple providers per env | ❌ | ⚠️ manual multi-cluster | ❌ | ⚠️ multi-region only | ⚠️ user-orchestrated |
| Terraform coexistence | ✅ add/install on pre-existing | ⚠️ overlapping concerns | ✅ separate layers | ❌ | ✅ infra-agnostic | ✅ native |
| Platform components auto-installed | ✅ ArgoCD, cert-manager, ExternalDNS | ⚠️ varies | ❌ user responsibility | ✅ fully managed | ❌ | ❌ |
| Organization config sharing | ✅ config push/pull via Git | ✅ centralized catalog | ❌ | ❌ | ⚠️ pipeline templates | ✅ module registries |
Scaling & Runtime Operations
| Feature | Calq Relay | Internal Developer Platforms | GitOps Controllers | Managed Container Platforms | Continuous Delivery Platforms | Kubernetes Raw (manual) |
|---|---|---|---|---|---|---|
| Grouped multi-service scaling | ✅ coordinated one-pod-per-node | ❌ | ❌ | ❌ per-service only | ❌ | ❌ manual coordination |
| Auto-tuned resource requests | ✅ CronJob + metrics patching | ⚠️ recommendations only | ❌ | ✅ fully managed | ❌ | ⚠️ VPA (separate install) |
| Pod recycling / JIT warmth | ✅ cluster-wide CronJob | ❌ | ❌ | ❌ | ❌ | ❌ |
| Canary drift protection | ✅ continuous enforcement | ❌ | ❌ | ✅ managed split persists | ❌ | ❌ |
| Configuration hot reload | ✅ ConfigMap volume + sync | ⚠️ platform restart | ⚠️ depends on sync | ⚠️ requires restart | ❌ | ✅ native ConfigMap |
| Secrets sync (GitHub → k8s) | ✅ automatic K8S_ prefix | ✅ External Secrets Operator | ❌ | ✅ managed secrets | ⚠️ pipeline injection | ⚠️ manual or ESO |
Table of Contents
- Usage - Calq Relay
- Quick Start
- License
Usage - Calq Relay
1. Foundations
1.1 CLI surface
dotnet tool install --global CalqFramework.Relay.Cli
In GitHub Actions:
- uses: calq-framework/relay@latest
with:
command: deploy --service web --environment dev
Prerequisites: kubectl, docker, gh, helm, argocd on PATH. Cloud CLI: gcloud + gke-gcloud-auth-plugin (GCP) or az (Azure).
Key points:
- Single binary, no runtime dependencies beyond standard cloud CLIs
- All subcommands accept
--dry-runfor safe preview - Designed for both human operators and CI/CD automation
- Minimal command surface operable by AI agents — unlike platforms that expose hundreds of API endpoints
See also: 1.2 JSON output
1.2 JSON output
All subcommands return JSON on stdout. Diagnostic output goes to stderr.
{
"Service": "web",
"Operation": "promote",
"SourceEnvironment": "dev",
"TargetEnvironment": "prod",
"ImageUrl": "acrprod.azurecr.io/web:a1b2c3d4e5f6",
"SyncStatus": "healthy",
"DryRun": false
}
Key points:
- Machine-readable output enables pipeline composition and status checks
- Stderr carries human-readable progress and diagnostics
DryRun: truepreviews the operation without applying changes
See also: 1.1 CLI surface
1.3 Platform config
All platform state is stored in .relay/relay.json. This file is the single source of truth for environments, clusters, services, scaling, and build configuration.
{
"Name": "my-platform",
"ArgoCD": { "Namespace": "argocd", "CanaryEnforcement": true, "PodRecycling": true },
"Environments": {
"dev": {
"Clusters": { "gke-dev": { "Provider": "gcp", "Project": "my-project", "Region": "us-central1" } },
"Registry": { "Provider": "gar", "Name": "my-project", "Region": "us-central1" }
}
},
"NodePools": { "critical": { "Scaling": "Grouped", "MinNodes": 2, "MaxNodes": 10 } },
"Services": {
"web": { "NodePool": "critical", "Build": { "Dockerfile": "", "Context": "." } }
}
}
Key points:
- Created automatically by
service add— no manual authoring required - Convention-based defaults: registry name, resource group, and region are inferred from the cluster config
- Monorepo support: each service stores its project path for targeted builds
- Blue-green services tracked via
BlueGreen: trueflag
See also: 1.1 CLI surface
1.4 Deployment lifecycle
Calq Relay orchestrates the full deployment lifecycle across environments:
cluster create → service add → deploy → promote/stage → switchover
- Add Service: Detects the .NET project, scaffolds Kustomize manifests (Deployment with cluster-wide anti-affinity for instant rolling updates, Service with cloud-specific annotations, optional Ingress with TLS), generates ArgoCD Application manifests, and creates the platform config.
- Deploy: Generates a Dockerfile if missing (.NET auto-detection), builds and pushes the container image tagged with the Git SHA, sets the image override on the ArgoCD Application, and syncs. No Git commits — ArgoCD stores the image override in its Application spec.
- Promote: Reads the source image from the cluster, imports across registries automatically (handles cross-cloud: GCP→Azure, Azure→GCP), sets the image override on the target ArgoCD Application, and syncs.
- Stage: Deploys to the inactive slot (blue or green) for verification before switchover.
- Switchover: Patches the Service selector from the active slot to the inactive slot — instant traffic switch with no IP or DNS change. Pre-scales the inactive slot to match active replicas first.
- Restart: Patches the deployment with a unique version label and pod anti-affinity — all new pods launch simultaneously on different nodes (cluster-wide parallel restart, not sequential).
- Pod Recycling: Cluster-wide CronJob continuously rotates which pod the autoscaler prefers to kill — newest pods are recycled first, keeping warmer JIT-compiled pods serving traffic longer while eliminating frozen or degraded pods that would otherwise require manual intervention.
- Canary Enforcement: Cluster-wide CronJob reads
relay.calq.io/canary-weightannotations and continuously scales both slot deployments to maintain the desired traffic ratio — compensating for HPA drift, pod crashes, and node preemption. No service mesh required.
Key points:
- All operations are idempotent — safe to re-run after partial failure
- .NET projects get zero-config deployment; other languages require only a Dockerfile and
--name - No service mesh, no sidecars, no runtime infrastructure — CLI tool only
See also: 1.1 CLI surface
2. CI/CD Pipeline
2.1 Build automation
calq-relay deploy --service myapp --environment dev
What deploy does:
- Resolves the Dockerfile (explicit path from config, existing
Dockerfile, or auto-generated for .NET) - Scaffolds Kustomize manifests if missing
- Builds the container image (configurable build command)
- Pushes to the environment's registry
- Sets the image override on the ArgoCD Application (no Git commits)
- Triggers ArgoCD sync and waits for healthy
Key points:
- .NET projects get zero-config deployment — Dockerfile and manifests are auto-generated
- For other languages, provide a Dockerfile and set the service name
- Image tagged with 12-character Git SHA by default — configurable via
Build.Tag - Build and push commands are fully customizable via
.relay/relay.json
See also: 4.1 Build configuration, 5.4 Dockerfile generation
2.2 Artifact promotion
calq-relay promote --service web --source dev --target prod
Reads the source image from the cluster, imports across registries (cross-cloud if needed), sets the image override on the target ArgoCD Application, and syncs.
Key points:
- No rebuild — the exact binary artifact from dev is promoted to prod
- Cross-cloud registries handled automatically (GCP→Azure, Azure→GCP)
- Image URL rewritten to target registry format
See also: 2.3 Cross-cloud image import
2.3 Cross-cloud image import
When promoting between environments on different cloud providers, the image is pulled from the source registry and pushed to the target registry automatically.
Key points:
- Default:
docker pull→docker tag→docker push - Custom
ImportCommandconfigurable per registry for provider-specific import mechanisms - Handles authentication to both source and target registries
- Preserves the exact image content — no rebuild, no layer modification
See also: 2.2 Artifact promotion
3. Platform Provisioning
3.1 Cluster provisioning
calq-relay cluster create --cluster-provider gcp --cluster gke-dev --environment dev --domain dev.example.com
calq-relay cluster create --cluster-provider azure --cluster aks-prod --environment prod --domain example.com
calq-relay cluster create --cluster-provider gcp --cluster gke-dev --environment dev # no DNS
What cluster create provisions:
- Kubernetes cluster (GKE with autoscaling + workload identity, or AKS with managed identity + autoscaling)
- Container registry (GAR or ACR)
- DNS zone (Cloud DNS or Azure DNS) — only with
--domain - cert-manager with Let's Encrypt ClusterIssuer
- ExternalDNS configured for the DNS zone — only with
--domain - Adds the cluster to
.relay/relay.json
Install on existing clusters (provisioned by Terraform or other means):
calq-relay cluster install --cluster-provider gcp --cluster gke-dev --environment dev --project my-project --region us-central1
Key points:
- Single command provisions a production-ready cluster with all platform components
cluster createavailable for Azure and GCP; other providers usecluster addafter manual provisioningcluster installinstalls platform components (cert-manager, ExternalDNS, ArgoCD) on pre-existing clusters
3.2 Platform bootstrapping (setup)
calq-relay setup
Registers clusters and Git repos with ArgoCD, generates Application manifests, and syncs. ArgoCD itself is installed by cluster create. Requires .relay/relay.json (created by service add).
What setup installs:
- ArgoCD cluster and repo registrations
- Platform CronJobs (pod recycler, canary enforcer, adaptive scaler)
- kubectl image imported to the environment's registry for CronJob execution
Key points:
- Idempotent — safe to re-run after adding services or clusters
- Generates platform-level Kubernetes resources that operate across all services
- Requires at least one service and one cluster configured
See also: 3.1 Cluster provisioning
3.3 Cluster destruction
calq-relay cluster destroy --cluster gke-dev --environment dev
Key points:
- Deletes the cluster and removes it from
.relay/relay.json - Registry is preserved — delete manually if needed
- Irreversible — all workloads on the cluster are lost
See also: 3.1 Cluster provisioning
3.4 Registry provisioning
Container registries are provisioned automatically as part of cluster create. Each environment gets one registry shared by all services.
Key points:
- GCP: Google Artifact Registry (GAR)
- Azure: Azure Container Registry (ACR)
- Custom providers: configure
LoginServerandAuthCommandin.relay/relay.json - Registry is preserved on
cluster destroy— delete manually if needed - Cross-cloud image import handles registry differences automatically during promotion
See also: 2.3 Cross-cloud image import, 3.1 Cluster provisioning
4. Configuration & Secrets
4.1 Build configuration
BuildConfig fields (in .relay/relay.json per service):
| Field | Default | Description |
|---|---|---|
Dockerfile |
"" (auto-detect) |
Path to Dockerfile. Empty = auto-generate for .NET or use existing |
BuildCommand |
docker build -f {dockerfile} -t {image} {context} |
Placeholders: {dockerfile}, {image}, {context} |
PushCommand |
docker push {image} |
Placeholder: {image} |
Context |
. |
Docker build context directory |
Tag |
{sha} |
Image tag template. {sha} = 12-char Git commit SHA |
Custom build example (Go with build args):
{
"Services": {
"worker": {
"Path": "k8s/worker",
"Build": {
"Dockerfile": "build/Dockerfile.prod",
"Context": ".",
"BuildCommand": "docker build -f {dockerfile} -t {image} --build-arg VERSION=1.0 {context}"
}
}
}
}
Key points:
- Build and push commands are fully customizable via placeholders
- Default tag uses Git SHA for traceability
- Context directory configurable for monorepo layouts
- All fields are optional — defaults cover the common case
See also: 2.1 Build automation
4.2 Secrets sync (GitHub → Kubernetes)
GitHub Secrets prefixed with K8S_ are automatically synced to Kubernetes Secrets during deployment. The prefix is stripped: K8S_DB_PASSWORD in GitHub becomes DB_PASSWORD in the Kubernetes Secret {service}-secrets.
Key points:
- Only secrets prefixed with
K8S_are synced — CI secrets likeAZURE_CREDENTIALSare not touched - Adding a new
K8S_*secret in GitHub automatically syncs it on the next deploy — no workflow changes needed - Uses
--dry-run=client -o yaml | kubectl applyfor idempotent create-or-update
See also: 2.1 Build automation
4.3 Hot configuration reload (ConfigMap)
Every scaffolded deployment mounts the service's ConfigMap as a volume at /app/config/. When you edit the ConfigMap in Git and push, ArgoCD syncs it to the cluster, and Kubernetes updates the mounted files in-place.
For ASP.NET Core apps:
builder.Configuration.AddJsonFile("/app/config/appsettings.k8s.json", optional: true, reloadOnChange: true);
Key points:
- The ConfigMap is mounted as a directory volume (not
subPath), which enables Kubernetes auto-update - Changes propagate within ~60 seconds (Kubernetes ConfigMap sync interval)
- No workflow or command needed — edit the YAML and push
See also: 6.3 Source of truth reconciliation
5. Project Scaffolding
5.1 ArgoCD application generation
Each service gets an ArgoCD Application manifest in .relay/apps/{service}.yaml. This manifest points ArgoCD at the service's Kustomize directory in the Git repo.
Key points:
- Auto-generated by
service addand updated bysetup - Sync policy: auto-sync with prune, retry with exponential backoff
- Supports
CreateNamespace,PrunePropagationPolicy=foreground,PruneLast - Image overrides stored in the Application spec — no Git commits needed for deploy/promote
See also: 6.1 Image override (no Git commits)
5.2 Kustomize manifest generation
calq-relay service add
What is generated (in k8s/{service}/):
kustomization.yamldeployment.yaml— with cluster-wide anti-affinity for instant rolling updatesservice.yaml— with cloud-specific annotationsconfigmap.yamlingress.yaml— only with--expose ingress --domainrelay/— auto-managed scaling patches (HPA, node-selector, anti-affinity)
Blue-green services use base/, blue/, and green/ subdirectories instead of a flat layout.
Key points:
- Files in
relay/are auto-managed byscaffold— do not edit manually scaffoldis re-runnable and cleans up when config changes- Anti-affinity is always included for cluster-wide rolling updates
See also: 5.1 ArgoCD application generation
5.3 GitHub workflow generation
Workflows are generated on first service add. Subsequent services are covered by the existing workflows.
Generated workflows (in .github/workflows/):
deploy.yaml— push to main → deploy all services to devpr-environment.yaml— PR open → clone dev, PR close → delete namespacepromote.yaml— manual trigger → promote to prodstage.yaml— manual trigger → stage to prod (blue-green only)switchover.yaml— manual trigger → switchover (blue-green only)relay.yaml— generic: run any calq-relay command
Key points:
- Existing workflow files are never overwritten
- Workflows include cloud-specific login steps based on cluster provider
relay.yamlcreates a PR for any config-changing commands (e.g., scaffold changes)
See also: 2.1 Build automation
5.4 Dockerfile generation
During deploy, if no Dockerfile exists and the project is .NET, a multi-stage Dockerfile is auto-generated.
Key points:
- Detects SDK type (Web SDK vs. runtime) from the .csproj
- Multi-stage build: restore → publish → runtime image
- Explicit Dockerfile path configurable via
Build.Dockerfilein.relay/relay.json - Non-.NET projects: provide your own Dockerfile — the pipeline works identically regardless of language
See also: 4.1 Build configuration
6. GitOps
6.1 Image override (no Git commits)
Deploy and promote operations store the target image directly in the ArgoCD Application spec via argocd app set --parameter. No Git commit is created for image updates.
Key points:
- Avoids commit spam during frequent deploys
- ArgoCD Application spec is the source of truth for "which image is running"
- Git repo remains the source of truth for manifests, config, and infrastructure
- ArgoCD sync status reports whether the cluster matches the desired state
See also: 2.1 Build automation
6.2 ArgoCD application management
Each service is represented as an ArgoCD Application. The Application spec points to the service's Kustomize directory and holds the current image override.
Key points:
- Application manifests stored in
.relay/apps/ - Sync policy includes retry with exponential backoff (limit 3, 5s/2x/3m)
ignoreDifferencesconfigured for fields managed by Calq Relay (image overrides, replica counts)- Application creation, update, and sync orchestrated by Calq Relay — not managed manually
See also: 5.1 ArgoCD application generation, 6.1 Image override (no Git commits)
6.3 Source of truth reconciliation
ArgoCD continuously reconciles cluster state against the Git repository. Calq Relay registers clusters and repos with ArgoCD via the setup command.
Key points:
- ArgoCD installed automatically by
cluster create - Cluster and repo registration handled by
setup - Drift detection and auto-sync provided by ArgoCD's native reconciliation loop
- Calq Relay orchestrates what ArgoCD cannot: source-to-cluster deployment, cross-environment promotion, cross-cloud image import, blue-green switchover, and platform bootstrapping
See also: 3.2 Platform bootstrapping (setup), 6.2 ArgoCD application management
7. Multi-Cluster / Multi-Cloud
7.1 Multi-region deployment
Multiple clusters in the same environment enable multi-region deployment. Operations target all clusters by default.
calq-relay cluster add --cluster aks-prod-east --cluster-provider azure --environment prod
calq-relay cluster add --cluster gke-prod-west --cluster-provider gcp --environment prod
calq-relay switchover --environment prod # all clusters
calq-relay switchover --environment prod --cluster aks-prod-east # one cluster
Key points:
- All deployment operations (deploy, promote, stage, switchover, canary, restart) operate across all clusters in the environment by default
- Single-cluster targeting available via
--clusterflag - Each cluster maintains its own ArgoCD Application and sync state
See also: 3.1 Cluster provisioning
7.2 Custom cloud provider integration
Azure and GCP have built-in support. Any other provider works by setting auth commands in .relay/relay.json.
AWS (EKS + ECR) example:
{
"Environments": {
"prod": {
"Clusters": {
"eks-prod": {
"Provider": "aws",
"Name": "eks-prod",
"Region": "us-east-1",
"AuthCommand": "aws eks update-kubeconfig --name {name} --region {region}",
"ContextName": "arn:aws:eks:{region}:123456789:cluster/{name}",
"WorkflowLogin": {
"Action": "aws-actions/configure-aws-credentials@v4",
"With": {
"role-to-assume": "${{ secrets.AWS_ROLE_ARN }}",
"aws-region": "us-east-1"
}
}
}
},
"Registry": {
"Provider": "ecr",
"Name": "myapp",
"Region": "us-east-1",
"LoginServer": "123456789.dkr.ecr.us-east-1.amazonaws.com",
"AuthCommand": "aws ecr get-login-password --region {region} | docker login --username AWS --password-stdin {loginServer}"
}
}
}
}
Custom provider fields:
| Config | Field | Description |
|---|---|---|
| Cluster | AuthCommand |
Shell command to set up kubectl access. Placeholders: {name}, {resourceGroup}, {project}, {region} |
| Cluster | ContextName |
kubectl context name after auth. Same placeholders |
| Cluster | WorkflowLogin |
GitHub Actions login step: Action (uses) + With (parameters) |
| Registry | LoginServer |
Registry hostname (required) |
| Registry | AuthCommand |
Shell command to authenticate Docker. Placeholders: {name}, {loginServer}, {project}, {region} |
| Registry | ImportCommand |
Shell command to import an image. Placeholders: {source}, {target}. Default: pull + tag + push |
Key points:
cluster createis only available for Azure and GCP — other providers usecluster addafter manual provisioning- All deployment commands (deploy, promote, switchover, canary, restart) work with any provider
- Set
WorkflowLoginso scaffolded workflows include the correct auth step
See also: 3.1 Cluster provisioning
7.3 Cross-cloud resilience
Multiple clusters from different cloud providers in the same environment provide cross-cloud resilience — if an entire cloud provider goes down, clusters on other providers keep serving.
Key points:
- No single point of failure at the cloud provider level
- Cross-cloud image import handles registry differences automatically
- Each cluster operates independently — no cross-cluster coordination required at runtime
- Failover is immediate: remaining clusters continue serving without reconfiguration
See also: 7.1 Multi-region deployment, 2.3 Cross-cloud image import
7.4 Terraform integration
Calq Relay and Terraform are complementary — Terraform for infrastructure, Calq Relay for deployments.
terraform apply
calq-relay cluster add --cluster gke-dev --cluster-provider gcp --environment dev --project my-project --region us-central1
calq-relay service add
calq-relay setup
Clusters created by cluster create are standard cloud resources and can be imported into Terraform:
terraform import google_container_cluster.dev gke-dev
Key points:
cluster addregisters pre-existing clusters without modifying themcluster installinstalls only platform components (cert-manager, ExternalDNS, ArgoCD) on pre-existing clusters- No conflict with Terraform-managed resources — Calq Relay operates at the application layer
See also: 7.2 Custom cloud provider integration, 3.1 Cluster provisioning
8. Environments
8.1 Separate repos for DEV and PROD
In each microservice repo (e.g., my-org/web, my-org/api):
calq-relay service add
Each repo manages its own DEV deployment. Push to main builds and deploys to DEV. PRs get preview environments.
In the production repo (e.g., my-org/production):
calq-relay service add --name web --blue-green
calq-relay service add --name api
calq-relay cluster add --cluster aks-dev --cluster-provider azure --environment dev
The production repo has no source code, no Dockerfile. It contains PROD Kustomize manifests and ArgoCD Applications for all services. Promoting from DEV to PROD reads the current image from the DEV cluster, imports it to the PROD registry, and syncs.
Key points:
- The microservice repos do not know about PROD
- The production repo does not know about source code
- Separation of concerns: development velocity in microservice repos, deployment control in production repo
- Monorepo support: multiple services in a single repo with per-service project paths
See also: 2.2 Artifact promotion
8.2 PR preview environments
All services in an environment share a single Kubernetes namespace. Creating a PR environment deploys all services into a new namespace where inter-service calls resolve automatically — no endpoint rewrites, no service mesh.
calq-relay environment clone pr-42 --base-environment dev
calq-relay environment remove pr-42
The auto-generated pr-environment.yaml workflow handles this automatically:
- PR opened/synchronized →
environment clone pr-{number} --base-environment dev - PR closed →
environment remove pr-{number}
Key points:
- Each PR gets a fully isolated copy of the entire platform
- All services communicate within the PR namespace — no config changes needed
- On PR close, the entire namespace and all resources are deleted
- Full-stack verification: all services, not just the changed one
See also: 5.3 GitHub workflow generation
8.3 Namespace isolation
All services in an environment share a single Kubernetes namespace (e.g., myplatform-dev). Service-to-service communication uses Kubernetes DNS (http://servicename:port) within the namespace.
Key points:
- Shared namespace enables automatic service discovery without configuration
- PR environments get their own namespace — full isolation from dev
- Namespace naming:
{platform-name}-{environment}
See also: 8.2 PR preview environments
8.4 Multi-environment promotion
Promotion reads the current image from the source environment's cluster, imports it to the target registry, and syncs the target ArgoCD Application.
Key points:
- Source and target can be on different cloud providers
- No access to source code needed — the production repo can promote without knowing how to build
- Pipeline: deploy to dev → promote to staging → promote to prod (or stage + switchover for blue-green)
See also: 2.2 Artifact promotion, 8.1 Separate repos for DEV and PROD
9. Scaling & Rolling Updates
9.1 Grouped multi-service scaling
Services in a Grouped pool share nodes — each node runs exactly one pod of each service. Scaling is coordinated: when the busiest service needs more replicas, all services scale together.
{
"NodePools": {
"critical": { "Scaling": "Grouped", "MinNodes": 2, "MaxNodes": 10, "TargetUtilization": 80 }
},
"Services": {
"web": { "NodePool": "critical" },
"api": { "NodePool": "critical" },
"scheduler": { "NodePool": "critical", "MaxReplicas": 1 }
}
}
Key points:
- One pod per service per node — coordinated scaling across all services in the pool
- Includes cluster-wide instant rolling updates via anti-affinity
- CronJob auto-tunes resource requests based on node capacity
- HPA scales when utilization exceeds
TargetUtilization(default 80%) - Grouped is the default scaling mode for node pools
9.2 Adaptive independent scaling
Services in an Adaptive pool scale independently. Each service gets its own HPA and anti-affinity rule.
{
"NodePools": {
"general": { "Scaling": "Adaptive", "MinNodes": 1, "MaxNodes": 20, "TargetUtilization": 80 }
},
"Services": {
"worker": { "NodePool": "general", "MinReplicas": 2, "MaxReplicas": 8 }
}
}
Key points:
- CronJob observes actual CPU usage and auto-tunes resource requests
- HPA scales when utilization exceeds
TargetUtilization(default 80%) - Each service gets anti-affinity (one pod per node per service) and its own HPA
- No cluster-wide instant rollout — pods update per standard rolling update strategy
See also: 9.1 Grouped multi-service scaling
9.3 Auto-tuned resource requests
A cluster-wide CronJob observes actual CPU usage via the Metrics API and patches deployment resource requests to match observed usage. This eliminates manual resource tuning.
What scaffold generates (in k8s/{service}/relay/):
hpa.yaml— HorizontalPodAutoscalerscaling-annotation.yaml— marks the deployment for the CronJobnode-selector.yaml— pins pods to the node pool (Grouped only)anti-affinity.yaml— one pod per node per service
Key points:
- Files in
relay/are auto-managed byscaffold— do not edit manually scaffoldis re-runnable and cleans up when config changes- Resource requests are adjusted continuously based on observed load
See also: 9.1 Grouped multi-service scaling, 9.2 Adaptive independent scaling
9.4 Manual HPA
Services without a node pool can still get HPA by setting min/max directly:
calq-relay service add --name api --min-replicas 2 --max-replicas 10
calq-relay scaffold
Key points:
- Scaffolds a standard HPA without auto-tuned resource requests
- The user manages resource requests manually in the deployment YAML
- Anti-affinity still applied for pod distribution
See also: 9.1 Grouped multi-service scaling, 9.2 Adaptive independent scaling
9.5 Cluster-wide rolling updates
Every scaffolded deployment includes a version label and pod anti-affinity rule that force Kubernetes to distribute new pods one-per-node during rolling updates. This is a permanent part of the deployment spec.
maxSurge: 100%, maxUnavailable: 0(default) — all new pods created simultaneously on different nodes, then old pods terminated. Cluster-wide parallel update.maxSurge: 0%, maxUnavailable: 1— sequential one-at-a-time, each new pod on a different node.
Key points:
- Anti-affinity ensures new pods are distributed across nodes — no two pods of the same service on one node
- Default strategy enables instant cluster-wide update by launching all new pods in parallel
restart --sequentialuses the sequential strategy for controlled rollout
See also: 9.1 Grouped multi-service scaling, 5.2 Kustomize manifest generation
10. Runtime Optimization
10.1 Singleton services
Services with MaxReplicas: 1 are singletons — they ride along on the pool's nodes but do not scale.
{
"Services": {
"scheduler": { "NodePool": "critical", "MaxReplicas": 1 }
}
}
Key points:
- Singleton services participate in the Grouped pool's node allocation but maintain exactly one replica
- Useful for background workers, schedulers, and services that require exclusive execution
See also: 9.1 Grouped multi-service scaling
10.2 Pod recycling / JIT warmth
Enabled by default. The setup command generates a cluster-wide CronJob that runs every 5 minutes. It discovers all HPA-managed deployments and marks the most recently created pod with a low pod-deletion-cost annotation — preserving older, warmer pods when the autoscaler scales down.
Disable in .relay/relay.json:
{ "ArgoCD": { "PodRecycling": false } }
Key points:
- Newer pods are preferred for eviction — older, JIT-compiled pods serve traffic longer
- Eliminates frozen or degraded pods that would otherwise require manual intervention
- Operates cluster-wide — discovers all HPA-managed deployments automatically
- No per-service configuration needed
See also: 9.1 Grouped multi-service scaling, 9.3 Auto-tuned resource requests
11. Deployment Strategies
11.1 Rollback
For blue-green services, run switchover again — it swaps back to the previous version instantly.
For non-blue-green services, use ArgoCD's native rollback:
argocd app rollback <app-name> <history-id>
Key points:
- Blue-green rollback is instant — Service selector patch, no redeployment
- Non-blue-green rollback delegates to ArgoCD history
- No manual image tag lookup or Git revert needed for blue-green services
See also: 6.2 ArgoCD application management
11.2 Blue-green switchover
calq-relay stage --service web --source dev --target prod
# Verify the inactive slot is healthy...
calq-relay switchover --service web --environment prod
Switchover patches the Service selector from the active slot to the inactive slot — instant traffic switch. The LoadBalancer IP and DNS don't change. Running switchover again swaps back.
Key points:
- Pre-scales the inactive slot to match active replicas before switching
- Zero-downtime: no connection draining, no DNS propagation delay
- Reversible: run switchover again to swap back immediately
- Works across all clusters in the environment simultaneously
See also: 11.1 Rollback, 8.4 Multi-environment promotion
11.3 Canary via replica ratio
For blue-green services, canary widens the Service selector to match both blue and green pods, then adjusts replica counts to control the traffic split. No service mesh, no extra load balancer — Kubernetes native pod distribution.
calq-relay stage --source dev --target prod
calq-relay canary --weight 10 --environment prod # 10% to new version
calq-relay canary --weight 50 --environment prod # 50% to new version
calq-relay switchover --environment prod # 100% to new version
# Problem? switchover again to swap back
Key points:
- Traffic split is proportional to replica count (e.g., 9 old + 1 new ≈ 10% canary)
- Minimum granularity depends on total replica count
switchoverafter canary restores the Service selector to a single slot, ending the canary- Works across all clusters in the environment simultaneously
See also: 11.2 Blue-green switchover
11.4 Canary drift protection / enforcement
Canary enforcement is enabled by default. The setup command generates a cluster-wide CronJob that runs every minute, discovering all Services with the relay.calq.io/canary-weight annotation and scaling both slot deployments to maintain the desired replica ratio — compensating for HPA scaling, pod crashes, and node preemption. switchover removes the annotations, ending enforcement.
Disable in .relay/relay.json:
{ "ArgoCD": { "CanaryEnforcement": false } }
Key points:
- Continuous enforcement — not a one-time operation
- Compensates for HPA drift, pod crashes, and node preemption automatically
switchoverremoves annotations, ending enforcement cleanly- No service mesh required — uses replica count manipulation only
See also: 11.3 Canary via replica ratio, 9.1 Grouped multi-service scaling
12. Ingress & TLS
12.1 External traffic routing
calq-relay service add --expose ingress --domain app.example.com
calq-relay service add --expose public # LoadBalancer without Ingress
Key points:
--expose ingress --domaingenerates an Ingress resource with TLS annotations--expose publiccreates a LoadBalancer Service without Ingress- Cloud-specific annotations applied automatically based on cluster provider
12.2 DNS automation
ExternalDNS is installed by cluster create when --domain is specified. It watches Ingress and Service resources and creates DNS records automatically.
Key points:
- DNS zone created in Cloud DNS (GCP) or Azure DNS (Azure) during cluster provisioning
- ExternalDNS syncs DNS records to match Kubernetes Ingress/Service resources
- Domain NS records must be pointed at the cloud DNS zone manually (one-time setup)
- Only provisioned when
--domainis passed tocluster create
See also: 12.1 External traffic routing, 3.1 Cluster provisioning
12.3 TLS provisioning
cert-manager with a Let's Encrypt ClusterIssuer is installed by cluster create. Ingress resources annotated for cert-manager automatically receive TLS certificates.
Key points:
- Certificates issued and renewed automatically by cert-manager
- Let's Encrypt production issuer configured by default
- No manual certificate management — annotation-driven
See also: 12.1 External traffic routing, 3.1 Cluster provisioning
13. Organization Configuration Sync
13.1 Config push / pull
Cluster provisioning steps are stored as JSON config files in .relay/config/. These can be shared across teams via an organization Git repo.
calq-relay config location # view config directory
calq-relay config push # push to organization repo (creates PR)
calq-relay config push --direct # push without PR
calq-relay config pull # pull from organization repo
Key points:
- On first
cluster create, default provisioning steps are written to disk for visibility and customization config pushshares local config with the organization via Gitconfig pulldownloads organization-standard config for local use- PR-based workflow available for config changes requiring review
See also: 3.1 Cluster provisioning
13.2 Custom provisioning templates
Save as .relay/config/ClusterProvisionConfig.{provider}.json:
{
"Steps": [
{ "Command": "aws eks create-cluster --name {cluster} --region {region}", "ContinueOnError": true },
{ "Command": "aws ecr create-repository --repository-name {registry} --region {region}", "ContinueOnError": true }
],
"DestroySteps": [
{ "Command": "aws eks delete-cluster --name {cluster} --region {region}" },
{ "Command": "aws ecr delete-repository --repository-name {registry} --region {region} --force" }
],
"PostSteps": [
{ "Command": "helm repo add jetstack https://charts.jetstack.io --force-update" },
{ "Command": "helm upgrade --install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set crds.enabled=true --wait --timeout 5m" }
]
}
Then calq-relay cluster create --cluster-provider aws uses it.
Key points:
Stepsrun during creation;DestroyStepsrun during destruction;PostStepsinstall platform componentsContinueOnError: trueallows idempotent re-runs (resource already exists is not fatal)- Placeholders:
{cluster},{registry},{region},{project},{resourceGroup} - Enables
cluster createfor any provider — not limited to built-in Azure/GCP support
See also: 13.1 Config push / pull, 7.2 Custom cloud provider integration
Quick Start
# Set your values (bash)
ORG=my-org; PROJECT=my-project; REGION=us-central1-a
# Set your values (PowerShell)
# $ORG="my-org"; $PROJECT="my-project"; $REGION="us-central1-a"
# Note: GCP project IDs must be globally unique (e.g., my-org-relay-2026)
# Note: Use a zone (us-central1-a) for 1 node, or a region (us-central1) for 3 nodes across zones
dotnet tool install --global CalqFramework.Relay.Cli
mkdir hello-relay
cd hello-relay
dotnet new web -n Hello.World
git init -b main
git add -A
git commit -m "init"
gh repo create $ORG/hello-relay --private --source=. --push
calq-relay cluster create --cluster-provider gcp --cluster gke-dev --environment dev --project $PROJECT --region $REGION
# Set up GitHub Actions credentials for CI/CD
gcloud iam service-accounts create calq-relay --project $PROJECT --display-name "Calq Relay CI"
gcloud projects add-iam-policy-binding $PROJECT --member serviceAccount:calq-relay@$PROJECT.iam.gserviceaccount.com --role roles/editor --quiet
gcloud iam service-accounts keys create key.json --iam-account calq-relay@$PROJECT.iam.gserviceaccount.com
gh secret set GCP_CREDENTIALS --repo $ORG/hello-relay < key.json
rm key.json
calq-relay service add --expose public
calq-relay scaffold
git add -A
git commit -m "add relay config"
git push
calq-relay setup
git add -A
git commit -m "add platform manifests"
git push
# Deploy (also triggered automatically on git push via the deploy workflow)
calq-relay deploy --environment dev
Prerequisites: kubectl, docker, gh, helm, argocd on PATH. Cloud CLI: gcloud + gke-gcloud-auth-plugin (GCP) or az (Azure). See How to Install for setup instructions.
License
Calq Relay is dual-licensed under PolyForm Noncommercial (with Evaluation Grant) and the Calq Commercial License.