BYOC Data Plane
Bring Your Own Cloud (BYOC) places the PromptQL Enterprise Data Plane in the customer's AWS account, GCP project, or Azure subscription. PromptQL provisions and operates the Data Plane through a customer-approved cloud identity and scoped permissions.
What you need to do
- Prepare the dedicated cloud boundary and confirm the target region.
- Confirm quotas, organization policies, network ranges, availability zones, and the private-connectivity plan.
- Apply the cloud-specific bootstrap in the applicable section below.
- Provide the required cloud identifiers and deployment parameters to PromptQL.
- Keep the cloud account, quotas, networking, and IAM trust available for ongoing management and upgrades.
PromptQL then uses the approved identity to provision and operate the Data Plane in your cloud account. You retain ownership of the account, network controls, audit logs, and the ability to revoke that identity. Availability is a shared responsibility.
An Enterprise agreement is required. Contact sales to begin provisioning.
AWS
AWS prerequisites
- Confirm the Enterprise deployment model and target AWS region with the PromptQL team.
- Confirm the target region is enabled and has sufficient service quotas.
- Record the Data Plane name, domain, availability-zone IDs, network CIDRs, and private-connectivity requirements.
- BYOC requires a dedicated AWS account with administrative access for the initial bootstrap.
AWS network and availability plan
- Select one AWS region and at least two Availability Zones. Use AZ IDs such as
use1-az1, not account-specific names such asus-east-1a. - Reserve a non-overlapping VPC CIDR. A /16 to /20 is recommended. If routable address space is constrained, AWS supports a /21 to /23 VPC CIDR when a separate /16 to /20 Pod IP CIDR is supplied.
- Reserve a non-overlapping Kubernetes Service CIDR. If using a Pod IP CIDR, it must not overlap either the VPC or service CIDR.
- Confirm the network path to each data source: VPC peering, Transit Gateway, VPN, PrivateLink, or another approved private path.
AWS customer tasks
- Prepare a dedicated AWS account and confirm the target region, quotas, availability zones, and network ranges.
- Deploy the CloudFormation bootstrap stack below in your account.
- Wait for the stack to complete and retrieve its Role ARN.
- Provide the Role ARN, region, network plan, and any custom External ID to PromptQL.
- Keep the bootstrap stack and IAM role in place while PromptQL operates the Data Plane.
PromptQL then assumes the scoped role to provision, upgrade, and operate the approved resources in your AWS account.
Detailed AWS setup
The setup involves creating an IAM role in your AWS account that establishes a trust relationship with PromptQL's AWS automation role (PulumiDDNCli). This role will be used to deploy and manage workloads necessary for PromptQL.
AWS setup requirements
- Dedicated AWS Account with administrative access
- AWS CLI installed and configured
AWS_REGIONenvironment variable set to your desired region (e.g.,export AWS_REGION=us-east-1)- Ensure the AWS region where you plan to deploy is enabled in your account
aws account get-regions --region-opt-status-contains ENABLED --query 'Regions[*].RegionName'
Apply the bootstrap
- Copy the following template and save it as
cloudformation.yaml
cloudformation.yaml
Parameters:
ExternalId:
Type: String
Default: hasura-cloud
Description: External ID for the trust relationship with PromptQL
MinLength: 2
MaxLength: 1224
AllowedPattern: "[A-Za-z0-9+=,.@:\\/-]*"
GCPProjectName:
Type: String
Default: hasura-lux
Description: GCP project ID for OIDC provider
MinLength: 1
MaxLength: 255
AllowedPattern: "[a-z0-9-]*"
Resources:
BootstrapRole:
Type: AWS::IAM::Role
Properties:
RoleName: HasuraCloudBYOC
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: arn:aws:iam::760537944023:role/PulumiDDNCli
Action: sts:AssumeRole
Condition:
StringEquals:
sts:ExternalId: !Ref ExternalId
BootstrapPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: HasuraCloudBYOC
Roles:
- !Ref BootstrapRole
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ec2:DescribeAddresses
- ec2:DescribeAddressesAttribute
- ec2:DescribeAvailabilityZones
- ec2:DescribeInternetGateways
- ec2:DescribeNatGateways
- ec2:DescribeNetworkInterfaces
- ec2:DescribeRegions
- ec2:DescribeRouteTables
- ec2:DescribeSecurityGroupRules
- ec2:DescribeSecurityGroups
- ec2:DescribeSubnets
- ec2:DescribeTags
- ec2:DescribeVpcAttribute
- ec2:DescribeVpcs
- ec2:DescribeVpcEndpointServices
- ec2:DescribeVpcEndpoints
- ec2:DescribeVpcEndpointServiceConfigurations
- eks:DeleteAddon
- eks:DescribeAddon
- eks:DescribeCluster
- eks:DescribeNodegroup
- eks:ListClusters
- iam:GetRole
- iam:GetServiceLinkedRoleDeletionStatus
- sqs:GetQueueAttributes
- rds:DescribeDBInstances
- rds:DescribeOrderableDBInstanceOptions
- s3:ListBucket
- s3:ListBucketVersions
- kms:ListAliases
- elasticloadbalancing:DescribeTargetGroups
- elasticloadbalancing:DescribeLoadBalancers
- elasticloadbalancing:DescribeTargetGroupAttributes
- elasticloadbalancing:DescribeLoadBalancerAttributes
- elasticloadbalancing:DescribeTags
- elasticloadbalancing:DescribeListeners
- elasticloadbalancing:DescribeListenerAttributes
Resource: '*'
- Effect: Allow
Action:
- ec2:AllocateAddress
- ec2:AssociateAddress
- ec2:AssociateRouteTable
- ec2:CreateInternetGateway
- ec2:CreateNatGateway
- ec2:CreateRoute
- ec2:CreateRouteTable
- ec2:CreateSubnet
- ec2:CreateTags
- ec2:CreateVpc
- ec2:CreateSecurityGroup
- ec2:AuthorizeSecurityGroupIngress
- ec2:CreateVpcEndpoint
- ec2:CreateVpcEndpointServiceConfiguration
- eks:CreateCluster
- eks:CreateNodegroup
- globalaccelerator:CreateAccelerator
- globalaccelerator:CreateEndpointGroup
- globalaccelerator:CreateListener
- globalaccelerator:TagResource
- sqs:CreateQueue
- sqs:TagQueue
- acm:RequestCertificate
- events:PutRule
- events:TagResource
- iam:CreateOpenIDConnectProvider
- iam:TagOpenIDConnectProvider
- rds:CreateDBSubnetGroup
- rds:CreateDBInstance
- elasticloadbalancing:CreateTargetGroup
- elasticloadbalancing:CreateLoadBalancer
- lambda:CreateFunction
Resource: '*'
Condition:
StringEquals:
aws:RequestTag/Created-By: HasuraCloud
- Effect: Allow
Action:
- ec2:CreateTags
Resource:
- !Sub arn:aws:ec2:*:${AWS::AccountId}:security-group/*
Condition:
StringEquals:
aws:RequestTag/karpenter.sh/discovery: dataplane
- Effect: Allow
Action:
- ec2:DeleteTags
Resource:
- !Sub arn:aws:ec2:*:${AWS::AccountId}:security-group/*
Condition:
StringEquals:
aws:ResourceTag/karpenter.sh/discovery: dataplane
- Effect: Allow
Action:
- ec2:AuthorizeSecurityGroupIngress
- ec2:RevokeSecurityGroupIngress
Resource:
- !Sub arn:aws:ec2:*:${AWS::AccountId}:security-group/*
Condition:
StringEquals:
aws:ResourceTag/aws:eks:cluster-name: dataplane
- Effect: Allow
Action:
- eks:AssociateAccessPolicy
- eks:DisassociateAccessPolicy
Resource:
- !Sub arn:aws:eks:*:${AWS::AccountId}:access-entry/dataplane/*
- Effect: Allow
Action:
- iam:AttachRolePolicy
- iam:CreateInstanceProfile
- iam:CreatePolicy
- iam:CreateRole
- iam:CreatePolicyVersion
- iam:DeleteInstanceProfile
- iam:DeleteOpenIDConnectProvider
- iam:DeletePolicy
- iam:DeleteRolePolicy
- iam:DeleteRole
- iam:DeleteServiceLinkedRole
- iam:DetachRolePolicy
- iam:UpdateAssumeRolePolicy
- iam:UpdateOpenIDConnectProviderThumbprint
- iam:GetInstanceProfile
- iam:GetOpenIDConnectProvider
- iam:GetPolicy
- iam:GetPolicyVersion
- iam:GetRolePolicy
- iam:ListAttachedRolePolicies
- iam:ListInstanceProfilesForRole
- iam:ListOpenIDConnectProviderTags
- iam:ListPolicyVersions
- iam:ListRolePolicies
- iam:PassRole
- iam:PutRolePolicy
- iam:RemoveRoleFromInstanceProfile
- iam:TagInstanceProfile
- iam:TagOpenIDConnectProvider
- iam:TagPolicy
- iam:TagRole
Resource:
# Roles
- !Sub arn:aws:iam::${AWS::AccountId}:role/KarpenterNodeRole
- !Sub arn:aws:iam::${AWS::AccountId}:role/KarpenterSandboxNodeRole
- !Sub arn:aws:iam::${AWS::AccountId}:role/eksClusterRole-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/lb-controller-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/autoscaler-controller-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/global-accelerator-operator-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/karpenter-controller-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/HasuraWorkloadAutomationRole-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/vpc-cni-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/ebsCsiDriverRole-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/kms-control-plane-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/bee-control-plane-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/bee-data-plane-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/promptql-artifact-server-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/dataplane-eks-api-lambda-role-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/promptql-warehouse-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/promptql-sandbox-broker-*
- !Sub arn:aws:iam::${AWS::AccountId}:role/promptql-sandbox-base-publisher-*
# Instance Profiles
- !Sub arn:aws:iam::${AWS::AccountId}:instance-profile/dataplane_*
# Policies
- !Sub arn:aws:iam::${AWS::AccountId}:policy/lb-controller-*
- !Sub arn:aws:iam::${AWS::AccountId}:policy/autoscaler-controller-*
- !Sub arn:aws:iam::${AWS::AccountId}:policy/global-accelerator-operator-*
- !Sub arn:aws:iam::${AWS::AccountId}:policy/karpenter-controller-*
- !Sub arn:aws:iam::${AWS::AccountId}:policy/dataplane-eks-api-register-targets-*
- !Sub arn:aws:iam::${AWS::AccountId}:policy/dataplane-eks-api-deregister-targets-*
# OIDC Providers
- !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/oidc.eks.*
- !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/container.googleapis.com/v1/projects/${GCPProjectName}/*
# Service Roles
- !Sub arn:aws:iam::${AWS::AccountId}:role/aws-service-role/spot.amazonaws.com/AWSServiceRoleForEC2Spot
- Effect: Allow
Action:
- iam:CreateServiceLinkedRole
Resource: '*'
Condition:
StringEquals:
iam:AWSServiceName:
- spot.amazonaws.com
- globalaccelerator.amazonaws.com
- eks.amazonaws.com
- eks-nodegroup.amazonaws.com
- rds.amazonaws.com
- Effect: Allow
Action:
- ec2:*
- eks:*
- globalaccelerator:*
- sqs:*
- acm:*
- events:*
- rds:*
- s3:*
- kms:*
- elasticloadbalancing:*
- lambda:*
Resource: '*'
Condition:
StringEquals:
aws:ResourceTag/Created-By: HasuraCloud
- Effect: Allow
Action:
- s3:Get*
- s3:CreateBucket
- s3:PutBucketPublicAccessBlock
- s3:PutBucketVersioning
- s3:PutBucketPolicy
- s3:PutBucketTagging
- s3:DeleteBucketPolicy
- s3:DeleteObjectVersion
- s3:DeleteBucket
Resource:
- !Sub arn:aws:s3:::metadata-store-keys-*
- !Sub arn:aws:s3:::metadata-store-*
- !Sub arn:aws:s3:::promptql-store-*
- !Sub arn:aws:s3:::promptql-warehouse-*
- !Sub arn:aws:s3:::promptql-sandbox-*
- Effect: Allow
Action:
- kms:CreateAlias
- kms:DeleteAlias
Resource:
- !Sub arn:aws:kms:*:${AWS::AccountId}:alias/bee
- Effect: Allow
Action:
- vpce:AllowMultiRegion
Resource: '*'
- Effect: Allow
Action:
- route53:AssociateVPCWithHostedZone
- route53:DisassociateVPCFromHostedZone
Resource: 'arn:aws:route53:::hostedzone/*'
Outputs:
RoleArn:
Description: "ARN of the BYOC IAM role"
Value: !GetAtt BootstrapRole.Arn
-
Deploy the CloudFormation stack:
First, check if the stack already exists:
aws cloudformation describe-stacks --stack-name hasura-cloud-byocThen, based on the result:
-
If you see an error "Stack with id hasura-cloud-byoc does not exist":
# Create new stack aws cloudformation create-stack \ --stack-name hasura-cloud-byoc \ --template-body file://cloudformation.yaml \ --capabilities CAPABILITY_NAMED_IAM \ --parameters ParameterKey=ExternalId,ParameterValue=hasura-cloud # Wait for creation to complete aws cloudformation wait stack-create-complete \ --stack-name hasura-cloud-byoc -
If the stack exists:
# Update existing stack aws cloudformation update-stack \ --stack-name hasura-cloud-byoc \ --template-body file://cloudformation.yaml \ --capabilities CAPABILITY_NAMED_IAM \ --parameters ParameterKey=ExternalId,ParameterValue=hasura-cloud # Wait for update to complete aws cloudformation wait stack-update-complete \ --stack-name hasura-cloud-byoc
-
-
Monitor stack status:
aws cloudformation describe-stacks \ --stack-name hasura-cloud-byoc \ --query 'Stacks[0].StackStatus' -
Once complete, retrieve the Role ARN:
aws cloudformation describe-stacks \ --stack-name hasura-cloud-byoc \ --query 'Stacks[0].Outputs[?OutputKey==`RoleArn`].OutputValue' \ --output text
AWS deployment details to provide
Share the following with the PromptQL team:
- (Required) Role ARN (From output above)
- (Required) AWS Region
- (Optional) External ID
- The external ID used in the trust relationship between your AWS account and PromptQL's AWS account
- This is the value you specified for the
ExternalIdparameter in the CloudFormation template - If not specified, the default value "hasura-cloud" will be used
- Must have a minimum of 2 characters and a maximum of 1,224 characters
- Must be alphanumeric without white space, but can include the following symbols: plus (+), equal (=), comma (,), period (.), at (@), colon (:), forward slash (/), and hyphen (-)
- Important: Make sure to provide this value to the PromptQL team if you've customized it
- (Optional) Preferred Availability Zones
- Use AZ IDs (e.g., use1-az1, use1-az2) instead of AZ names (us-east-1a, us-east-1b)
- You can get the AZ IDs by running:
aws ec2 describe-availability-zones \ --region <region> \ --output table \ --query "AvailabilityZones[?State=='available'] | [].{ZoneName: ZoneName, ZoneId: ZoneId}" - If you have specific zones which you'd like to use, please pass it along. Otherwise, PromptQL will assign accordingly.
- (Optional) VPC CIDR (/16-/19 CIDR)
- If you have a specific CIDR in mind for the VPC setup, please pass it along. If not specified, PromptQL will assign 10.0.0.0/16.
- A /21-/23 VPC CIDR is also supported if you provide a Pod IP CIDR (see below), which enables VPC CNI custom networking.
- Note: If you are planning to use VPC Peering, this CIDR should not conflict with any networks on your side.
- (Optional) Kubernetes Service CIDR (/16-20 CIDR)
- A /16-/20 CIDR block used for Kubernetes service cluster IP addresses in your Data Plane. If not specified, PromptQL will assign 172.20.0.0/16.
- Note: If you are planning to use VPC Peering, this CIDR should not conflict with any networks on your side.
- (Optional) Pod IP CIDR (/16-/20 CIDR)
- A /16-/20 CIDR block that Kubernetes pods draw IP addresses from, via AWS VPC CNI custom networking. When set, the VPC CIDR can be as small as /23. If not specified, PromptQL will assign 100.64.0.0/16.
- Optional for a /16-/20 VPC CIDR; required for a /21-/23 VPC CIDR. Must not overlap the VPC CIDR or the Kubernetes Service CIDR.
- Note: If you are planning to use VPC Peering, this CIDR should not conflict with any networks on your side.
GCP
GCP prerequisites
- Confirm the Enterprise deployment model and target GCP region with the PromptQL team.
- Record the Data Plane name, domain, zones, network CIDR, and private-connectivity requirements.
- BYOC requires a dedicated GCP project with billing enabled and permission to create project-level custom roles and IAM bindings.
GCP network and availability plan
- Select one GCP region and at least two zones.
- Reserve a non-overlapping VPC CIDR, normally /16 to /20.
- Confirm the network path to each data source: VPC Network Peering, VPN, Private Service Connect, or another approved private path.
- For BYOC, use a dedicated GCP project and confirm organization policies and quotas permit the required GKE, Cloud SQL, storage, load-balancing, KMS, and networking resources.
GCP customer tasks
- Prepare a dedicated GCP project with billing enabled and confirm the target region, quotas, organization policies, and network range.
- Apply the Terraform configuration below as a project administrator.
- Verify that all 12 custom roles and their IAM bindings were created.
- Provide the project ID, region, and network plan to PromptQL.
- Keep the custom roles, IAM bindings, required APIs, and project quotas available while PromptQL operates the Data Plane.
PromptQL then uses its scoped provisioning service account to provision, upgrade, and operate the approved resources in your GCP project.
Detailed GCP setup
The setup involves enabling APIs and creating IAM policy bindings. The IAM policy bindings establish a trust relationship with PromptQL's GCP service account, which is used to deploy and manage the infrastructure required for PromptQL.
GCP setup requirements
- Dedicated GCP Project
Apply the setup
- Create the project-level custom IAM roles for PromptQL's
ddn-automationservice account and bind them (see below). These grant only the granular permissions PromptQL's provisioning automation uses, avoiding the broader capabilities bundled into predefined roles such ascloudsql.adminorstorage.admin.
Keep this configuration up to date
New PromptQL features may require additional GCP permissions. When PromptQL provides an updated version of this Terraform configuration for a new feature, review its
terraform planand apply it to your GCP project before enabling that feature. This updates the custom roles and IAM bindings with the permissions the feature requires.If you see
PERMISSION_DENIEDafter applying the latest configuration, contact PromptQL support rather than changing the custom roles manually.
Copy the following and save it as main.tf, then apply it. It is the GCP counterpart to the AWS
CloudFormation template above. A single terraform apply enables the required APIs, creates all 12
custom roles, binds them to ddn-automation, and applies the IAM grant-restriction condition. When PromptQL provides
a newer version, replace your local main.tf, review the resulting terraform plan, and apply it.
main.tf
# GCP BYOC: least-privilege IAM for PromptQL.
#
# A single, self-contained Terraform config a project admin applies in their own project. It:
# 1. enables the APIs PromptQL's provisioning automation needs,
# 2. creates 12 custom least-privilege project roles (replacing the broad predefined roles),
# 3. binds them to PromptQL's `ddn-automation` service account,
# 4. applies an IAM grant-restriction condition on the four roles that carry a *.setIamPolicy
# permission, so `ddn-automation` may only grant the specific roles the provision hands out.
#
# Adding a permission later = edit the relevant role's list below and `terraform apply`. Widening
# what the automation may grant = add the role to the relevant role's `grantable_roles`.
terraform {
required_version = ">= 1.3"
required_providers {
google = {
source = "hashicorp/google"
version = ">= 5.0, < 7.0"
}
}
}
provider "google" {
project = var.project_id
}
locals {
member = "serviceAccount:${var.ddn_automation_service_account}"
# APIs the data-plane project must have enabled for provisioning to run.
required_apis = [
"compute.googleapis.com",
"container.googleapis.com",
"dns.googleapis.com",
"servicenetworking.googleapis.com",
"sqladmin.googleapis.com",
"cloudkms.googleapis.com",
"certificatemanager.googleapis.com",
"gkehub.googleapis.com",
"multiclusterservicediscovery.googleapis.com",
"multiclusteringress.googleapis.com",
"trafficdirector.googleapis.com",
"monitoring.googleapis.com",
"storage.googleapis.com",
"iam.googleapis.com",
"iamcredentials.googleapis.com",
"cloudresourcemanager.googleapis.com", # google_project_iam_member goes through Resource Manager
]
# Each setIamPolicy-bearing role below carries its OWN `grantable_roles` allowlist, scoped to
# exactly the roles ddn-automation grants at that resource level (project / SA / bucket / key).
# The grant-restriction condition on each binding is built from that per-role list: so e.g. the
# project-IAM role can grant only the three project-level roles, never storage/KMS roles.
# 12 custom roles. A role with `grants_iam = true` also defines `grantable_roles`: its binding
# gets a grant-restriction condition permitting ONLY those roles at that scope (see below).
custom_roles = {
compute_network_admin = {
role_id = "promptqlDdnComputeNetworkAdmin"
title = "PromptQL DDN - Compute Network Admin"
description = "Least-privilege replacement for roles/compute.networkAdmin, scoped to PromptQL provisioning"
permissions = [
"compute.networks.create",
"compute.networks.get",
"compute.networks.list",
"compute.networks.update",
"compute.networks.delete",
"compute.networks.use",
"compute.networks.updatePolicy", # add subnets/routers/PSA peering to the VPC
"compute.subnetworks.create",
"compute.subnetworks.get",
"compute.subnetworks.list",
"compute.subnetworks.update",
"compute.subnetworks.delete",
"compute.subnetworks.use",
"compute.subnetworks.setPrivateIpGoogleAccess",
"compute.routers.create",
"compute.routers.get",
"compute.routers.list",
"compute.routers.update", # also covers RouterNat create/delete (a router PATCH)
"compute.routers.delete",
"compute.routers.use",
"compute.addresses.create",
"compute.addresses.get",
"compute.addresses.list",
"compute.addresses.delete",
"compute.addresses.use",
"compute.globalAddresses.create", # external gateway IP
"compute.globalAddresses.get",
"compute.globalAddresses.list",
"compute.globalAddresses.delete",
"compute.globalAddresses.use",
"compute.globalAddresses.createInternal", # internal PSA range (AddressType=INTERNAL)
"compute.globalAddresses.deleteInternal", # tear down the PSA range on destroy
"compute.globalOperations.get",
"compute.globalOperations.list",
"compute.regionOperations.get",
"compute.regionOperations.list",
"compute.projects.get",
"servicenetworking.services.addPeering",
"servicenetworking.services.get",
"servicenetworking.services.deleteConnection", # tear down the PSA peering on destroy
"servicenetworking.operations.get",
]
}
dns_admin = {
role_id = "promptqlDdnDnsAdmin"
title = "PromptQL DDN - DNS Admin"
description = "Least-privilege replacement for roles/dns.admin, scoped to PromptQL provisioning"
permissions = [
"dns.managedZones.create",
"dns.managedZones.get",
"dns.managedZones.list",
"dns.managedZones.update",
"dns.managedZones.delete",
"dns.resourceRecordSets.create",
"dns.resourceRecordSets.get",
"dns.resourceRecordSets.list",
"dns.resourceRecordSets.update",
"dns.resourceRecordSets.delete",
"dns.changes.create",
"dns.changes.get",
"dns.changes.list",
"dns.managedZoneOperations.get",
"dns.managedZoneOperations.list",
"dns.projects.get",
"dns.networks.bindPrivateDNSZone", # bind the private managed zone to the VPC
]
}
gkehub_editor = {
role_id = "promptqlDdnGkeHubEditor"
title = "PromptQL DDN - GKE Hub Editor"
description = "Least-privilege replacement for roles/gkehub.editor, scoped to PromptQL provisioning"
permissions = [
"gkehub.features.create",
"gkehub.features.get",
"gkehub.features.list",
"gkehub.features.update",
"gkehub.features.delete",
"gkehub.memberships.create",
"gkehub.memberships.get",
"gkehub.memberships.list",
"gkehub.memberships.update",
"gkehub.memberships.delete",
"gkehub.operations.get",
"gkehub.operations.list",
"gkehub.locations.get",
"gkehub.locations.list",
]
}
container_cluster_admin = {
role_id = "promptqlDdnContainerClusterAdmin"
title = "PromptQL DDN - Container Cluster Admin"
description = "Least-privilege replacement for roles/container.clusterAdmin, scoped to PromptQL provisioning"
permissions = [
"container.clusters.create",
"container.clusters.get",
"container.clusters.list",
"container.clusters.update",
"container.clusters.delete",
"container.operations.get",
"container.operations.list",
"resourcemanager.projects.get",
"compute.instanceGroupManagers.get",
"compute.instanceGroupManagers.list",
"compute.instanceGroups.get",
"compute.instanceGroups.list",
"compute.instances.get",
"compute.instances.list",
]
}
monitoring_metrics_scopes_admin = {
role_id = "promptqlDdnMonitoringMetricsScopesAdmin"
title = "PromptQL DDN - Monitoring Metrics Scopes Admin"
description = "Least-privilege replacement for roles/monitoring.metricsScopesAdmin, scoped to PromptQL provisioning"
permissions = [
"monitoring.metricsScopes.link",
"resourcemanager.projects.get",
]
}
certificate_manager_editor = {
role_id = "promptqlDdnCertificateManagerEditor"
title = "PromptQL DDN - Certificate Manager Editor"
description = "Least-privilege replacement for roles/certificatemanager.editor, scoped to PromptQL provisioning"
permissions = [
"certificatemanager.certmaps.create",
"certificatemanager.certmaps.get",
"certificatemanager.certmaps.list",
"certificatemanager.certmaps.update",
"certificatemanager.certmaps.delete",
"certificatemanager.certmapentries.create",
"certificatemanager.certmapentries.get",
"certificatemanager.certmapentries.list",
"certificatemanager.certmapentries.update",
"certificatemanager.certmapentries.delete",
"certificatemanager.certs.create",
"certificatemanager.certs.get",
"certificatemanager.certs.list",
"certificatemanager.certs.update",
"certificatemanager.certs.delete",
"certificatemanager.dnsauthorizations.create",
"certificatemanager.dnsauthorizations.get",
"certificatemanager.dnsauthorizations.list",
"certificatemanager.dnsauthorizations.update",
"certificatemanager.dnsauthorizations.delete",
"certificatemanager.locations.get",
"certificatemanager.locations.list",
"certificatemanager.operations.get",
"certificatemanager.operations.list",
# .use perms: the SA itself creates the Certificate referencing the DnsAuthorization and
# the CertificateMapEntry referencing the cert during provision (not at GKE runtime).
"certificatemanager.dnsauthorizations.use",
"certificatemanager.certs.use",
"certificatemanager.certmaps.use",
]
}
project_iam_admin = {
role_id = "promptqlDdnProjectIamAdmin"
title = "PromptQL DDN - Project IAM Admin"
description = "Least-privilege replacement for roles/resourcemanager.projectIamAdmin, scoped to PromptQL provisioning"
grants_iam = true # resourcemanager.projects.setIamPolicy
# Only the three project-level roles the provision grants; never storage/KMS at project scope.
grantable_roles = [
"roles/container.defaultNodeServiceAccount", # -> GKE node SA
"roles/container.admin", # -> argocd
"roles/compute.networkViewer", # -> gke-mcs importer
]
permissions = [
"resourcemanager.projects.getIamPolicy",
"resourcemanager.projects.setIamPolicy",
]
}
service_account_admin = {
role_id = "promptqlDdnServiceAccountAdmin"
title = "PromptQL DDN - Service Account Admin"
description = "Least-privilege replacement for roles/iam.serviceAccountAdmin, scoped to PromptQL provisioning"
grants_iam = true # iam.serviceAccounts.setIamPolicy
# Only the actAs grant on the freshly-created GKE node SA.
grantable_roles = ["roles/iam.serviceAccountUser"]
permissions = [
"iam.serviceAccounts.create",
"iam.serviceAccounts.get",
"iam.serviceAccounts.list",
"iam.serviceAccounts.update",
"iam.serviceAccounts.delete",
"iam.serviceAccounts.getIamPolicy",
"iam.serviceAccounts.setIamPolicy",
]
}
cloudsql_admin = {
role_id = "promptqlDdnCloudsqlAdmin"
title = "PromptQL DDN - Cloud SQL Admin"
description = "Least-privilege replacement for roles/cloudsql.admin, scoped to PromptQL provisioning"
# Excludes cloudsql.instances.connect/export/import: the automation provisions instances,
# databases and users but never connects to or reads a customer database.
permissions = [
"cloudsql.instances.create",
"cloudsql.instances.get",
"cloudsql.instances.list",
"cloudsql.instances.update",
"cloudsql.instances.delete",
"cloudsql.databases.create",
"cloudsql.databases.get",
"cloudsql.databases.list",
"cloudsql.databases.update",
"cloudsql.databases.delete",
"cloudsql.users.create",
"cloudsql.users.get",
"cloudsql.users.list",
"cloudsql.users.update",
"cloudsql.users.delete",
]
}
workload_identity_pool_admin = {
role_id = "promptqlDdnWorkloadIdentityPoolAdmin"
title = "PromptQL DDN - Workload Identity Pool Admin"
description = "Least-privilege replacement for roles/iam.workloadIdentityPoolAdmin, scoped to PromptQL provisioning"
# Workload Identity Federation permissions use the service-qualified form in custom roles.
permissions = [
"iam.googleapis.com/workloadIdentityPools.create",
"iam.googleapis.com/workloadIdentityPools.get",
"iam.googleapis.com/workloadIdentityPools.list",
"iam.googleapis.com/workloadIdentityPools.update",
"iam.googleapis.com/workloadIdentityPools.delete",
"iam.googleapis.com/workloadIdentityPoolProviders.create",
"iam.googleapis.com/workloadIdentityPoolProviders.get",
"iam.googleapis.com/workloadIdentityPoolProviders.list",
"iam.googleapis.com/workloadIdentityPoolProviders.update",
"iam.googleapis.com/workloadIdentityPoolProviders.delete",
]
}
storage_admin = {
role_id = "promptqlDdnStorageAdmin"
title = "PromptQL DDN - Storage Admin"
description = "Least-privilege replacement for roles/storage.admin, scoped to PromptQL provisioning"
grants_iam = true # storage.buckets.setIamPolicy
# Only the bucket-level roles the provision grants to workloads (on buckets, never project-wide).
grantable_roles = [
"roles/storage.objectAdmin",
"roles/storage.objectViewer",
"roles/storage.legacyBucketReader",
]
# storage.objects.list/delete are needed because buckets are created with force_destroy,
# which empties objects before deleting the bucket; the automation never reads object content.
permissions = [
"storage.buckets.create",
"storage.buckets.get",
"storage.buckets.list",
"storage.buckets.update",
"storage.buckets.delete",
"storage.buckets.getIamPolicy",
"storage.buckets.setIamPolicy",
"storage.objects.list",
"storage.objects.delete",
]
}
cloudkms_admin = {
role_id = "promptqlDdnCloudkmsAdmin"
title = "PromptQL DDN - Cloud KMS Admin"
description = "Least-privilege replacement for roles/cloudkms.admin, scoped to PromptQL provisioning"
grants_iam = true # cloudkms.cryptoKeys.setIamPolicy
# Only the key-level roles the provision grants on the bee key (on the key, never project-wide).
grantable_roles = [
"roles/cloudkms.cryptoKeyDecrypter",
"roles/cloudkms.cryptoKeyEncrypterDecrypter",
]
# Key rings and keys cannot be deleted in GCP, so on `pulumi destroy` the provider lists and
# destroys the key's versions instead (cryptoKeyVersions.list/destroy). Still excludes
# encrypt/decrypt: the automation never performs cryptographic operations itself.
permissions = [
"cloudkms.keyRings.create",
"cloudkms.keyRings.get",
"cloudkms.keyRings.list",
"cloudkms.cryptoKeys.create",
"cloudkms.cryptoKeys.get",
"cloudkms.cryptoKeys.list",
"cloudkms.cryptoKeys.update",
"cloudkms.cryptoKeys.getIamPolicy",
"cloudkms.cryptoKeys.setIamPolicy",
"cloudkms.cryptoKeyVersions.list", # teardown: enumerate versions to destroy
"cloudkms.cryptoKeyVersions.destroy", # teardown: destroy versions (key itself can't be deleted)
]
}
}
}
# 1. Enable required APIs (disable_on_destroy=false so teardown never disables a shared API).
resource "google_project_service" "apis" {
for_each = toset(local.required_apis)
project = var.project_id
service = each.value
disable_on_destroy = false
disable_dependent_services = false
}
# 2. Create the 12 custom roles.
resource "google_project_iam_custom_role" "roles" {
for_each = local.custom_roles
project = var.project_id
role_id = each.value.role_id
title = each.value.title
description = each.value.description
permissions = each.value.permissions
stage = "GA"
# iam.googleapis.com (which backs custom roles) is enabled by this same module, so the roles
# must wait for API enablement: otherwise the first apply on a fresh project can race it.
depends_on = [google_project_service.apis]
}
# 3a. Bind roles that do NOT grant IAM to others (no grant-restriction condition needed).
resource "google_project_iam_member" "bindings" {
for_each = { for k, v in local.custom_roles : k => v if !try(v.grants_iam, false) }
project = var.project_id
role = google_project_iam_custom_role.roles[each.key].id
member = local.member
}
# 3b. Bind the four roles that carry a setIamPolicy permission, WITH the grant-restriction
# condition. modifiedGrantsByRole is only populated on setIamPolicy calls, so non-grant
# operations (create/get/delete/etc.) see an empty set and pass the condition unconditionally.
resource "google_project_iam_member" "iam_granting_bindings" {
for_each = { for k, v in local.custom_roles : k => v if try(v.grants_iam, false) }
project = var.project_id
role = google_project_iam_custom_role.roles[each.key].id
member = local.member
condition {
title = "Restrict IAM Granting for ddn-automation"
description = "Roles ddn-automation may grant via ${each.value.role_id}"
expression = "api.getAttribute(\"iam.googleapis.com/modifiedGrantsByRole\", []).hasOnly(${jsonencode(each.value.grantable_roles)})"
}
}
variable "project_id" {
type = string
description = "The GCP project ID where the PromptQL data plane will be provisioned."
}
variable "ddn_automation_service_account" {
type = string
description = "PromptQL's provisioning service account the custom roles are bound to."
default = "ddn-automation@hasura-ddn.iam.gserviceaccount.com"
validation {
condition = can(regex("^[^@]+@[^.]+\\.iam\\.gserviceaccount\\.com$", var.ddn_automation_service_account))
error_message = "ddn_automation_service_account must be a GCP service account email (name@project.iam.gserviceaccount.com)."
}
}
Apply it in your project:
export GCP_PROJECT_ID="your-project-id"
gcloud auth application-default login # credentials Terraform will use
terraform init
terraform apply -var project_id="${GCP_PROJECT_ID}"
Confirm all 12 roles were created:
gcloud iam roles list --project=${GCP_PROJECT_ID} --filter="name:promptqlDdn" --format="value(name)" | wc -l
# Should print 12
Note
The four roles that can modify IAM policies:
promptqlDdnProjectIamAdmin,promptqlDdnServiceAccountAdmin,promptqlDdnStorageAdminandpromptqlDdnCloudkmsAdmin: are bound with amodifiedGrantsByRolegrant-restriction condition. It restrictsddn-automationto granting only the specific roles the provision hands out, across project, service-account, bucket, and KMS-key scope (seegrantable_rolesin the Terraform), so the service account cannot escalate privileges by granting any other role.
GCP deployment details to provide
Share the following with the PromptQL team:
- (Required) Project ID
- (Required) GCP Region
- (Optional) Preferred Availability Zones
- If you have specific zones which you'd like to use, please pass it along. Otherwise, PromptQL will assign accordingly.
- (Optional) VPC CIDR (/16-/19 CIDR)
- If you have a specific CIDR in mind for the VPC setup, please pass it along. If not specified, PromptQL will assign 10.0.0.0/16.
- Note: If you are planning to use VPC Peering, this CIDR should not conflict with any networks on your side.
Azure
Azure prerequisites
- Confirm the deployment model and target Azure region with the PromptQL team.
- Record the Data Plane name, domain, availability zones, VNet CIDR, and private-connectivity requirements.
- BYOC requires a dedicated Azure subscription or approved resource groups, permission to register Enterprise applications, and permission to assign roles.
- Confirm the selected region and subscription have sufficient quotas for the required services.
Azure network and availability plan
- Select one Azure region and the required availability zones.
- Reserve a non-overlapping VNet CIDR, normally /16 to /20.
- Confirm the network path to each data source: VNet peering, VPN, Azure Private Link/Private Endpoint, or another approved private path.
- For BYOC, use a dedicated subscription or approved resource groups and confirm policies and quotas permit AKS, database, storage, load-balancing, identity, and networking resources.
Azure customer tasks
- Prepare a dedicated Azure subscription or approved resource groups and confirm the target region, quotas, policies, and VNet range.
- Register the PromptQL infrastructure-management service principal in your Microsoft Entra ID tenant.
- Deploy the role-assignment template below to the approved resource groups.
- Register the required AKS API server VNet integration feature.
- Provide the subscription ID, tenant ID, resource group names, region, and network plan to PromptQL.
- Keep the service-principal registration and role assignments in place while PromptQL operates the Data Plane.
PromptQL then uses the scoped service principal to provision, upgrade, and operate the approved resources in your Azure subscription.
Detailed Azure setup
Prerequisites
- Azure Account with administrative access
- Dedicated Azure Subscription where PromptQL will provision BYOC infrastructure
- Permissions to register Enterprise applications in your Azure AD tenant
- Permissions to assign roles at the subscription level
Provisioning Access
You need to register the PromptQL provisioning service principal in your Azure AD tenant:
For Infrastructure Provisioning (PrivateDdnInfra):
- Log in with an account that has permissions to register Enterprise applications
- Open a browser tab and navigate to the following URL, replacing
<TENANT_ID>with your Azure Tenant ID:https://login.microsoftonline.com/<TENANT_ID>/oauth2/authorize?client_id=4f7f1f59-f0b0-4adb-8603-2afacc50552b&response_type=code&redirect_uri=https://hasura.io/docs/3.0/private-ddn/creating-a-data-plane/byoc - Verify the service principal details match exactly:
- Client ID:
4f7f1f59-f0b0-4adb-8603-2afacc50552b - Name:
Hasura Cloud Infrastructure Manager
- Client ID:
- Select "Consent on behalf of your organization" and click "Accept"
- You will be redirected back to this documentation page, confirming the registration was completed
After completing the registration process, you can verify that the service principal was created successfully:
- Go to the Azure Portal
- Navigate to Azure Active Directory → Enterprise applications
- Search for "Hasura Cloud Infrastructure Manager" to confirm it appears in your tenant
Use the provided Azure Bicep template to automatically set up the required roles and permissions:
- Copy the following ARM template and save it as
role.json:
role.json
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.37.4.10188",
"templateHash": "17100090430408911702"
}
},
"parameters": {
"resourceGroupNames": {
"type": "array",
"metadata": {
"description": "An array of resource group names to assign the role in."
}
},
"principalId": {
"type": "string",
"metadata": {
"description": "The principal (service principal) ID to assign the role to."
}
},
"principalType": {
"type": "string",
"defaultValue": "ServicePrincipal",
"allowedValues": [
"User",
"Group",
"ServicePrincipal"
],
"metadata": {
"description": "The type of principal (User, Group, or ServicePrincipal)."
}
}
},
"resources": [
{
"type": "Microsoft.Authorization/roleDefinitions",
"apiVersion": "2022-04-01",
"name": "[guid(subscription().id, 'HasuraCloudBYOCRole')]",
"properties": {
"roleName": "HasuraCloudBYOC",
"description": "Role for PromptQL BYOC",
"type": "CustomRole",
"assignableScopes": [
"[subscription().id]"
],
"permissions": [
{
"actions": [
"Microsoft.DBforPostgreSQL/flexibleServers/*"
]
}
]
}
},
{
"copy": {
"name": "roleAssignments",
"count": "[length(parameters('resourceGroupNames'))]"
},
"type": "Microsoft.Resources/deployments",
"apiVersion": "2022-09-01",
"name": "[format('roleAssignment-{0}', parameters('resourceGroupNames')[copyIndex()])]",
"resourceGroup": "[parameters('resourceGroupNames')[copyIndex()]]",
"properties": {
"expressionEvaluationOptions": {
"scope": "inner"
},
"mode": "Incremental",
"parameters": {
"customRoleDefinitionId": {
"value": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', guid(subscription().id, 'HasuraCloudBYOCRole'))]"
},
"principalId": {
"value": "[parameters('principalId')]"
},
"principalType": {
"value": "[parameters('principalType')]"
}
},
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.37.4.10188",
"templateHash": "4955557183172246065"
}
},
"parameters": {
"customRoleDefinitionId": {
"type": "string",
"metadata": {
"description": "The custom role definition ID to assign."
}
},
"principalId": {
"type": "string",
"metadata": {
"description": "The principal (user, group, service principal) object ID to assign the role to."
}
},
"principalType": {
"type": "string",
"defaultValue": "ServicePrincipal",
"allowedValues": [
"User",
"Group",
"ServicePrincipal"
],
"metadata": {
"description": "The type of principal (User, Group, or ServicePrincipal)."
}
}
},
"variables": {
"networkContributorRoleId": "4d97b98b-1d4f-4787-a291-c67834d212e7",
"managedIdentityContributorRoleId": "e40ec5ca-96e0-45a2-b4ff-59039f2c2b59",
"managedIdentityOperatorRoleId": "f1a07417-d97a-45cb-824c-7a7467783830",
"aksContributorRoleId": "ed7f3fbd-7b88-4dd4-9017-9adb7ce333f8",
"storageAccountContributorRoleId": "17d1049b-9a84-46fb-8f53-869881c3d3ab",
"rbacAdministratorRoleId": "f58310d9-a9f6-439a-9e8d-f62e7b41a168",
"rbacCondition": "!(ActionMatches{'Microsoft.Authorization/roleAssignments/write'} OR ActionMatches{'Microsoft.Authorization/roleAssignments/delete'}) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {4d97b98b-1d4f-4787-a291-c67834d212e7, acdd72a7-3385-48ef-bd42-f606fba81ae7, b1ff04bb-8a4e-4dc4-8eb5-8693973ce19b, ba92f5b4-2d11-453d-a403-e96b0029c9fe, 2a2b9908-6ea1-4ae2-8e65-a410df84e7d1} AND @Request[Microsoft.Authorization/roleAssignments:PrincipalType] ForAnyOfAnyValues:StringEqualsIgnoreCase {'ServicePrincipal', 'MSI'})"
},
"resources": [
{
"type": "Microsoft.Authorization/roleAssignments",
"apiVersion": "2022-04-01",
"name": "[guid(resourceGroup().id, parameters('customRoleDefinitionId'), parameters('principalId'), 'custom')]",
"properties": {
"roleDefinitionId": "[parameters('customRoleDefinitionId')]",
"principalId": "[parameters('principalId')]",
"principalType": "[parameters('principalType')]"
}
},
{
"type": "Microsoft.Authorization/roleAssignments",
"apiVersion": "2022-04-01",
"name": "[guid(resourceGroup().id, variables('networkContributorRoleId'), parameters('principalId'), 'network')]",
"properties": {
"roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('networkContributorRoleId'))]",
"principalId": "[parameters('principalId')]",
"principalType": "[parameters('principalType')]"
}
},
{
"type": "Microsoft.Authorization/roleAssignments",
"apiVersion": "2022-04-01",
"name": "[guid(resourceGroup().id, variables('managedIdentityContributorRoleId'), parameters('principalId'), 'identity')]",
"properties": {
"roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('managedIdentityContributorRoleId'))]",
"principalId": "[parameters('principalId')]",
"principalType": "[parameters('principalType')]"
}
},
{
"type": "Microsoft.Authorization/roleAssignments",
"apiVersion": "2022-04-01",
"name": "[guid(resourceGroup().id, variables('managedIdentityOperatorRoleId'), parameters('principalId'), 'operator')]",
"properties": {
"roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('managedIdentityOperatorRoleId'))]",
"principalId": "[parameters('principalId')]",
"principalType": "[parameters('principalType')]"
}
},
{
"type": "Microsoft.Authorization/roleAssignments",
"apiVersion": "2022-04-01",
"name": "[guid(resourceGroup().id, variables('aksContributorRoleId'), parameters('principalId'), 'aks')]",
"properties": {
"roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('aksContributorRoleId'))]",
"principalId": "[parameters('principalId')]",
"principalType": "[parameters('principalType')]"
}
},
{
"type": "Microsoft.Authorization/roleAssignments",
"apiVersion": "2022-04-01",
"name": "[guid(resourceGroup().id, variables('storageAccountContributorRoleId'), parameters('principalId'), 'storage')]",
"properties": {
"roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('storageAccountContributorRoleId'))]",
"principalId": "[parameters('principalId')]",
"principalType": "[parameters('principalType')]"
}
},
{
"type": "Microsoft.Authorization/roleAssignments",
"apiVersion": "2022-04-01",
"name": "[guid(resourceGroup().id, variables('rbacAdministratorRoleId'), parameters('principalId'), 'rbac')]",
"properties": {
"roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('rbacAdministratorRoleId'))]",
"principalId": "[parameters('principalId')]",
"principalType": "[parameters('principalType')]",
"condition": "[variables('rbacCondition')]",
"conditionVersion": "2.0"
}
}
]
}
},
"dependsOn": [
"[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', guid(subscription().id, 'HasuraCloudBYOCRole'))]"
]
}
]
}
-
Deploy the template using Azure CLI:
# Set your subscription az account set --subscription "your-subscription-id" # Get the Object ID of the PromptQL provisioning service principal PRINCIPAL_ID=$(az ad sp list --filter "appId eq '4f7f1f59-f0b0-4adb-8603-2afacc50552b'" --query "[0].id" --output tsv) # Deploy the template az deployment sub create \ --name "hasura-cloud-byoc" \ --location "East US" \ --template-file role.json \ --parameters \ resourceGroupNames='["your-resource-group-name"]' \ principalId="$PRINCIPAL_ID" \ principalType="ServicePrincipal"Replace the following values:
your-subscription-id: Your Azure subscription IDyour-resource-group-name: The resource group where PromptQL will operate (you can specify multiple resource groups:'["rg1","rg2","rg3"]')"East US": Your preferred Azure region
-
Monitor the deployment:
az deployment sub show --name hasura-cloud-byoc --query 'properties.provisioningState'
Additional Requirements
Register the required Azure feature flag for API server VNet integration:
az feature register --namespace "Microsoft.ContainerService" --name "EnableAPIServerVnetIntegrationPreview"
You can check the registration status with:
az feature show --namespace "Microsoft.ContainerService" --name "EnableAPIServerVnetIntegrationPreview"
Azure deployment details to provide
Gather the following information from your Azure Portal and provide it to the PromptQL team:
Required:
- Subscription ID: The target subscription where BYOC infrastructure will be deployed
- Tenant ID: Your Azure AD tenant identifier
- Resource Group Name(s): The resource group(s) where PromptQL will operate
- Azure Region: The target region for deployment (e.g., "East US", "West Europe")
Optional:
- Preferred Availability Zones: Specific logical zone IDs within your chosen region
- If not specified, PromptQL will select zones automatically
- Use logical zone IDs (e.g., "1", "2", "3") which map to physical zones
- You can get the zone mappings by running:
Replaceaz rest --method get \ --uri '/subscriptions/{subscriptionId}/locations?api-version=2022-12-01' \ --query 'value[?name==`eastus` && availabilityZoneMappings != `null`].{displayName: displayName, name: name, availabilityZoneMappings: availabilityZoneMappings}'eastuswith your desired region name and{subscriptionId}with your subscription ID - Example: ["1", "2", "3"] for logical zones 1, 2, and 3
- VNet CIDR: Custom CIDR block for the virtual network (/16-/19 range)
- Default: 10.0.0.0/16 if not specified
- Important: Ensure this doesn't conflict with existing networks if using VNet peering