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

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-run for 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: true previews 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: true flag

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
  1. 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.
  2. 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.
  3. 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.
  4. Stage: Deploys to the inactive slot (blue or green) for verification before switchover.
  5. 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.
  6. 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).
  7. 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.
  8. Canary Enforcement: Cluster-wide CronJob reads relay.calq.io/canary-weight annotations 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:

  1. Resolves the Dockerfile (explicit path from config, existing Dockerfile, or auto-generated for .NET)
  2. Scaffolds Kustomize manifests if missing
  3. Builds the container image (configurable build command)
  4. Pushes to the environment's registry
  5. Sets the image override on the ArgoCD Application (no Git commits)
  6. 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 pulldocker tagdocker push
  • Custom ImportCommand configurable 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 create available for Azure and GCP; other providers use cluster add after manual provisioning
  • cluster install installs 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 LoginServer and AuthCommand in .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 like AZURE_CREDENTIALS are 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 apply for 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 add and updated by setup
  • 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.yaml
  • deployment.yaml — with cluster-wide anti-affinity for instant rolling updates
  • service.yaml — with cloud-specific annotations
  • configmap.yaml
  • ingress.yaml — only with --expose ingress --domain
  • relay/ — 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 by scaffold — do not edit manually
  • scaffold is 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 dev
  • pr-environment.yaml — PR open → clone dev, PR close → delete namespace
  • promote.yaml — manual trigger → promote to prod
  • stage.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.yaml creates 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.Dockerfile in .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)
  • ignoreDifferences configured 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 --cluster flag
  • 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 create is only available for Azure and GCP — other providers use cluster add after manual provisioning
  • All deployment commands (deploy, promote, switchover, canary, restart) work with any provider
  • Set WorkflowLogin so 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 add registers pre-existing clusters without modifying them
  • cluster install installs 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 — HorizontalPodAutoscaler
  • scaling-annotation.yaml — marks the deployment for the CronJob
  • node-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 by scaffold — do not edit manually
  • scaffold is 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 --sequential uses 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
  • switchover after 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
  • switchover removes 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 --domain generates an Ingress resource with TLS annotations
  • --expose public creates 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 --domain is passed to cluster 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 push shares local config with the organization via Git
  • config pull downloads 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:

  • Steps run during creation; DestroySteps run during destruction; PostSteps install platform components
  • ContinueOnError: true allows idempotent re-runs (resource already exists is not fatal)
  • Placeholders: {cluster}, {registry}, {region}, {project}, {resourceGroup}
  • Enables cluster create for 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.

An unhandled error has occurred. Reload 🗙