# AWS Architecture Reference
Source: https://docs.gp.scale.com/docs/infrastructure/aws/aws-architecture-reference
Overview of the SGP AWS architecture
## Architecture Reference
## Cloud Services
| Service | Usage |
| ------------------------------------ | ------------------------------------------------------------------------ |
| Elastic Kubernetes Service (EKS) | SGP applications are deployed to this cluster |
| Identity and Access Management (IAM) | Role and policy management for SGP applications access to cloud services |
| Secrets Manager | Secret and configuration management for SGP applications |
| Key Management Service (KMS) | Encryption of sensitive data at rest |
| Simple Storage Service (S3) | Object storage for SGP applications (knowledge bases, models, etc.) |
| Simple Queue Service (SQS) | Karpenter spot instance interruption queue |
| OpenSearch | Search provider for SGP applications |
| Elastic Container Registry (ECR) | Container registry for custom agent images |
| Aurora PostgreSQL | Relational databases for SGP applications |
| DocumentDB | Document-oriented database for SGP applications |
| ElastiCache for Redis | In-memory caching for SGP applications |
| CloudWatch | Logging for cloud services |
## Networking
SGP is deployed into a VPC spanning three Availability Zones with separate public and private subnets. All application workloads run in private subnets. Outbound internet access is provided by a NAT Gateway.
### Ingress
| Property | Default |
| -------------------- | ----------------------------------------------------------- |
| Load balancer type | Application Load Balancer (ALB) |
| Listener port | 443 (TLS) |
| Placement | External (internet-facing) |
| WAFv2 | Optional — associate an existing Web ACL by name |
| VPC Endpoint Service | Optional — enables AWS PrivateLink for private connectivity |
The load balancer can be configured as internal (private subnet only) for deployments that require no direct internet exposure.
### Service Mesh
Istio is deployed as a cluster add-on and manages all east-west traffic between SGP services. An internal Istio ingress gateway handles traffic routing within the cluster.
## EKS Cluster Default Configuration
### Node Groups
| Node Group | Size | Limits | Machine Type | Machine Image | Taints |
| ------------ | ---------------------------- | -------- | ------------- | --------------------------------- | ---------------------------------- |
| System | 1 | — | m6a.2xlarge | EKS Default AMI | CriticalAddonsOnly=true:NoSchedule |
| Default | Karpenter Managed Node Group | 1000 CPU | c\*, m\* | Karpenter Bottlerocket AMI Family | |
| High Storage | Karpenter Managed Node Group | 100 CPU | c\*, m\*, r\* | Karpenter Bottlerocket AMI Family | |
| GPU | Karpenter Managed Node Group | 385 CPU | g5.\* | Karpenter Bottlerocket AMI Family | nvidia.com/gpu:NoSchedule |
| GPU (Large) | Karpenter Managed Node Group | 100 CPU | p4d.\* | Karpenter Bottlerocket AMI Family | nvidia.com/gpu:NoSchedule |
### Cluster Add-ons
| Add-on | Purpose |
| ---------------------------- | ------------------------------------------------------------------ |
| Karpenter | Workload-driven node autoscaling for Karpenter-managed node groups |
| Cluster Autoscaler | Scaling for the system node group |
| Istio | Service mesh for east-west traffic management |
| cert-manager | TLS certificate provisioning and renewal |
| AWS Load Balancer Controller | Kubernetes-native ALB and NLB provisioning |
| external-dns | Automatic Route 53 DNS record management |
| AWS Node Termination Handler | Graceful pod drain on spot interruption or node termination |
| NVIDIA GPU Operator | GPU driver and device plugin lifecycle management |
| AWS EBS CSI Driver | Persistent volume support for EKS workloads |
## Aurora PostgreSQL Default Configuration
| Instance | Service | Instance Count | Engine | Instance Class |
| ----------------------------------------------------- | --------------------------------- | -------------- | ------------ | -------------- |
| agents-service-\ | Agents | 2 | PostgreSQL16 | db.r6g.xlarge |
| duc-\-duc-api-aurora-db | Document Understanding Capability | 3 | PostgreSQL15 | db.r6g.large |
| reducto-\-aurora-db | Reducto | 3 | PostgreSQL15 | db.r6g.large |
| sae-\-sae-postgres | Semantic Analysis Engine | 1 | PostgreSQL17 | db.r6g.large |
| scale-egp-\-agentex-aurora-db | Agentex | 3 | PostgreSQL15 | db.r6g.large |
| scale-egp-\-egp-api-aurora-db | EGP API | 3 | PostgreSQL15 | db.r6g.large |
| scale-egp-\-identity-service-aurora-db | Identity Service | 3 | PostgreSQL15 | db.r6g.large |
| scale-egp-\-llm-engine-aurora-db | LLM Engine | 2 | PostgreSQL14 | db.r6g.large |
| scale-egp-\-spicedb-postgres | SpiceDB | 3 | PostgreSQL16 | db.r6g.large |
| scale-egp-\-temporal-aurora-db | Temporal | 3 | PostgreSQL15 | db.r6g.large |
| scale-egp-\-train-aurora-db | SGP Train | 3 | PostgreSQL15 | db.t3.medium |
## DocumentDB Default Configuration
| Instance | Service | Instance Count | Instance Class |
| ---------------------------------------- | ------- | -------------- | -------------- |
| scale-egp-\-compass-docdb | Compass | 1 | db.t3.medium |
| \-agentex-mongo | Agentex | 1 | db.r5.large |
## Customer Sensitive Data
Sensitive data may be stored in the following services, all data is encrypted at rest and in transit:
* Aurora PostgreSQL: For storing metadata, persistent data, and service-specific databases (SGP API Backend, Identity Service, LLM Engine, SpiceDB, Agents Service, SGP Train, and Temporal)
* DocumentDB: For storing Agentex agent data/workflows and Workflows document processing state
* S3 Buckets: For storing large data objects and datasets
* OpenSearch: For managing text embeddings and vector search capabilities
# AWS SGP Deployment
Source: https://docs.gp.scale.com/docs/infrastructure/aws/aws-sgp-deployment
End-to-end guide to deploying SGP in an AWS account.
## Overview
This guide will walk you through the process of deploying SGP in an AWS account. SGP AWS infrastructure is defined by terraform modules managed by Scale.
## Prerequisites
* Access to an AWS account with sufficient permissions to create resources
* The following tools installed:
* [AWS CLI](https://aws.amazon.com/cli/)
* [Terraform](https://www.terraform.io/)
* [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
* The following from Scale:
* The SGP AWS Infrastructure Terraform modules
* A `workspace_id` and `registration_secret` unique to your deployment
* `install.sh` script to bootstrap the SGP cluster
* A new application configured in your identity provider to authenticate to the SGP platform (SAML or OIDC) (optional)
* A custom domain for your deployment (optional)
## Installation
### Step 1: Prepare Terraform Root Module
In a new directory, create a `.tf` file to invoke the SGP AWS infrastructure Terraform modules. Reference the `egp-hybrid-cloud-customer-deployment` module as the entrypoint for the SGP infrastructure.
```terraform theme={null}
module "egp_hybrid_deployment" {
source = ""
scale_account_id = ""
registration_secret = "" # sensitive — use TF_VAR_registration_secret env var
domain = ".workspace.egp.scale.com" # or your custom domain
# Core services — enable the components you need
deploy_egp_api = true # EGP API backend — required for the SGP platform
deploy_spellbook = false # Spellbook prompt management service (optional)
deploy_sgp_models = false # SGP model serving infrastructure (optional)
deploy_llm_engine = false # Model Engine service (optional)
deploy_agentex = false # Agentex agentic workflow service (optional)
create_agentex_tool_cache = false # Persistent tool cache for Agentex (optional)
enable_cloud_build = false # Cloud-based container build service for Agentex (optional)
add_agents_service = false # Agent Service for building and running agents (optional)
add_document_understanding_capability = false # Document Understanding (Dex) service (optional)
enable_reducto = false # Reducto OCR and document parsing service (optional)
enable_train = false # SGP model training infrastructure (optional)
enable_compass = false # Workflows workflow orchestration service (optional)
enable_ukbp = false # Universal Knowledge Base Pipeline (optional)
enable_system_manager = true # System Manager deployment orchestrator — required
enable_monitoring = false # Observability stack (optional)
create_temporal_postgresql = true # Recommended; set false to use an in-cluster Cassandra instead
omit_egp_control_plane = false # Set true to disable communication with Scale's hosted control plane
base_repository = "" # Only needed if not pulling from Scale's registry
openai_api_key = "" # sensitive — use TF_VAR_openai_api_key env var
openai_org_id = ""
anthropic_api_key = "" # sensitive — use TF_VAR_anthropic_api_key env var
launch_api_key = "" # sensitive — use TF_VAR_launch_api_key env var
authentication_type = "SAML" # "SAML" or "OIDC"
use_cmk = true # Recommended for production: encrypts data at rest using AWS KMS
# If deploying into an existing VPC rather than creating a new one, provide the IDs here
# vpc_id = ""
# private_subnet_ids = ["", ""]
# public_subnet_ids = ["", ""]
access_cidrs = [] # List of CIDR blocks to allow access; defaults to open to internet
# Load balancer configuration
load_balancer_type = "application" # "application" or "network"
# waf_name = "" # Optional; only applies to application load balancers
internal_lb = false # Set true to prevent internet access to the load balancer
# additional_certs = [""] # Additional TLS certificates for the load balancer
# permissions_boundary_arn = "" # Optional: IAM permissions boundary for provisioned roles
# See the module documentation for the full list of available variables
}
# Example Terraform provider and backend configuration
provider "aws" {
region = ""
}
terraform {
required_version = ">= 1.1.7"
backend "s3" {
bucket = "sgp--terraform-state"
key = "sgp--sgp.tfstate"
region = ""
}
}
```
### Step 2: Provision Infrastructure via Terraform
Run the following commands in the same directory as the previous step's .tf file.
```bash theme={null}
terraform init
terraform plan -out=tfplan
# Inspect this plan to review the planned resources to be provisioned before proceeding
terraform show tfplan
terraform apply tfplan
```
*This step may take significant time due to resource creation dependencies*
If a pre-created certificate and domain entry are not provided, you will likely encounter the following error:
```bash theme={null}
Error: creating ELBv2 Listener (arn:aws:elasticloadbalancing:us-east-1:12345678910:loadbalancer/net/scale-egp-/12345678910): operation error Elastic Load Balancing v2: CreateListener, https response error StatusCode: 400, RequestID: 12345678910, api error UnsupportedCertificate: The certificate 'arn:aws:acm:us-east-1:12345678910:certificate/12345678910' must have a fully-qualified domain name, a supported signature, and a supported key size.
```
Resolve this by properly configuring DNS and TLS for your domain before re-running `terraform apply`.
### Step 3: Bootstrap the Cluster
Note the following values from the previous step's apply output:
```bash theme={null}
export SYSTEM_MANAGER_IRSA_ROLE_ARN=$(terraform output -raw system_manager_irsa_role_arn)
export SPICEDB_DATASTORE_URI=$(terraform output -raw spicedb_connection_string)
export AWS_REGION=""
```
#### Configure EKS access
Configure access for yourself to the provisioned EKS cluster:
1. Navigate to "Elastic Kubernetes Service" on the AWS console
2. Go to the required cluster
3. Go to *Access*
4. Under *IAM access entries* click *Create Access Entry*
5. Select your current AWS IAM role from the dropdown, leave the *Type* as *Standard*
6. Add the `AmazonEKSAdminPolicy` and `AmazonEKSClusterAdminPolicy` to the *Cluster* scope
7. Click create
#### Allowlist your IP (if needed)
1. Navigate to "Elastic Kubernetes Service" on the AWS console
2. Go to the required cluster
3. Go to *Networking*
4. On the right, click *Manage* and click *Endpoint Access*
5. Under *Advanced settings* add your IP address
#### Execute install.sh
Run the `install.sh` script to create the following Kubernetes resources:
* AWS Secret Manager CSI Driver
* Spicedb
* [SGP System Manager](/docs/infrastructure/system-manager)
```bash theme={null}
cd
# Set the correct kubectl context
aws eks update-kubeconfig --name scale-egp- --region
# Execute the install.sh script
./install.sh
```
System Manager is the deployment orchestrator for the SGP platform. It will automatically begin deploying the other services required for the SGP platform within the cluster.
### Step 4: Verify the Deployment
Wait for all services to be ready:
```bash theme={null}
kubectl get helmreleases -A # All should be Ready=True
kubectl get pods -A # All should be Running
```
### Step 5: Configure Identity Provider
#### SAML Configuration
In your Identity Provider, configure a new SAML application with the following settings:
* Service Entity ID: `https://auth.`
* Redirect URI: `https://auth./dashboard/org/saml/callback`
Modify the configuration `scale-egp-/identity-service-saml-secrets` (either via System Manager or directly in AWS Secrets Manager):
```json theme={null}
{
"id": "",
"samlConfiguration": {
"entityId": "",
"x509Cert": "",
"ssoUrl": "",
"attributeMappings": {
"email": "",
"firstName": "",
"lastName": ""
}
}
}
```
#### OIDC Configuration
In your Identity Provider, configure a new OIDC application with the following settings:
* Redirect URI: `https://auth./dashboard/org/oidc/callback`
Modify the configuration `scale-egp-/identity-service-oidc-secrets` (either via System Manager or directly in AWS Secrets Manager) with the following JSON:
```json theme={null}
{
"id": "",
"oidcConfiguration": {
"clientId": "",
"clientSecret": "",
"issuer": "",
"authorizationUrl": "",
"tokenUrl": "",
"userInfoUrl": ""
}
}
```
After modifying the secret, restart the `sgp-system-manager` deployment to apply the changes.
```bash theme={null}
kubectl rollout restart deployment sgp-system-manager -n sgp-system-manager
```
## Accessing the Platform
If all goes smoothly, you should be able to navigate to the SGP platform at `https://.workspace.egp.scale.com` (or your custom domain) and authenticate via the configured identity provider.
# Azure Architecture Reference
Source: https://docs.gp.scale.com/docs/infrastructure/azure/azure-architecture-reference
Overview of the SGP Azure architecture
## Architecture Diagram
```mermaid theme={null}
graph TD
USER(["Client"])
INTERNET(["Internet"])
subgraph AZ["Azure Subscription"]
FD["Azure Front Door
WAF · CDN · TLS termination"]
subgraph VNET["Virtual Network — 10.0.0.0/16"]
INGRESS["Istio Ingress
Internal Load Balancer"]
NAT["NAT Gateway
Outbound egress"]
subgraph AKS["AKS Cluster — private API server"]
SYS["System Node Pool
3× Standard_D4s_v3"]
APP["User Node Pool
3–10× Standard_D16s_v3"]
end
BASTION["Azure Bastion"]
JUMP["Jump Host VM"]
end
KV["Key Vault
Secrets + CMK keys"]
LAW["Log Analytics Workspace"]
PG[("PostgreSQL
Flexible Server")]
REDIS[("Redis Cache
Premium")]
STOR[("Storage Account")]
SB[("Service Bus
Premium")]
end
USER --> FD
FD -->|"Private Link"| INGRESS
INGRESS --> APP
APP -->|"Private Endpoint"| PG
APP -->|"Private Endpoint"| REDIS
APP -->|"Private Endpoint"| STOR
APP -->|"Private Endpoint"| SB
APP --> KV
SYS --> KV
APP --> LAW
SYS --> LAW
APP --> NAT --> INTERNET
BASTION --> JUMP -->|"kubectl"| AKS
```
***
## Resources by Type
### Compute Resources
| Resource | Count | Purpose |
| ---------------- | ----- | ---------------------------- |
| AKS Cluster | 1 | Kubernetes orchestration |
| System Node Pool | 1 | System pods (fixed 3 nodes) |
| User Node Pool | 1 | Application workloads |
| GPU Node Pool | 0-1 | AI/ML workloads (optional) |
| Cassandra Pool | 0-1 | Temporal database (optional) |
**Total VMs:** 6-24 (3 system + 3-10 user + 0-5 GPU + 0-6 Cassandra)
***
### Network Resources
| Resource | Count | Purpose |
| ----------------- | ----- | ------------------------ |
| Virtual Network | 1 | Network boundary |
| Subnets | 4-5 | Network segmentation |
| NSGs | 4-5 | Traffic control |
| Route Tables | 3-4 | Traffic routing |
| Private DNS Zones | 7 | Internal name resolution |
| Private Endpoints | 7 | Secure PaaS access |
| Bastion Host | 0-1 | Secure VM access |
| Public IPs | 0-1 | Bastion endpoint |
***
### Data & Storage Resources
| Resource | Count | Purpose |
| ----------------- | ----- | --------------------------- |
| PostgreSQL Server | 1 | Relational database |
| Redis Cache | 1 | Distributed cache |
| Storage Account | 1 | Blob/File storage |
| AI Search Service | 0–1 | Full-text search (optional) |
| OpenAI Service | 0–1 | LLM models (optional) |
***
### Security Resources
| Resource | Count | Purpose |
| --------------------- | ----- | ---------------------- |
| Key Vault | 1 | Secrets management |
| Managed Identity | 1-2 | Service authentication |
| RBAC Role Assignments | 10+ | Access control |
***
### Monitoring Resources
| Resource | Count | Purpose |
| ----------------------- | ----- | ------------------- |
| Log Analytics Workspace | 1 | Centralized logging |
| Data Collection Rule | 1 | AKS metrics |
| Diagnostic Settings | 7+ | Resource logging |
| Datadog Connection | 0-1 | External monitoring |
***
## Network Architecture
### Address Space Planning
```
VNet: 10.0.0.0/16 (65,536 IPs)
├── AKS Subnet: 10.0.1.0/24 (256 IPs)
├── Bastion Subnet: 10.0.2.0/26 (64 IPs)
├── Database Subnet: 10.0.3.0/24 (256 IPs)
└── Private Endpoints: 10.0.4.0/25 (128 IPs)
Pod CIDR: 10.244.0.0/16 (65,536 IPs)
Service CIDR: 10.243.0.0/16 (65,536 IPs)
```
### Traffic Flow
**Egress (Internet):**
```
Pods/VMs → NAT Gateway → Public IP → Internet
(Stateful, return traffic allowed)
```
**Ingress (Internal):**
```
Service IP → Load Balancer → Pod IP (via CNI)
```
**Database Access:**
```
AKS Pods → Private Endpoint → Private Link → PostgreSQL
(DNS: server.postgres.database.azure.com)
```
**External Service Access:**
```
AKS Pods → API Gateway / Load Balancer → OpenAI / AI Search
(Via Private Endpoints)
```
***
## AKS Configuration Deep Dive
### API Server Access
**Type:** Private cluster (recommended)
**Endpoint:** Internal only
**Access Method:** Bastion host or VPN
**DNS:** k8s..azmk8s.io (private)
### Network Policies
**Engine:** Azure Network Policy
**Scope:** Pod-to-pod communication
**Default:** Allow all (unrestricted)
**Configuration:** Define in Kubernetes manifests
### Container Registry
**Integration:** Azure Container Registry (optional)
**Authentication:** Managed identity or pull secrets
**Pulling:** Private endpoint (optional)
### Monitoring & Observability
**Azure Monitor Agent:** Deployed in kube-system
**Metrics:** CPU, Memory, Disk, Network
**Logs:** Container stdout/stderr, Kubernetes events
**Dashboards:** Pre-built in Log Analytics
# Azure SGP Deployment
Source: https://docs.gp.scale.com/docs/infrastructure/azure/azure-sgp-deployment
End-to-end guide to deploying SGP in an Azure subscription.
## Overview
This guide walks you through deploying SGP in an Azure cloud subscription using the SGP Azure Terraform modules. SGP Azure infrastructure is defined by terraform modules managed by Scale.
## Prerequisites
* Access to an Azure subscription with sufficient permissions to create resources (Contributor + User Access Administrator roles, or equivalent)
* The following tools installed:
* [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) (`az login` completed)
* [Terraform](https://www.terraform.io/) (`>= 1.1.7, < 2.0.0`)
* [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
* The following from Scale:
* The SGP Azure Infrastructure Terraform modules (`azure-terraform/infra`)
* A `workspace_id` and `registration_secret` unique to your deployment
* A new application configured in your identity provider to authenticate to the SGP platform (SAML or OIDC) (optional)
* A custom domain for your deployment (optional)
## Installation
### Step 1: Build Configuration
Configuration is split across three files. This allows you to reuse the same configuration for multiple environments.
```
infra/
├── main.tfvars.json # Selects which domain + environment to deploy
└── config/
└── /
├── default.yaml # Domain-wide defaults (all environments)
└── /
└── .yaml # Environment-specific overrides
```
#### File 1: `main.tfvars.json`
This file selects which configuration to load. Edit it to point at your customer and environment before running Terraform.
```json theme={null}
{
"environment_type": "",
"domain_code": "",
"location": ""
}
```
| Field | Description | Allowed values |
| ------------------ | ---------------------------------------------------- | ---------------------------------------------- |
| `environment_type` | Environment tier | `dev`, `qa`, `staging`, `uat`, `prod` |
| `domain_code` | Customer identifier (3–9 alphanumeric chars/hyphens) | e.g. `acme`, `contoso` |
| `location` | Azure region | e.g. `eastus2`, `northeurope`, `southeastasia` |
#### File 2: `config//default.yaml`
Customer-wide baseline settings that apply to **all** environments unless overridden. This file is the right place for stable policy and posture decisions: tenant identity, tagging standards, and security baselines for Key Vault, PostgreSQL, Redis, Storage, and other services.
```yaml theme={null}
# Identity
business_unit: ""
tenant_id: ""
default_tags:
costcenter: ""
product: ""
deploymenttype: "new"
safe-to-delete: "no"
# CMK key names (Terraform creates these keys when cmk.create=true in the env YAML)
cmk:
create: true
encryption_key_name: "encryptionCMK"
k8s_key_name: "k8sEncryptionCMK"
service_bus_key_name: "serviceBusEncryptionCMK"
# Key Vault network posture — secure-by-default baseline.
# Override public_network_access_enabled and network_acls_ip_rules in your env YAML
# if you need temporary public access during bootstrap from a laptop or CI runner.
keyvault:
public_network_access_enabled: false
enable_rbac_authorization: true
sku_name: "standard"
soft_delete_retention_days: 90 # Cannot be changed after vault creation
purge_protection_enabled: true
network_acls_default_action: "Deny"
network_acls_bypass: "AzureServices"
network_acls_ip_rules: []
# PostgreSQL baseline
psql:
public_network_access_enabled: false
sku_name: "GP_Standard_D2s_v3"
psql_version: "15"
backup_retention_days: 7
bootstrap_aad_principals: false
auth:
active_directory_auth_enabled: true
password_auth_enabled: true
storage_mb: 32768
storage_tier: "P10"
auto_grow_enabled: true
azure_extensions: "uuid-ossp,ltree"
# Redis baseline
redis:
public_network_access_enabled: false
sku_name: "Premium"
family: "P"
capacity: 1
active_directory_authentication_enabled: true
access_policy_name: "Data Owner"
non_ssl_port_enabled: false
tls_version: 1.2
redis_version: 6
# Storage account baseline
storage_account:
public_network_access_enabled: false
min_tls_version: "TLS1_2"
account_tier: "Standard"
account_replication_type: "LRS"
allow_nested_items_to_be_public: false
# OpenAI — leave client.mode empty to let Terraform infer from open_ai.create
open_ai:
client:
mode: ""
key: ""
org_id: ""
custom_url: ""
# NAT Gateway baseline
nat_gateway:
sku_name: "Standard"
public_ip_sku: "Standard"
public_ip_allocation_method: "Static"
zones: ["1", "2", "3"]
idle_timeout_in_minutes: 10
# AKS baseline
aks:
load_balancer_sku: "standard"
only_critical_addons_enabled: true
key_vault_secret_rotation_enabled: true
# Front Door baseline
frontdoor:
create: true
ssl_mode: managed
minimum_tls_version: "TLS12"
forwarding_protocol: HttpOnly
private_link_service_name: "sgp-ingress-lb"
subdomains: ["auth", "api", "@", "admin"]
extra_subdomains: []
dns_ttl_seconds: 60
ip_filtering:
enabled: false
allowed_ip_ranges: []
# System Manager bootstrapping baseline
bootstrapping:
az_cli_version: "2.59.0"
helm_chart_version: "2.1.0"
base_repository: ""
```
#### File 3: `config///.yaml`
Environment-specific configuration. This is where you set everything that differs per environment: resource names, subscription, CIDRs, feature flags, node pool sizing, and bootstrapping state.
```yaml theme={null}
# ── Environment identity ───────────────────────────────────────────────────────
location: eastus2
subscription_id: ""
name_suffix: "" # e.g. "acme01"
deployment_id: ""
deployment_url: ".workspace.egp.scale.com"
temporal_db_mode: postgresCommonHosted # Recommended; or "cassandraK8s" (not recommended)
# ── Auth / secrets ────────────────────────────────────────────────────────────
authType: default # Options: default, SAML, OIDC
RBAC: false
registration_secret: "" # Auto-generated if empty
identity_service_jwt: "" # Auto-generated if empty
# ── OIDC (required only when authType = OIDC) ─────────────────────────────────
oidc:
clientId: ""
clientSecret: ""
issuer: ""
authorizationUrl: ""
tokenUrl: ""
userInfoUrl: ""
# ── SAML (required only when authType = SAML) ─────────────────────────────────
saml:
X509Cert: ""
SSOUrl: ""
emailAttrName: ""
firstNameAttrName: ""
lastNameAttrName: ""
# ── SSL ───────────────────────────────────────────────────────────────────────
sslMode: e2e # Options: e2e, managed
# SSL cert/key for Istio ingress (PEM file paths or base64). Required when sslMode=e2e.
istio:
sslCert: ""
sslKey: ""
# ── Resource group + networking ───────────────────────────────────────────────
resource_group: "rg-eus2---dev-01"
network:
vnet: "sgpaz-vnet"
int_rt_name: "sgpaz-rt-int"
int_nsg_name: "sgpaz-nsg-int"
private_cluster: true
nsg_rules:
postgresql: true
redis: true
private_dns:
create: true
private_cluster: true
subnets:
psql:
name: "sgpaz-snet-psql"
type: int
address_prefixes:
- "" # e.g. "10.95.23.160/27"
service_endpoints:
- Microsoft.Storage
service_delegation:
name: Microsoft.DBforPostgreSQL/flexibleServers
actions:
- Microsoft.Network/virtualNetworks/subnets/join/action
deployment_scripts:
name: "sgpaz-snet-deployscripts"
type: int
address_prefixes:
- ""
service_delegation:
name: Microsoft.ContainerInstance/containerGroups
actions:
- Microsoft.Network/virtualNetworks/subnets/action
aks:
name: "sgpaz-snet-aks"
type: int
address_prefixes:
- ""
private_endpoint_network_policies: Disabled
service_endpoints:
- Microsoft.CognitiveServices
bastion:
name: AzureBastionSubnet
type: int
address_prefixes:
- ""
jump_host:
name: "sgpaz-snet-jumphost"
type: int
address_prefixes:
- ""
# ── Workload Identity + NAT Gateway ───────────────────────────────────────────
workload_identities:
enabled: true
nat_gateway:
enabled: true
name: "sgpaznat"
zones: [] # [] = regional (no zone pinning)
# ── CMK ───────────────────────────────────────────────────────────────────────
# Set create=true for new deployments. If CMK keys already exist, set create=false
# and provide the key URIs to avoid "already exists (import required)" errors.
cmk:
enabled: true
create: true
# encryption_key_uri: "https://.vault.azure.net/keys/encryptionCMK/"
# k8s_key_uri: "https://.vault.azure.net/keys/k8sEncryptionCMK/"
# service_bus_key_uri: "https://.vault.azure.net/keys/serviceBusEncryptionCMK/"
# ── Key Vault ─────────────────────────────────────────────────────────────────
# Override public access settings here if bootstrapping from a laptop or CI runner.
keyvault:
name: "sgpazkeyvault" # Must be globally unique; 3-24 characters
soft_delete_retention_days: 90 # Must match existing vault if already created
public_network_access_enabled: true # Set false once stable; keep true during bootstrap
network_acls_ip_rules:
- "/32" # Your laptop or CI runner IP
# ── Core services ─────────────────────────────────────────────────────────────
law:
create: true
psql:
create: true
name: "sgpazpostgres" # Must be globally unique
admin_user: postgres
bootstrap_aad_principals: true # Runs once after cluster is up; requires kubectl access
redis:
create: true
name: "sgpazredis" # Must be globally unique
storage_account:
create: true
name: "sgpazstorage" # Must be globally unique; no hyphens
enable_file_private_endpoint: true
service_bus:
create: true
name: "sgpazservicebus"
sku: Premium # Premium required for private endpoints
capacity: 1
premium_messaging_partitions: 1
private_endpoint_enabled: true
public_network_access: false
# ── AI services ───────────────────────────────────────────────────────────────
# Modes: use_openai_via_azure | use_openai_via_custom_endpoint | no_openai
# NOTE: Do not commit real API keys. Keep `key` as a local uncommitted change.
open_ai:
create: false
name: "sgpazopenai"
client:
mode: "use_openai_via_custom_endpoint"
key: ""
custom_url: ""
ai_search:
create: false
name: "sgpazaisearch"
# ── AKS ───────────────────────────────────────────────────────────────────────
aks:
create: true
name: "sgpazaks"
node_resource_group: "rg-aks-eus2---dev-01"
dns_prefix: "egp-k8scluster"
sku: Free # Free | Standard (use Standard for production)
kubernetes_version: "1.30"
run_command_enabled: true
enable_encryption_at_host: true
use_azure_managed_flux: true
zones: ["1", "2", "3"]
istio:
enabled: true
ingress_mode: External
revisions:
- asm-1-20
node_pools:
system:
name: default
vm_size: Standard_D4s_v3
count: 3
min_count: 3
max_count: 6
user:
enabled: true
name: user
vm_size: Standard_D16s_v3
count: 3
min_count: 3
max_count: 10
gpu:
enabled: false # Set true to enable GPU workloads
name: gpu
vm_size: Standard_NV72ads_A10_v5
min_count: 0
max_count: 5
cassandra:
enabled: false # Required only if temporal_db_mode is cassandraK8s
network:
pod_cidr: "10.244.0.0/16"
service_cidr: "10.243.0.0/16"
dns_service_ip: "10.243.0.10"
outbound_type: userDefinedRouting
# ── Bastion / jump host ───────────────────────────────────────────────────────
bastion:
create: true
admin_username: azureuser
ssh_public_key: ""
# ── Front Door overrides ──────────────────────────────────────────────────────
# Most Front Door settings inherit from default.yaml. Override only what differs.
frontdoor:
extra_subdomains: [] # e.g. ["chat"] if Agentex UI is enabled
# ── Feature flags ─────────────────────────────────────────────────────────────
feature_flags:
models: false
agentex:
create: false # Set true to provision Agentex service
compass:
create: false # Set true to provision Workflows service
dex:
create: false # Set true to provision Dex (Document Understanding) service
reducto:
create: false # Set true to provision Reducto service
# ── Bootstrapping (System Manager) ────────────────────────────────────────────
bootstrapping:
enabled: true
use_managed_flux: true
system_manager_version: ""
desiredState: |
{
"version": "0.1",
"packs": [
{ "name": "cert-manager" },
{ "name": "egp" },
{ "name": "identity-service" },
{ "name": "spicedb" },
{ "name": "sgp-apps" },
{ "name": "sgp-models" }
]
}
# ── Policy assignments ────────────────────────────────────────────────────────
policy_assignments:
enabled: false # Set true to enforce Azure Policy tag rules
# ── Observability ─────────────────────────────────────────────────────────────
observiqidp:
monitoring:
enabled: false
insights:
enabled: false
```
**Naming constraints for Azure resources:**
* Key Vault names: 3–24 alphanumeric characters and hyphens, globally unique
* Storage Account names: 3–24 lowercase letters and numbers only (no hyphens), globally unique
* PostgreSQL and Redis names: globally unique within Azure
* All names must remain stable after first apply — many Azure resources cannot be renamed
### Step 2: Provision Infrastructure via Terraform
Navigate to the `infra` directory and initialize:
```bash theme={null}
cd infra
terraform init
```
Review and apply the plan using the `main.tfvars.json` file you configured in the previous step:
```bash theme={null}
terraform plan -var-file=main.tfvars.json -out=tfplan
# Inspect the plan to review resources before proceeding
terraform show tfplan
terraform apply tfplan
```
If the Datadog monitoring integration is enabled in your YAML, pass the API key via environment variable rather than committing it to file:
```bash theme={null}
TF_VAR_datadog_api_key="" terraform plan -var-file=main.tfvars.json -out=tfplan
```
*This step may take significant time (30–60 minutes) due to resource creation dependencies, particularly the AKS cluster and PostgreSQL Flexible Server.*
### Step 3: Bootstrap the Cluster
The Azure infrastructure **automatically bootstraps** [SGP System Manager](/docs/infrastructure/system-manager) via an Azure Deployment Script. When `bootstrapping.enabled: true` is set in your configuration, Terraform provisions an Azure Container Instance that:
1. Installs Flux CD on the AKS cluster (using Azure Managed Flux if `use_managed_flux: true`)
2. Applies the System Manager `HelmRepository` and `HelmRelease` Flux CRDs
3. Waits for System Manager to reconcile
Monitor bootstrap progress in the Azure Portal under **Deployment Scripts** in your resource group, or check System Manager logs after bootstrap:
```bash theme={null}
# Get AKS credentials (from inside the VNet or via Bastion — cluster is private by default)
az aks get-credentials \
--resource-group \
--name sgpazaks \
--overwrite-existing
# Verify System Manager is running
kubectl get pods -n sgp-system-manager
# Watch System Manager deploy remaining services via Flux
kubectl get helmreleases -A
```
#### Accessing the Private AKS Cluster
Because the AKS cluster is private by default, you must access it from within the provisioned VNet. Two options are provided:
**Option A: From the jump host VM (via Azure Bastion)**
The Bastion host and jump host VM are provisioned when `bastion.create: true`. Connect via the Azure Portal (Bastion blade) or using the helper script:
```bash theme={null}
python3 scripts/connect_private_aks.py --run-mode=local --bootstrap-jump-host
```
**Option B: Using `az aks command invoke`**
When `aks.run_command_enabled: true`, you can run kubectl commands without VPN access:
```bash theme={null}
az aks command invoke \
--resource-group \
--name sgpazaks \
--command "kubectl get helmreleases -A"
```
### Step 4: Configure DNS
After `terraform apply` completes, retrieve the Front Door endpoint hostname:
```bash theme={null}
terraform output frontdoor_endpoint_host_name
terraform output frontdoor_dns_zone_id
```
Configure a CNAME record in your DNS provider pointing your `deployment_url` to the Front Door endpoint (the `azurefd.net` hostname from the output above).
If using Azure DNS (the DNS zone is managed by Terraform when `frontdoor.create: true`), CNAME records are created automatically. Verify with:
```bash theme={null}
az network dns record-set list \
--resource-group \
--zone-name
```
### Step 5: Verify the Deployment
Wait for all services to be ready:
```bash theme={null}
kubectl get helmreleases -A # All should show Ready=True
kubectl get pods -A # All should be Running or Completed
```
System Manager continuously reconciles the desired state. The `bootstrapping.desiredState` value in your environment YAML is written to a secret in Azure Key Vault during `terraform apply`, and System Manager reads from that secret at runtime. If a HelmRelease shows `Ready=False`, check its events:
```bash theme={null}
kubectl describe helmrelease -n
```
### Step 6: Configure Identity Provider
#### SAML Configuration
Set `authType: "SAML"` in your environment YAML, then configure your Identity Provider with:
* **Service Entity ID**: `https://auth.`
* **Redirect URI**: `https://auth./dashboard/org/saml/callback`
Update the `is-saml-secret` secret in Key Vault (or via System Manager GUI):
```json theme={null}
{
"id": "",
"samlConfiguration": {
"entityId": "",
"x509Cert": "",
"ssoUrl": "",
"attributeMappings": {
"email": "",
"firstName": "",
"lastName": ""
}
}
}
```
#### OIDC Configuration
Set `authType: "OIDC"` in your environment YAML, then configure your Identity Provider with:
* **Redirect URI**: `https://auth./dashboard/org/oidc/callback`
Update the `is-oidc-secret` secret in Key Vault (or via System Manager GUI):
```json theme={null}
{
"id": "",
"oidcConfiguration": {
"clientId": "",
"clientSecret": "",
"issuer": "",
"authorizationUrl": "",
"tokenUrl": "",
"userInfoUrl": ""
}
}
```
After modifying an identity secret, restart System Manager to apply the changes:
```bash theme={null}
kubectl rollout restart deployment sgp-system-manager -n sgp-system-manager
```
## Accessing the Platform
If all goes smoothly, you should be able to navigate to the SGP platform at `https://.workspace.egp.scale.com` (or your custom domain) and authenticate via the configured identity provider.
# GCP Architecture Reference
Source: https://docs.gp.scale.com/docs/infrastructure/gcp/gcp-architecture-reference
Overview of the SGP GCP architecture
## Architecture Diagram
```mermaid theme={null}
graph TD
USER(["Client"])
ENGINEER(["Engineer"])
subgraph GCP["GCP Project"]
GLB["Global Load Balancer
Static IP · TLS 1.2+"]
IAP["Cloud IAP"]
subgraph VPC["VPC — 10.0.0.0/16"]
INGRESS["Istio Ingress"]
subgraph GKE["GKE Cluster"]
SYS["System Pool
3× n2-standard-4"]
CPU["CPU Pool
4–30× n2-standard-16"]
end
BASTION["Bastion VM
e2-micro"]
end
subgraph PEERED["VPC-Peered — Cloud SQL"]
PG_MAIN[("Cloud SQL
PostgreSQL 17 (main)")]
PG_TEMP[("Cloud SQL
PostgreSQL 17 (temporal)")]
end
SM["Secret Manager"]
subgraph GCS_GROUP["Cloud Storage"]
BUCKET[("GCS — platform")]
KB[("GCS — knowledge base")]
end
AR["Artifact Registry
Docker + Helm"]
KMS["Cloud KMS
CMEK (optional)"]
end
USER --> GLB
GLB --> INGRESS
INGRESS --> CPU
CPU -->|"VPC Peering · SSL enforced"| PG_MAIN
CPU -->|"VPC Peering · SSL enforced"| PG_TEMP
CPU -->|"Workload Identity"| SM
CPU -->|"Workload Identity"| BUCKET
CPU -->|"Workload Identity"| KB
AR -->|"Workload Identity"| CPU
KMS -.->|"encrypts"| PG_MAIN
KMS -.->|"encrypts"| PG_TEMP
KMS -.->|"encrypts"| BUCKET
ENGINEER --> IAP --> BASTION -->|"private API"| GKE
```
***
## Terraform Structure
SGP's GCP infrastructure is provisioned in two separate Terraform phases with different privilege levels:
```mermaid theme={null}
graph LR
CREDS(["Personal GCP
Credentials"])
subgraph PS["projectsetup/ — run once with personal credentials"]
SA["Terraform
Service Account"]
AR_PS["Artifact Registries
Docker + Helm"]
SMK["SA Key stored in
Secret Manager"]
end
subgraph DEP["deployments/name/ — SA credentials auto-loaded from Secret Manager"]
GKE_D["GKE Cluster"]
SQL_D["Cloud SQL × 2"]
VPC_D["VPC + Subnets"]
SEC_D["Secrets + IAM
+ Workload Identity"]
end
CREDS -->|"terraform apply"| PS
SMK -->|"credentials
auto-read at plan time"| DEP
```
| Phase | Directory | Credentials | Scope |
| ----------------------- | --------------------- | ---------------------------------------------------- | ---------------------------------------------------- |
| Privileged bootstrap | `projectsetup/` | Your personal GCP identity | Service account, Artifact Registries, API enablement |
| Deprivileged main infra | `deployments//` | Terraform service account (read from Secret Manager) | GKE, Cloud SQL, networking, IAM, secrets |
The main infrastructure Terraform reads the service account key directly from Secret Manager — no key file needs to exist on disk during the infrastructure run.
***
## Resources by Type
### Compute Resources
| Resource | Count | Purpose |
| ---------------------------- | ----- | ------------------------------------------ |
| GKE Cluster | 1 | Kubernetes orchestration |
| System Node Pool (`default`) | 1 | System pods — tainted `CriticalAddonsOnly` |
| CPU Node Pool (`cpu`) | 1 | Application workloads |
| GPU Node Pool (`gpu`) | 0–1 | AI/ML workloads (optional) |
| Cassandra Node Pool | 0–1 | Temporal database (optional) |
| Bastion Host | 0–1 | Private cluster access via IAP (optional) |
**Default node pool sizing:**
| Pool | Machine Type | Min Nodes | Max Nodes |
| ------------------ | ---------------- | --------- | ----------------------- |
| System (`default`) | `n2-standard-4` | 3 | 10 |
| CPU (`cpu`) | `n2-standard-16` | 4 | 30 |
| GPU (`gpu`) | `a2-highgpu-1g` | 0 | 0 (disabled by default) |
| Cassandra | `n2-standard-4` | 3 | 6 |
All pools use `COS_CONTAINERD` image type and are preemptible by default (configurable via `node_pool_config.preemptible`).
***
### Network Resources
| Resource | Count | Purpose |
| ------------------- | ----- | ----------------------------------- |
| VPC Network | 1 | Network boundary |
| Subnetwork | 1 | Kubernetes nodes |
| Secondary IP Ranges | 2 | GKE pod and service CIDRs |
| Cloud DNS Zone | 1 | Internal + external name resolution |
| Global Static IP | 1 | Load balancer ingress endpoint |
| SSL Policy | 1 | Minimum TLS 1.2 enforcement |
| Firewall Rules | 4–5 | Traffic control (offline mode) |
| VPC Peering | 1 | Private connectivity to Cloud SQL |
| Private Route | 0–1 | Google APIs access in offline mode |
***
### Data & Storage Resources
| Resource | Count | Purpose |
| ----------------------------- | ----- | ----------------------------- |
| Cloud SQL (PostgreSQL 17) | 1 | Main platform database |
| Cloud SQL (PostgreSQL 17) | 1 | Temporal workflow database |
| GCS Bucket (main) | 1 | Platform object storage |
| GCS Bucket (knowledge base) | 1 | KB document storage |
| GCS Bucket (monitoring) | 0–1 | Observability data (optional) |
| Vertex AI Vector Search Index | 0–N | Vector embeddings (optional) |
| Cloud Firestore | 0–1 | Agentex state (optional) |
Both Cloud SQL instances are private-only (no public IP) and connected to the VPC via VPC peering. SSL is enforced for all database connections (`ENCRYPTED_ONLY`).
***
### Security Resources
| Resource | Count | Purpose |
| -------------------------------------------------------- | ----- | ------------------------------------------- |
| Secret Manager Secrets | 10+ | Platform configuration and credentials |
| Cloud KMS Key Ring + Key | 0–1 | Customer Managed Encryption Keys (optional) |
| Service Account (`sgp--sa`) | 1 | Main SGP workload identity SA |
| Service Account (`sgp--node-pool-creator`) | 1 | Node pool creation SA |
| Service Account (`sgp-tf-lp-`) | 1 | Terraform execution SA (projectsetup) |
| Workload Identity Pool | 1 | GKE pod → GCP SA binding |
| IAP Tunnel (Bastion) | 0–1 | Private cluster access |
***
### Artifact Resources (projectsetup phase)
| Resource | Count | Purpose |
| -------------------------- | ----- | -------------------- |
| Artifact Registry (Docker) | 1 | SGP container images |
| Artifact Registry (Helm) | 1 | SGP Helm charts |
Both registries are named `sgp--docker-repository` and `sgp--helm-repository`.
***
### Monitoring Resources
| Resource | Count | Purpose |
| -------------------------- | ----- | -------------------------------------------- |
| Cloud Logging | 1 | GKE system components, API server, workloads |
| VPC Flow Logs | 0–1 | Network traffic sampling (optional) |
| GKE Vulnerability Scanning | 1 | Basic vulnerability mode enabled by default |
***
## Network Architecture
### Address Space
```
VPC: sgp--network
└── Subnet: sgp--network-kubernetes-subnet
├── Primary range: 10.0.0.0/16 (65,536 IPs — GKE nodes)
├── Secondary range: 10.2.0.0/16 (65,536 IPs — Kubernetes services)
└── Secondary range: 10.4.0.0/16 (65,536 IPs — Kubernetes pods)
Cloud SQL VPC Peering:
└── Reserved range: /16 block (managed by service networking)
GKE Control Plane (private):
└── Master CIDR: 10.5.0.0/28 (configurable via private_gke_master_ipv4_cidr_block)
```
### Traffic Flow
**Ingress (External):**
```
Internet → Global Load Balancer (static IP) → Istio Ingress → Pod
(TLS terminated at load balancer; SSL policy enforces TLS 1.2+)
```
**Database Access:**
```
GKE Pod → VPC Peering → Cloud SQL (private IP)
(SSL enforced; no public IP on Cloud SQL instances)
```
**Google APIs (offline mode):**
```
GKE Pod → Private Google Access → Google APIs (199.36.153.4/30)
(Dedicated route; no default internet gateway route created)
```
**Bastion Access:**
```
Engineer → IAP Tunnel → Bastion VM (e2-micro) → GKE API (private endpoint)
```
***
## GKE Configuration
### Cluster Features
| Feature | Value |
| -------------------------- | -------------------------------------------- |
| Datapath provider | `ADVANCED_DATAPATH` (eBPF-based) |
| IP stack | Dual-stack IPv4/IPv6 |
| Workload Identity | Enabled (`.svc.id.goog`) |
| Secret Manager integration | Enabled |
| Vulnerability scanning | Basic mode |
| Private nodes | Enabled (when `offline_mode = true`) |
| Private endpoint | Configurable (`enable_gke_private_endpoint`) |
| Master authorized networks | Configurable per deployment |
| DNS endpoint | Enabled (allows external cluster DNS access) |
### Workload Identity
GKE pods authenticate to GCP services using Workload Identity rather than node-level service account keys. Kubernetes service accounts are bound to GCP service accounts via the workload identity pool:
```
.svc.id.goog[/]
→ GCP Service Account
→ Secret Manager, GCS, Artifact Registry, etc.
```
Key bindings provisioned by Terraform:
| Kubernetes Identity | GCP Role |
| ----------------------------------------------- | --------------------------------------------------------------- |
| `sgp-system-manager` (system-manager namespace) | `secretmanager.viewer`, `secretmanager.secretAccessor` |
| `sgp-system-manager-pre-install` | `secretmanager.viewer`, `secretmanager.secretAccessor` |
| `egp-api-backend` (egp namespace) | `secretmanager.secretAccessor`, GCS access via SA impersonation |
| `egp-api-backend-db-setup` | `secretmanager.secretAccessor` |
| `agents` (agents namespace) | `secretmanager.secretAccessor` |
***
## Secret Manager Secrets
Key secrets provisioned by Terraform:
| Secret Name | Contents |
| --------------------------------------- | ------------------------------------------------------------------------------ |
| `-system-manager-config` | System Manager runtime configuration (URLs, workspace ID, cloud provider info) |
| `-system-manager-desired-state` | Initial desired state (pack list) — managed externally after first apply |
| `-saml-config-secret` | SAML IdP configuration |
| `-oidc-config-secret` | OIDC IdP configuration |
| `terraform-service-account-key-secret` | Terraform SA private key (used by `deployments/` as provider credentials) |
The `secret_name_prefix` variable (typically `sgp-`) scopes secrets for deployments in shared GCP projects.
***
## Customer Managed Encryption Keys (CMEK)
When `useCustomerManagedEncryptionKey = true`, Terraform provisions a Cloud KMS key ring and symmetric encryption key:
| Resource | Name |
| ---------- | ------------------------------- |
| Key Ring | `sgp--key-ring` |
| Crypto Key | `sgp--crypto-key` |
The key is applied to:
* Cloud SQL instances (both main and Temporal)
* GCS buckets
CMEK is recommended for production deployments to maintain cryptographic control over data at rest.
***
## Optional Capabilities
Each optional capability provisions dedicated infrastructure:
| Capability | Variable | Additional Resources |
| ---------------------------- | ---------------------------------- | --------------------------------------------------------------------------- |
| Agentex | `deployAgentex` | Firestore, dedicated service account, GCS bucket |
| Workflows | `deployCompass` | Cloud SQL database, GCS bucket, service account |
| Dex (Document Understanding) | `enable_dex` | Cloud SQL (`db-custom-4-15360`), GCS bucket, service account |
| Reducto | `enable_reducto` | Cloud SQL (`db-custom-2-7680`), GCS bucket, Vision API key, service account |
| Model Engine | `deployModelEngine` | Vertex AI, additional node pools |
| Monitoring | `enable_monitoring` | GCS bucket, IAM bindings |
| Cloud Build | `enable_cloud_build` | GCS bucket, Artifact Registry, service accounts |
| Vertex AI Search | `vertex_ai_indices` | Vector Search indices and endpoints |
| LiveKit | `provision_livekit_infrastructure` | GCS bucket (audio), IAM bindings |
# GCP SGP Deployment
Source: https://docs.gp.scale.com/docs/infrastructure/gcp/gcp-sgp-deployment
End-to-end guide to deploying SGP in a GCP project.
## Overview
This guide walks you through deploying SGP in a GCP project using the SGP GCP Terraform modules. SGP GCP infrastructure is defined by terraform modules managed by Scale.
GCP deployment is split into two Terraform phases with different privilege levels:
* **`projectsetup/`** — Run once with privileged GCP credentials. Creates a dedicated Terraform service account (with least privileges), enables required GCP APIs, and provisions Artifact Registries for SGP images and Helm charts.
* **`deployments//`** — Run with the service account created above. Provisions the GKE cluster, Cloud SQL, networking, and all SGP infrastructure.
This split ensures the main infrastructure Terraform never requires broad privileged credentials.
## Prerequisites
* Access to a GCP project with permissions to manage IAM, APIs, and compute resources
* The following tools installed:
* [gcloud CLI](https://cloud.google.com/sdk/docs/install) (`gcloud auth login` completed)
* [Terraform](https://www.terraform.io/) (`>= 1.1.7`)
* [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
* [helm](https://helm.sh/docs/intro/install/) (for cluster bootstrap)
* [crane](https://github.com/google/go-containerregistry/tree/main/cmd/crane) or [Docker](https://docs.docker.com/engine/install/) (for copying images)
* \[sgpctl] ([https://github.com/scaleapi/sgpctl](https://github.com/scaleapi/sgpctl)) (cli tool to execute system manager related commands)
* The following from Scale:
* The SGP GCP Infrastructure modules (`gcp/`)
* A `workspace_id` (8-digit number) and `registration_secret` unique to your deployment
* A new application configured in your identity provider to authenticate to the SGP platform (SAML or OIDC) (optional)
* A custom domain for your deployment (optional)
## Installation
### Step 1: Bootstrap the Project
Navigate to the `projectsetup/` directory. This phase creates the Terraform service account and Artifact Registries.
Edit the `locals` block at the top of `projectsetup/main.tf`:
```hcl theme={null}
locals {
project = ""
region = "" # e.g. "us-east1"
zone = "" # e.g. "us-east1-a"
service_account_name = "-sa" # Suffix for the service account name
workspaceID = "" # 8-digit number from Scale, e.g. "90000001"
}
```
Then initialize and apply:
```bash theme={null}
cd projectsetup
terraform init
terraform apply
```
This creates:
* A Terraform service account (`sgp-tf-lp-`) with the IAM roles needed to provision SGP infrastructure
* A service account key stored as `terraform-service-account-key-secret` in Secret Manager
* Docker and Helm Artifact Registries (`sgp--docker-repository`, `sgp--helm-repository`)
* All required GCP APIs enabled on the project
The Terraform outputs include the `service_account_email` of the newly created service account, which is useful for auditing. The service account key is automatically stored in Secret Manager and read by the main infrastructure Terraform — no manual key management is required.
### Step 2: Copy SGP Images and Helm Charts
*This step is only required if your deployment is configured to block internet access from the cluster (`offline_mode` in step 3).*
Before provisioning the main infrastructure, copy SGP's Docker images and Helm charts from Scale's registry into your GCP Artifact Registry.
**Download the manifest from Scale's control plane**:
```bash theme={null}
sgpctl manifest download \
--control-plane-id \
--registration-token \
--version \
--channel \
--output .manifest.yaml
```
Note: sgpctl manifest download requires internet access to [https://control.egp.scale.com](https://control.egp.scale.com), so it must be run from a connected machine.
Users with air-gapped operator workstations should run it from their laptop and SCP the output to wherever they'll run the mirror commands.
**Authenticate to source + destination registries**:
```bash theme={null}
# Source: Scale's ECR (credentials from Scale engineer)
crane auth login 022465994601.dkr.ecr.us-west-2.amazonaws.com \
--username AWS \
--password-stdin
# Destination: customer's GCP Artifact Registry (uses gcloud creds)
gcloud auth configure-docker -docker.pkg.dev
```
**Mirror images and charts**:
```bash theme={null}
# Container images
sgpctl mirror-manifest \
--manifest-file .manifest.yaml \
--dest-docker-registry -docker.pkg.dev//sgp--docker-repository
# Helm charts
sgpctl mirror-helm-charts \
--manifest-file .manifest.yaml \
--dest-helm-repo oci://-docker.pkg.dev//sgp--helm-repository
```
**Verify the mirror**:
```bash theme={null}
gcloud artifacts docker images list \
-docker.pkg.dev//sgp--docker-repository \
--include-tags --limit=20
gcloud artifacts docker images list \
-docker.pkg.dev//sgp--helm-repository \
--include-tags --limit=20
```
### Step 3: Configure the Deployment
Copy the `deployments/prototype/` directory and rename it for your deployment:
```bash theme={null}
cp -r deployments/prototype deployments/
cd deployments/
```
Edit the `locals` block at the top of `main.tf`. The deployment reads its Terraform service account credentials automatically from Secret Manager — no key file is required on disk.
```hcl theme={null}
locals {
project = ""
region = "" # e.g. "us-east1"
zone = "" # e.g. "us-east1-b"
# ── Deployment identity ────────────────────────────────────────────────
workspaceID = "" # 8-digit number from Scale
registrationSecret = "" # From Scale
deploymentURL = ".workspace.egp.scale.com" # Or your custom domain
# ── Repositories ──────────────────────────────────────────────────────
# baseRepository: used by workloads running inside the cluster to pull images
# publicBaseRepository: used for initial image pulls during bootstrap (before private DNS resolves)
# Both point to the same registry when using a private Artifact Registry.
baseRepository = "-docker.pkg.dev//sgp--docker-repository"
publicBaseRepository = "-docker.pkg.dev//sgp--docker-repository"
# ── System Manager ────────────────────────────────────────────────────
systemManagerImageTag = "" # From Scale; must match manifest.yaml
offline_mode = true # true when using your own Artifact Registry
# ── Auth ──────────────────────────────────────────────────────────────
authType = "SAML" # "SAML" or "OIDC"
# ── Bootstrap ─────────────────────────────────────────────────────────
# deploy_system_manager=false: Terraform creates infra only; run manual-helm-install.sh to bootstrap
# deploy_system_manager=true: Terraform also installs System Manager into the cluster
deploy_system_manager = false
# ── DNS ───────────────────────────────────────────────────────────────
createDNSRecords = false # Set true if Terraform should manage Cloud DNS records
# ── Encryption ────────────────────────────────────────────────────────
useCustomerManagedEncryptionKey = false # Set true for CMEK (recommended for production)
# ── Networking ────────────────────────────────────────────────────────
gke_config = {
private_endpoint = true
master_authorized_networks = [
{ cidr_block = "10.0.0.0/16", display_name = "cluster vpc primary range" },
{ cidr_block = "10.2.0.0/16", display_name = "cluster vpc services range" },
{ cidr_block = "10.4.0.0/16", display_name = "cluster vpc pod range" },
]
}
}
```
**If using SAML,** place your IdP's x509 certificate (without BEGIN/END lines) as `x509.cer` in the deployment directory. Update the `samlConfigSecret` in the `module "sgp"` block:
```hcl theme={null}
module "sgp" {
source = "../../modules/sgp/"
samlConfigSecret = jsonencode({
"id" = local.workspaceID
"samlConfiguration" = {
"entityId" = "https://auth.${local.deploymentURL}"
"x509Cert" = file("${path.module}/x509.cer")
"ssoUrl" = ""
"attributeMappings" = {
"email" = ""
"firstName" = ""
"lastName" = ""
}
}
})
# ... other variables
}
```
**If using OIDC,** update `oidcConfigSecret` instead:
```hcl theme={null}
oidcConfigSecret = jsonencode({
"id" = local.workspaceID
"clientId" = ""
"clientSecret" = ""
"issuer" = ""
"authorizationUrl" = ""
"tokenUrl" = ""
"userInfoUrl" = ""
})
```
### Step 4: Provision Infrastructure via Terraform
From the deployment directory, initialize and apply:
```bash theme={null}
terraform init -upgrade
terraform plan -out=tfplan
# Inspect the plan before proceeding
terraform show tfplan
terraform apply tfplan
```
*This step can take 30–60 minutes due to GKE cluster provisioning and Cloud SQL setup.*
After apply completes, connect to the cluster:
```bash theme={null}
gcloud container clusters get-credentials sgp--kubernetes-cluster \
--region \
--project
```
If `gke_config.private_endpoint = true`, the cluster API server is only accessible from within the VPC. Use the provisioned bastion host (via IAP) or a network-connected runner to access it. Set `bastion_enabled = true` and add your user email to `bastion_iap_members` in the `security_compliance` block to enable bastion access.
### Step 5: Bootstrap the Cluster
[SGP System Manager](/docs/infrastructure/system-manager) orchestrates SGP service deployment. If you set `deploy_system_manager = false` in the previous step, bootstrap it manually using the provided script.
Edit `manual-helm-install.sh` in your deployment directory with the correct values:
```bash theme={null}
PROJECT_ID=""
PROJECT_NUMBER="" # From: gcloud projects describe --format='value(projectNumber)'
BASE_REPOSITORY="-docker.pkg.dev//sgp--docker-repository"
SYSTEM_MANAGER_IMAGE_TAG=""
OFFLINE_MODE=true
# Obtain from Cloud SQL instance → "Connect to this instance" in the console, or from Terraform state
SPICEDB_IP_ADDRESS=""
SPICEDB_PASSWORD=""
TEMPORAL_DB_PASSWORD=""
```
Then run the bootstrap script:
```bash theme={null}
chmod +x manual-helm-install.sh
./manual-helm-install.sh
```
This installs System Manager into the cluster. System Manager will then begin reconciling the `desired_state.json` file and deploying SGP services automatically.
#### Desired State
The `desired_state.json` file in your deployment directory defines which SGP packs System Manager installs. Update it to reference your GCP Artifact Registry:
```json theme={null}
{
"version": "0.1",
"packs": [
{ "name": "flux" },
{
"name": "sgp-helm-repository",
"properties": {
"helm-repo": {
"url": "oci://-docker.pkg.dev//sgp--helm-repository"
}
}
},
{ "name": "istio" },
{
"name": "sgp-base",
"properties": {
"helm-base": {
"value_overrides": {
"refreshRegcred": { "enabled": false },
"gcp": {
"presharedCertificates": ["sgp--ssl-certificate"]
}
}
}
}
},
{ "name": "spicedb" },
{ "name": "identity-service" },
{ "name": "temporalf" },
{ "name": "sgp-apps" }
]
}
```
See [Step 6](#step-6-configure-ssl-certificates) for the SSL certificate options and how to set the correct value in the `gcp` block above.
### Step 6: Configure SSL Certificates
Three options are available, in order of preference:
#### Option 1: Google Certificate Manager (Recommended)
Provisions a wildcard certificate (`*.your-domain.com`) via DNS authorization. Covers all subdomains automatically.
Enable it in `main.tf`:
```hcl theme={null}
module "sgp" {
source = "../../modules/sgp/"
enable_certificate_manager = true
# ...
}
```
After `terraform apply`, note the two outputs:
* `certificate_manager_dns_auth_record` — a CNAME you must add to your DNS zone for domain validation
* `certificate_map_name` — typically `sgp--cert-map`
Add the CNAME to your DNS provider, then update `desired_state.json`:
```json theme={null}
{
"name": "sgp-base",
"properties": {
"helm-base": {
"value_overrides": {
"gcp": {
"certificateMapName": "sgp--cert-map"
}
}
}
}
}
```
The certificate typically provisions within 10–60 minutes of the DNS record being in place.
#### Option 2: Google Managed Certificate
Google provisions and auto-renews per-subdomain certificates. Does not require uploading a certificate, but requires DNS to resolve to the load balancer IP before provisioning, and does not support wildcards.
This is the default when neither `certificateMapName` nor `presharedCertificates` is set in the `sgp-base` pack's `gcp` block. Simply omit those keys from `desired_state.json`.
#### Option 3: Preshared Certificate
Use a certificate you manage and upload to Google Cloud. Required when DNS is not publicly resolvable or Certificate Manager is not available.
Upload the certificate:
```bash theme={null}
gcloud compute ssl-certificates create sgp--ssl-certificate \
--certificate=fullchain1.pem \
--private-key=privkey1.pem \
--project
```
Reference it in `desired_state.json`:
```json theme={null}
{
"name": "sgp-base",
"properties": {
"helm-base": {
"value_overrides": {
"gcp": {
"presharedCertificates": ["sgp--ssl-certificate"]
}
}
}
}
}
```
Preshared certificates must be renewed manually. Let's Encrypt certificates expire after 90 days.
After changing the SSL configuration in `desired_state.json`, restart System Manager to apply it:
```bash theme={null}
kubectl rollout restart deployment sgp-system-manager -n sgp-system-manager
```
### Step 7: Configure DNS
Get the external IP address of the load balancer:
```bash theme={null}
# From inside the cluster:
kubectl get svc istio-ingress --namespace istio-ingress -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
# Or from gcloud (the global address is created by Terraform):
gcloud compute addresses describe sgp--external-ip \
--global \
--project \
--format="value(address)"
```
Create DNS A records in your DNS provider pointing to this IP for:
* `` (apex)
* `api.`
* `auth.`
* `app.`
If `createDNSRecords = true` in your `main.tf` locals, Terraform manages a Cloud DNS zone and creates these records automatically. Retrieve the name servers from the Terraform output:
```bash theme={null}
terraform output name_servers
```
Then delegate the zone by configuring these as NS records at your domain registrar.
### Step 8: Verify the Deployment
Wait for all services to be ready:
```bash theme={null}
kubectl get helmreleases -A # All should show Ready=True
kubectl get pods -A # All should be Running or Completed
```
System Manager continuously reconciles the desired state. If a HelmRelease shows `Ready=False`, check its events:
```bash theme={null}
kubectl describe helmrelease -n
```
## Accessing the Platform
Once all HelmReleases are ready and DNS resolves correctly, navigate to `https://.workspace.egp.scale.com` (or your custom domain) and authenticate via your configured identity provider.
# System Manager
Source: https://docs.gp.scale.com/docs/infrastructure/system-manager
Overview of the SGP System Manager deployment orchestrator
## Overview
System Manager is a Kubernetes operator used to deploy and manage the SGP platform. It is responsible for deploying the SGP platform services and agents to an existing Kubernetes cluster.
## Installation
System Manager is installed as a Helm chart into the Kubernetes cluster during the deployment of the SGP platform. See your cloud provider's corresponding deployment guide for more information.
## Configuration
System Manager is configured via `system-manager-config.json`. This file is stored in the cloud provider's secret manager. To modify the configuration, you can either update the secret directly or use the System Manager GUI, then restart the System Manager deployment to apply the changes.
```bash theme={null}
kubectl rollout restart deployment sgp-system-manager -n sgp-system-manager
```
An example configuration file is shown below. The `aws` block is only present for AWS deployments; GCP and Azure deployments use equivalent `gcp` and `azure` blocks instead. The `baseRepository` format also varies by cloud provider (e.g., `oci://-docker.pkg.dev//sgp--helm-repository` for GCP, `oci://.dkr.ecr..amazonaws.com/sgp--helm-repository` for AWS).
```json theme={null}
{
"cloudProvider": "",
"baseRepository": "",
"samlSetupEnabled": true,
"oidcSetupEnabled": true,
"deploymentURL": "https://.workspace.egp.scale.com",
"workspaceId": "",
"authType": "",
"baseDomain": "