This guide covers how to run Confidential Containers workloads across supported cloud and virtualization environments by using the Cloud API Adaptor (CAA) and the peer-pods runtime.
This deployment model provides strong workload isolation without tying the Kubernetes worker nodes to a particular virtualization or confidential-computing technology.
Because PodVMs are provisioned alongside the Kubernetes worker nodes rather than inside them, the worker nodes can themselves be virtual machines; bare-metal workers and nested virtualization are not required.
Note
The capabilities available to a workload depend on the infrastructure provider, region, machine type, and guest image.
A PodVM can use a supported Trusted Execution Environment (TEE), such as Intel® TDX, AMD SEV-SNP, or IBM Secure Execution, where available.
Some environments also support non-confidential VMs for development and testing.
Consult the provider-specific guide for current requirements and limitations.
At a high level, the Cloud API Adaptor integrates with a provider’s compute API or virtualization backend to provision a suitable VM for each pod.
The CAA then coordinates with the peer-pods runtime to run the pod workload in that dedicated PodVM.
When the selected environment provides a TEE, this architecture can protect the workload from the host or hypervisor and support attestation.
Application manifests remain largely portable between environments.
Provider-specific differences are handled during cluster setup and CAA configuration, including credentials, networking, regions or zones, instance types, VM images, and confidential-computing settings.
The provider-specific sections describe these differences and identify compatible machine types and isolation technologies where available.
Deployment workflow
The guides separate reusable CAA operations from cloud-specific configuration. Follow them in this order:
Start with Common setup to install the required tools and understand the shared deployment steps.
Follow one provider guide to create the cluster and configure its networking, identity, PodVM image, machine type, and Helm values:
Use Verify and clean up for the shared workload test and Kubernetes cleanup. The provider guide includes the commands to verify and remove cloud resources.
Each provider guide links back to the relevant shared step, so you can also begin directly with your provider.
Core concepts
Cloud API Adaptor
The Cloud API Adaptor is an implementation of the remote hypervisor interface of Kata Containers.
The CAA provisions Kata Containers VMs through the configured infrastructure backend instead of starting them on the worker nodes.
In Confidential Containers, a PodVM is the confidential virtual machine created to host a single Kubernetes Pod.
It is the VM boundary used by the peer-pods deployment model: when a pod is scheduled to a Confidential Containers runtime class, the runtime provisions a dedicated VM for that pod and then starts the pod’s containers inside the VM.
A PodVM typically includes:
a minimal guest OS,
a container runtime (for example, containerd), and
an agent that coordinates container lifecycle operations inside the guest.
From Kubernetes’ point of view, you still create a normal Pod (or a Deployment, Job, etc.).
The PodVM is an implementation detail of the runtime that provides a stronger isolation boundary than traditional containers:
No shared host kernel: the pod runs with its own guest kernel instead of the worker node’s kernel.
Dedicated VM boundary: the pod’s execution is isolated from other pods by virtualization.
Confidential computing support: when backed by Intel® TDX, memory inside the PodVM is protected from the host/hypervisor, and the platform can be attested before secrets are released.
In this guide, PodVMs are created on demand by the peer-pods runtime. The Cloud API Adaptor uses the configured infrastructure backend to provision and manage the VM instances that run those workloads.
Peer Pods
Peer Pods is a Confidential Containers deployment model where each Kubernetes Pod runs inside its own dedicated confidential VM (a PodVM).
Operationally, when you deploy a pod using a Confidential Containers RuntimeClass, the peer-pods components:
request the creation of a PodVM for that pod, using the configured isolation technology where available,
boot a minimal guest environment in the PodVM, and
start the pod’s containers inside the PodVM rather than on the Kubernetes worker node.
Across managed Kubernetes, self-managed clusters, and supported virtualization environments, the Cloud API Adaptor provides the infrastructure integration needed to provision the VM instances used for these PodVMs.
Key properties of Peer Pods:
Strong isolation per pod: Each pod gets a separate VM boundary. When a supported TEE is enabled, it can also protect the pod’s memory from the host or hypervisor.
Kubernetes-native workflow: You deploy standard Kubernetes resources (Pods/Deployments/Jobs). The runtime handles VM lifecycle without changing application manifests.
Attestation-ready: When supported by the selected TEE and environment, the PodVM can be attested to verify platform and workload identity before sensitive data is released.
Infrastructure-independent architecture: Provider-specific provisioning is handled by the Cloud API Adaptor, allowing peer pods to use compatible VM types across supported environments.
1 - Common Cloud API Adaptor setup
Setup steps shared by all Cloud API Adaptor cloud-provider deployments
Use this page together with one provider guide. The provider guide tells you when to complete each shared step and supplies the cloud-specific cluster, network, identity, PodVM image, and Helm values.
Caution: You can also use the latest tag, but it is not recommended,
because of its lack of version control and potential for unpredictable
updates, impacting stability and reproducibility in deployments.
Return to your provider guide to configure the PodVM image, machine type, credentials, and provider values before installing the chart.
2 - Providers
Cloud providers supported by Confidential Containers
2.1 - Alibaba Cloud (ACK)
Cloud API Adaptor (CAA) on Alibaba Cloud
This documentation will walk you through setting up CAA (a.k.a. Peer Pods) on Alibaba Cloud Container Service for Kubernetes (ACK) and Alibaba Cloud Elastic Compute Service (ECS). It explains how to deploy:
One worker for ACK Managed Cluster
CAA on that Kubernetes cluster
An Nginx pod backed by CAA pod VM on ECS
Note: Run the following commands from the following directory - src/cloud-api-adaptor
Note: Now Confidential Computing instances are available in some regions.
Note: Official document from Alibaba Cloud can be found here.
Wait for the cluster to be created. Get the vSwitch id of the cluster.
Then add one worker node to the cluster.
Add Internet access for the cluster VPC
exportVPC_ID=$(aliyun cs DescribeClusterDetail --ClusterId ${CLUSTER_ID}| jq -r ".vpc_id")exportVSWITCH_ID=$(echo${VSWITCH_IDS}| sed 's/[][]//g'| sed 's/"//g') aliyun vpc CreateNatGateway \
--region ${REGION_ID}\
--RegionId ${REGION_ID}\
--VpcId ${VPC_ID}\
--NatType Enhanced \
--VSwitchId ${VSWITCH_ID}\
--NetworkType internet
exportGATEWAY_ID="<NatGatewayId>"exportSNAT_TABLE_ID="<SnatTableId>"# The band width of the public ip (Mbps)exportBAND_WIDTH=5 aliyun vpc AllocateEipAddress \
--region ${REGION_ID}\
--RegionId ${REGION_ID}\
--Bandwidth ${BAND_WIDTH}exportEIP_ID="<AllocationId>"exportEIP_ADDRESS="<EipAddress>" aliyun vpc AssociateEipAddress \
--region ${REGION_ID}\
--RegionId ${REGION_ID}\
--AllocationId ${EIP_ID}\
--InstanceId ${GATEWAY_ID}\
--InstanceType Nat
aliyun vpc CreateSnatEntry \
--region ${REGION_ID}\
--RegionId ${REGION_ID}\
--SnatTableId ${SNAT_TABLE_ID}\
--SourceVSwitchId ${VSWITCH_ID}\
--SnatIp ${EIP_ADDRESS}
Grant role permissions
Give role permission to the cluster to allow the worker to create ECS instances.
Create the PodVM image
Note:
There is a pre-built Community Image (id:m-j6c3kfz2vhu6ze04wacb) for version 0.22.0 in cn-hongkong that you can use for testing.
You can also use the region-specific image IDs listed under Export PodVM image version below.
If you want to build a pod VM image yourself, follow the steps below. The current PodVM build uses mkosi and targets Ubuntu 26.04 (resolute). See the PodVM README for prerequisites and customization options.
Build the Ubuntu 26.04 PodVM image.
cd podvm
make
This builds the PodVM binaries and the OS image. The resulting qcow2 file is:
podvm/build/podvm-ubuntu-amd64.qcow2
To rebuild only the OS image after binaries are already present:
cd podvm
make image
Upload to OSS storage and create an ECS Image.
From src/cloud-api-adaptor, upload the qcow2 file to OSS (Object Storage Service):
This downloads the Helm artifacts, selects the CAA image, installs cert-manager, and creates the installation namespace.
Note: Keep the resulting CAA_VERSION, CAA_IMAGE, and CAA_TAG values in your terminal, then return here.
Export PodVM image version
Exports the PodVM image ID used by peer pods. This variable tells the deployment tooling which PodVM image version
to use when creating peer pod virtual machines in Alibaba Cloud.
exportIMAGEID="m-j6c3kfz2vhu6ze04wacb"
Note: Alibaba Cloud builds the images ahead of time. Different regions has different image id to use.
Note: Below example assumes that you are using RRSA for auth hence
ALIBABACLOUD_ACCESS_KEY_ID and ALIBABACLOUD_ACCESS_KEY_SECRET are not provided,
while ALIBABA_CLOUD_ROLE_ARN and ALIBABA_CLOUD_OIDC_PROVIDER_ARN are provided.
Peer Pods Helm Chart using Cloud API Adaptor (CAA) on AWS
This documentation will walk you through setting up CAA (a.k.a. Peer Pods) on AWS Elastic Kubernetes Service (EKS). It explains how to deploy:
A single worker node Kubernetes cluster using Elastic Kubernetes Service (EKS)
CAA on that Kubernetes cluster
A Nginx pod backed by CAA pod VM
Before you begin
Complete the common prerequisites, including the AWS-specific tools. Then return here to prepare AWS.
EKS Preparation
Set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY (or AWS_PROFILE) and AWS_REGION for AWS CLI access
Note: As an alternative to static credentials, you can use IRSA (IAM Roles for Service Accounts)
on EKS. With IRSA, the CAA pods authenticate via OIDC — no static AWS keys are stored in Kubernetes secrets.
You still need AWS_REGION and temporary credentials for the cluster setup steps below.
Set the region:
exportAWS_REGION="us-east-2"
Note: We have chose region us-east-2 as it has AMD SEV-SNP instances as well as prebuilt pod VM images readily available.
exportAWS_REGION="us-east-2"
Note: We have chose region us-east-2 because it has prebuilt pod VM images readily available.
Setup cluster
Make changes to the following environment variable as you see fit:
Port 15150 is the default port for CAA to connect to the agent-protocol-forwarder
running inside the pod VM.
Port 9000 is the VXLAN port used by CAA. Ensure it doesn’t conflict with the VXLAN port
used by the Kubernetes CNI.
Configure Authentication
Choose how CAA authenticates with AWS. Static credentials use a Kubernetes secret with access keys.
IRSA uses
OIDC federation so the pods assume an IAM role directly — no static keys needed.
Ensure AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (or AWS_PROFILE) are set in your environment.
These will be stored in a Kubernetes secret and used by the CAA pods.
No additional setup is needed here — the secret is created during the Helm chart deployment step.
IRSA eliminates the need for long-lived AWS access keys stored in Kubernetes secrets,
and is the recommended authentication method for EKS.
Enable OIDC Provider
Check if the IAM OIDC provider is already registered:
Create the IAM role and attach the AmazonEC2FullAccess managed policy:
aws iam create-role \
--role-name ${CAA_ROLE_NAME}\
--assume-role-policy-document file:///tmp/caa-trust-policy.json \
--description "IRSA role for Cloud API Adaptor on EKS"aws iam attach-role-policy \
--role-name ${CAA_ROLE_NAME}\
--policy-arn arn:aws:iam::aws:policy/AmazonEC2FullAccess
Note:AmazonEC2FullAccess grants broad EC2 permissions. For production workloads, it is strongly
recommended to replace it with a custom least-privilege policy scoped to the specific EC2 actions
CAA requires.
See the AWS IRSA documentation for more configuration options.
Export the role ARN for later use:
exportCAA_ROLE_ARN=$(aws iam get-role \
--role-name ${CAA_ROLE_NAME}\
--query 'Role.Arn'\
--output text)echo"CAA Role ARN: ${CAA_ROLE_ARN}"
Create IAM Role for Peerpod-ctrl (optional)
In case you disabled Peerpod-ctrl (--set resourceCtrl.enabled=false) you can skip this step.
aws iam create-role \
--role-name ${CTRL_ROLE_NAME}\
--assume-role-policy-document file:///tmp/peerpod-ctrl-trust-policy.json \
--description "IRSA role for PeerPod Controller on EKS"aws iam attach-role-policy \
--role-name ${CTRL_ROLE_NAME}\
--policy-arn arn:aws:iam::aws:policy/AmazonEC2FullAccess
Note:AmazonEC2FullAccess grants broad EC2 permissions. For production workloads, it is strongly
recommended to replace it with a custom least-privilege policy scoped to the specific EC2 actions
CAA requires.
See the AWS IRSA documentation for more configuration options.
Export the role ARN:
exportCTRL_ROLE_ARN=$(aws iam get-role \
--role-name ${CTRL_ROLE_NAME}\
--query 'Role.Arn'\
--output text)echo"Controller Role ARN: ${CTRL_ROLE_ARN}"
This downloads the Helm artifacts, selects the CAA image, installs cert-manager, and creates the installation namespace.
Note: Keep the resulting CAA_VERSION, CAA_IMAGE, and CAA_TAG values in your terminal, then return here.
Export PodVM image version
Exports the PodVM image ID used by peer pods. This variable tells the deployment tooling which PodVM image version
to use when creating peer pod virtual machines in AWS.
The image is pulled from the Coco community gallery (or manually built) and must match the current CAA release version.
We have a pre-built debug pod VM image available in us-east-2 for PoCs. You can find the AMI id for the release specific image
by running the following CLI:
The AMI must be available to your account in AWS_REGION.
There are no pre-built pod VM AMI for latest builds. You’ll need to build your own pod VM image and then create the AMI by following
the instructions here.
Remember to set TEE_PLATFORM=amd before building the pod VM image for AWS.
Once image build is finished, export image id to the environment variable PODVM_AMI_ID.
You can build your custom pod VM image by following the instructions here.
Remember to set TEE_PLATFORM=amd before building the pod VM image for AWS.
Once image build is finished, export image id to the environment variable PODVM_AMI_ID.
Note: --from-file=id_rsa.pub=${SSH_KEY} is optional. It allows user to SSH into the pod VMs for troubleshooting purposes.
This option works only for custom debug enabled pod VM images. The prebuilt pod VM images do not have SSH connection enabled.
Note: The resourceCtrl.serviceAccount.annotations line is only required if you are deploying Peerpod-ctrl.
The chart enables it by default. To disable it, add --set resourceCtrl.enabled=false, omit its annotation flag,
and skip the optional Peerpod-ctrl IAM role step above.
To verify IRSA is working, check the service account annotation and pod environment:
kubectl get serviceaccount cloud-api-adaptor \
-n confidential-containers-system \
-o jsonpath='{.metadata.annotations.eks\.amazonaws\.com/role-arn}'
Delete the EKS cluster by running the following command:
eksctl delete cluster --name=$CLUSTER_NAME
2.3 - Azure Kubernetes Service (AKS)
Peer Pods Helm Chart using Cloud API Adaptor (CAA) on Azure
This documentation will walk you through setting up Cloud API Adaptor (CAA) (a.k.a. Peer Pods) on
Azure Kubernetes Service (AKS).
It explains how to deploy:
A single worker node Kubernetes cluster using Azure Kubernetes Service (AKS),
CAA on that Kubernetes cluster,
A sample application deployed using CAA to verify that everything is working as expected.
Confidential Containers also supports using Azure Key Vault as a resource backend for Trustee.
More info
Before you begin
Complete the common prerequisites, including the Azure-specific tools. Then return here to prepare Azure.
AKS Preparation
Azure login
Authenticate with Azure by running the following command and following the instructions in the command line:
az login
Note: If you have access to multiple Azure accounts, make sure to select the one you want to use for this deployment in the command line after running az login.
Export the Azure subscription ID into AZ_SUBSCR_ID using the subscription currently selected in the Azure CLI context:
exportAZ_SUBSCR_ID=$(az account show --query id --output tsv)
Verify that the correct subscription is selected
az account show --query '{name:name, id:id, tenantId:tenantId}' -o yaml
Set the AZURE_REGION environment variable to the region you want to use for this deployment.
This region will be used to deploy both the AKS cluster and the peer pod VMs.
exportAZURE_REGION="eastus"
Note: We selected the eastus region as it not only offers AMD SEV-SNP machines but also has prebuilt pod VM images readily available.
exportAZURE_REGION="eastus"
Note: We selected the eastus region as it not only offers Intel® TDX machines but also has prebuilt pod VM images readily available.
Note: Currently Intel® TDX machines are available in a limited number of regions:
(westeurope, westus, WestUS3, eastus, EastUS2EUAP, northcentralus), so make sure to select a region that supports them.
exportAZURE_REGION="eastus"
Note: We have chose region eastus because it has prebuilt pod VM images readily available.
Resource group
Note: Skip this step if you already have a resource group you want to use.
Please, export the resource group name in the AZURE_RESOURCE_GROUP environment variable.
Create an Azure resource group by running the following command:
Export the AZURE_RESOURCE_GROUP environment variable to a unique name for the resource group:
Create the resource group in the specified region:
az group create \
--name "${AZURE_RESOURCE_GROUP}"\
--location "${AZURE_REGION}"
Setup cluster
Export environment variables related to the AKS cluster deployment:
exportCLUSTER_NAME="caa-$(date '+%Y%m%b%d%H%M%S')"exportAKS_WORKER_USER_NAME="azuser"exportAKS_RG="${AZURE_RESOURCE_GROUP}-aks"# SSH key is optional, but it allows user to SSH into the pod VMs for troubleshooting purposes. # This option works only for custom debug enabled pod VM images. # The prebuilt pod VM images do not have SSH connection enabled.exportSSH_KEY=~/.ssh/id_rsa.pub
Deploy AKS with single worker node to the same resource group you have created:
Note: Optionally, deploy the worker nodes into an existing Azure Virtual Network (VNet) and subnet by adding the following flag: --vnet-subnet-id <MY_SUBNET_ID>.
Note: For sample usage Standard_F4s_v2 machine type is used for the worker node - this machine is General purpose - without enabled confidentiality.
Download kubeconfig locally to access the cluster using kubectl:
az aks get-credentials \
--resource-group "${AZURE_RESOURCE_GROUP}"\
--name "${CLUSTER_NAME}"
User assigned identity and federated credentials
CAA needs privileges to talk to Azure API.
This privilege is granted to CAA by associating a workload identity to the CAA service account.
This workload identity (a.k.a. user assigned identity) is given permissions to create VMs, fetch images and join networks in the next step.
Note: If you use an existing AKS cluster it might need to be configured to support workload identity and OpenID Connect (OIDC), please refer to the instructions in this guide.
Create an identity for CAA:
Set the AZURE_WORKLOAD_IDENTITY_NAME environment variable to a name for the user assigned identity:
The VMs that will host Pods will commonly require access to internet services, e.g. to pull images from a public OCI registry.
Create a discrete subnet next to the AKS cluster subnet in the same VNet for peer pod VMs.
Then create a NAT gateway, attach a public IP, and associate the NAT gateway with the peer-pods subnet to provide outbound connectivity.
Export the subnet ID to the environment variable AZURE_SUBNET_ID:
exportAZURE_SUBNET_ID="$( az network vnet subnet show \
-g "$AKS_RG"\
--vnet-name "$AZURE_VNET_NAME"\
-n peerpod \
--query id \
-o tsv)"
AKS resource group permissions
For CAA to be able to manage VMs, assign Virtual Machine and Network related roles to the user assigned identity.
The roles grant permissions to create VMs in AZURE_RESOURCE_GROUP and attach them to the VNet/subnet in AKS_RG.
Assign the “Virtual Machine Contributor” role to the user assigned identity:
az role assignment create \
--role "Virtual Machine Contributor"\
--assignee "$USER_ASSIGNED_CLIENT_ID"\
--scope "/subscriptions/${AZ_SUBSCR_ID}/resourcegroups/${AZURE_RESOURCE_GROUP}"
Assign the “Reader” role to the user assigned identity:
az role assignment create \
--role "Reader"\
--assignee "$USER_ASSIGNED_CLIENT_ID"\
--scope "/subscriptions/${AZ_SUBSCR_ID}/resourcegroups/${AZURE_RESOURCE_GROUP}"
Assign the “Network Contributor” role to the user assigned identity:
az role assignment create \
--role "Network Contributor"\
--assignee "$USER_ASSIGNED_CLIENT_ID"\
--scope "/subscriptions/${AZ_SUBSCR_ID}/resourcegroups/${AKS_RG}"
Create the federated credential for the CAA ServiceAccount using the OIDC endpoint from the AKS cluster:
Set the AKS_OIDC_ISSUER environment variable to the OIDC issuer URL of your AKS cluster:
exportAKS_OIDC_ISSUER="$(az aks show \
--name "${CLUSTER_NAME}"\
--resource-group "${AZURE_RESOURCE_GROUP}"\
--query "oidcIssuerProfile.issuerUrl"\
-o tsv)"
Note: If you are using Calico Container Network Interface (CNI) on the Kubernetes cluster, then, configure Virtual Extensible LAN (VXLAN) encapsulation for all inter workload traffic.
Complete Prepare the CAA deployment.
This downloads the Helm artifacts, selects the CAA image, installs cert-manager, and creates the installation namespace.
Note: Keep the resulting CAA_VERSION, CAA_IMAGE, and CAA_TAG values in your terminal, then return here.
Export PodVM image version
Exports the PodVM image ID used by peer pods. This variable tells the deployment tooling which PodVM image version
to use when creating peer pod virtual machines in Azure.
The image is pulled from the Coco community gallery (or manually built) and must match the current CAA release version.
Export this environment variable to use for the peer pod VM:
This repository contains the necessary scripts and configurations to build the PodVM image.
Build the PodVM image
Navigate to the ../../../../../../cloud-api-adaptor/src/cloud-api-adaptor/podvm directory.
Build the binaries using below command:
ARCH=amd64 TEE_PLATFORM=az-cvm-vtpm \
make podvm-binaries
The ARCH parameter can be:
amd64 / x86_64: 64-bit x86 systems using Intel® or AMD processors
arm64 / aarch64: 64-bit Arm systems
s390x: 64-bit IBM systems
ppc64le: 64-bit IBM Power systems
The TEE_PLATFORM parameter can be:
none: for tests with non-confidential guests
all: for all following platforms
fs: for platforms with encrypted root filesystems (i.e. s390x)
tdx: for Intel® TDX
az-tdx-vtpm: for Intel® TDX with Azure vTPM
snp/amd: for AMD SEV-SNP
az-snp-vtpm: for AMD SEV-SNP with Azure vTPM
se: for IBM Secure Execution (SE)
Build the image:
Run below command to build the release image:
make image
Note: This will only build the pod VM image without SSH access.
Run command to build debug image
make image-debug
Note: This will only build the pod VM image with SSH access.
Convert QCOW2 image to Virtual Hard Disk (VHD) format
Caution: The below command can produce a VHD that is not properly aligned for Azure, which can cause issues when uploading and using the image.
To ensure that VHD image is properly aligned, follow the steps below to resize the QCOW2 image to a MiB boundary before converting it to VHD format.
Set the QCOW2 environment variable to the path of the generated QCOW2 image:
Azure requires VHD images to be aligned to 1 MiB, so the QCOW2 image must be resized to a multiple of 1 MiB before conversion.
Note: Currently the release image size is ~866MB in release builds, so resizing to 900MB ensures that the image is properly aligned while minimizing the additional space used.
Note: The flag --features SecurityType=ConfidentialVmSupported allows you to an upload custom image and boot it up as a Confidential Virtual Machine (CVM).
Note: --from-file=id_rsa.pub=${SSH_KEY} is optional. It allows user to SSH into the pod VMs for troubleshooting purposes.
This option works only for custom debug enabled pod VM images. The prebuilt pod VM images do not have SSH connection enabled.
Note: Above example assumes that you are using workload identity for authentication.
This line: --set-json daemonset.podLabels='{"azure.workload.identity/use":"true"}' is required only when using workload identity.
Generic Peer pods Helm charts deployment instructions are also described
here.
Reference values for the PCRs can be retrieved from the registry and used in attestation policies to verify the integrity of the Pod VM image before trusting it with sensitive workloads or secrets.
As part of a Pod VM image build expected PCR measurements are published into an OCI registry.
Pre-requisites
Install ORAS tool to pull the reference values from the OCI registry and GitHub CLI to verify its build provenance.
Verify build provenance
Assert that the measurements have been generated by a trusted build process on the official repository.
Specify --format=json to get more details about the build process.
Note: The e2-standard-4 machine type is used for the GKE cluster nodes, which is a general-purpose machine.
The UBUNTU_CONTAINERD image type is specified to ensure compatibility with the container runtime used by CAA.
kubectl get nodes \
--selector='!node-role.kubernetes.io/master'\
-o name \
| xargs -I{} kubectl label {} node.kubernetes.io/worker=
This labeling step adds the node.kubernetes.io/worker label to non-control-plane nodes.
Note
Starting with GKE version 1.27, GCP configures containerd with the discard_unpacked_layers=true flag to optimize disk
usage by removing compressed image layers after they are unpacked. However, this can cause issues with PeerPods,
as the workload may fail to locate required layers.
To avoid this, disable the discard_unpacked_layers setting in the containerd configuration.
If you encounter problem with VM’s not running check Troubleshooting section on this page.
Configure VPC network
We need to make sure port 15150 is open under the default VPC network:
For production scenarios, it is advisable to restrict the source IP range to
minimize security risks. For example, you can restrict the source range to a
specific IP address or CIDR block:
This repository contains the necessary scripts and configurations to build the PodVM image.
Build Steps:
Navigate to the ../../../../../../cloud-api-adaptor/src/cloud-api-adaptor/podvm directory.
Build the binaries using below command:
ARCH=amd64 TEE_PLATFORM=tdx \
make podvm-binaries
The ARCH parameter can be:
amd64 / x86_64: 64-bit x86 systems using Intel® or AMD processors
arm64 / aarch64: 64-bit Arm systems
s390x: 64-bit IBM systems
ppc64le: 64-bit IBM Power systems
The TEE_PLATFORM parameter can be:
none: for tests with non-confidential guests
all: for all following platforms
fs: for platforms with encrypted root filesystems (i.e. s390x)
tdx: for Intel® TDX
az-tdx-vtpm: for Intel® TDX with Azure vTPM
snp/amd: for AMD SEV-SNP
az-snp-vtpm: for AMD SEV-SNP with Azure vTPM
se: for IBM Secure Execution (SE)
Prepare SSH key to build debug image:
For using SSH, create a file resources/authorized_keys with your SSH public key.
Ensure the permissions are set to 0400 for the authorized_keys file.
SSH access is only possible for the root user.
Below are the commands to generate a new SSH key and create the authorized_keys file:
Create SSH key pair and copy public keys to proper location:
Note: TTL (time to live) is set to 0, which means that the key will not expire.
You can set it to any value you want, for example 1h for 1 hour or 30m for 30 minutes.
Build the debug image:
make image-debug
Note: This will build the pod VM image with SSH access.
Above command will produce ./build/system.raw (~1.6GB), a disk image that can be booted with an ESP/UEFI partition.
Prepare the raw disk image and package it as build/disk.tar.gz:
cp build/system.raw build/disk.raw &&\
tar -cvzf build/disk.tar.gz -C build disk.raw
Prerequisites:
Install the required tools:
Install the ORAS CLI tool for pulling OCI artifacts
Extract the qcow2 image from the downloaded tar.xz:
tar -xf podvm.tar.xz
Convert the qcow2 image to raw format:
mkdir -p build
qemu-img convert -O raw podvm-ubuntu-amd64.qcow2 build/disk.raw
Package the raw disk image as build/disk.tar.gz:
tar -cvzf build/disk.tar.gz -C build disk.raw
Publish image to Google Storage
Export the bucket name:
exportBUCKET_NAME="peerpods-bucket"
Note: GCP_PROJECT_ID and GCP_REGION should already be set from the GCP Preparation section.
The BUCKET_NAME should be globally unique across all of Google Cloud, so consider adding a random suffix if needed.
This command creates a new image in GCP with the specified name and the uploaded disk image.
The --guest-os-features flag ensures that the image is compatible with UEFI.
BothUEFI_COMPATIBLE and TDX_CAPABLE are required for tdx TEE.
This command creates a new image in GCP with the specified name and the uploaded disk image.
The --guest-os-features flag ensures that the image is compatible with UEFI and TDX.
Export PodVM image id
Export the PodVM image id to be used in the provider configuration. This is the name of the image created in the previous step.
exportPODVM_IMAGE_ID="$IMAGE_NAME"
Show command how to retrieve latest published image from GCP
Run below command to retrieve the latest published image from GCP:
Choose how CAA authenticates with GCP. You can use either static credentials (service account key file)
or Workload Identity Federation (WIF).
Note: Workload Identity Federation (WIF) is the recommended authentication method for GKE deployments.
With WIF, the CAA pods authenticate via OIDC — no static GCP credentials are stored in Kubernetes secrets.
Create GCP Service Account
Create a GCP service account with the necessary permissions for CAA to manage peer pod VMs:
exportGSA_NAME="cloud-api-adaptor"exportGSA_EMAIL="${GSA_NAME}@${GCP_PROJECT_ID}.iam.gserviceaccount.com"gcloud iam service-accounts create ${GSA_NAME}\
--description="Cloud API Adaptor Service Account"\
--display-name="Cloud API Adaptor Service Account"\
--project=${GCP_PROJECT_ID}gcloud projects add-iam-policy-binding ${GCP_PROJECT_ID}\
--member="serviceAccount:${GSA_EMAIL}"\
--role="roles/compute.instanceAdmin.v1"gcloud projects add-iam-policy-binding ${GCP_PROJECT_ID}\
--member="serviceAccount:${GSA_EMAIL}"\
--role="roles/iam.serviceAccountUser"
These roles allow the Cloud API Adaptor to:
create, start/stop, and delete the Compute Engine instances used as PodVMs (roles/compute.instanceAdmin.v1)
run actions as the service account when provisioning those resources (roles/iam.serviceAccountUser)
Note: IAM policy updates can take a few minutes to propagate. If later steps fail with permission errors, wait briefly and retry.
Choose Authentication Method
Generate and save the service account key file:
exportGOOGLE_APPLICATION_CREDENTIALS=~/.config/gcloud/peerpods_application_key.json
gcloud iam service-accounts keys create \
${GOOGLE_APPLICATION_CREDENTIALS}\
--iam-account=${GSA_EMAIL}
Workload Identity Federation eliminates the need for long-lived service account key files stored in Kubernetes secrets,
and is the recommended authentication method for GKE.
# Note: Use 'locations' instead of 'zones' for the OIDC issuer URLexportOIDC_ISSUER="https://container.googleapis.com/v1/projects/${GCP_PROJECT_ID}/locations/${GCP_REGION}-a/clusters/${CLUSTER_NAME}"exportPROJECT_NUMBER=$(gcloud projects describe ${GCP_PROJECT_ID}\
--format="value(projectNumber)")echo"OIDC Issuer: ${OIDC_ISSUER}"echo"Project Number: ${PROJECT_NUMBER}"# Verify the OIDC endpoint is accessiblecurl -s "${OIDC_ISSUER}/.well-known/openid-configuration"| jq .issuer
Create Workload Identity Pool and OIDC Provider
Create a custom workload identity pool for direct token exchange:
exportWI_POOL_NAME="caa-direct-wif-pool"gcloud iam workload-identity-pools create ${WI_POOL_NAME}\
--location=global \
--description="Workload Identity pool for cloud-api-adaptor with hostNetwork true"\
--display-name="CAA Direct WIF Pool"\
--project=${GCP_PROJECT_ID}
Note: If you get an error that the pool already exists, it may be in a soft-deleted state (GCP delays actual deletion for 30 days). Either use a different pool name or undelete it.
Bind Kubernetes Service Account to GCP Service Account
Allow the Kubernetes service account to impersonate the GCP service account:
gcloud iam service-accounts add-iam-policy-binding ${GSA_EMAIL}\
--member="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${WI_POOL_NAME}/subject/system:serviceaccount:${CAA_NAMESPACE}:${CAA_K8S_SERVICE_ACCOUNT}"\
--role="roles/iam.workloadIdentityUser"\
--project=${GCP_PROJECT_ID}gcloud iam service-accounts add-iam-policy-binding ${GSA_EMAIL}\
--member="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${WI_POOL_NAME}/subject/system:serviceaccount:${CAA_NAMESPACE}:${CAA_K8S_SERVICE_ACCOUNT}"\
--role="roles/iam.serviceAccountTokenCreator"\
--project=${GCP_PROJECT_ID}
Note: The binding uses principal:// with the full subject (system:serviceaccount:<namespace>:<name>) to scope impersonation to the exact Kubernetes service account. Using principalSet:// with only attribute.service_account_name would allow a service account with the same name in any namespace to impersonate the GSA.
Note: For more detailed configuration options and troubleshooting, see the
GCP WIF documentation.
Deploy helm chart
Create a ResourceQuota to allow system priority classes:
GKE enforces strict resource quota policies. The kata-deploy DaemonSet requires system
priority classes (system-node-critical or system-cluster-critical) to ensure proper
scheduling and execution. Create a ResourceQuota to allow these priority classes:
When using WIF, no GCP credentials secret is needed. The CAA pods authenticate via the
GCP service account configured in the Configure Authentication section.
This example showcases a more advanced deployment using TEE and confidential
VMs with the kata-remote runtime class. It demonstrates how to deploy a sample
pod and retrieve a secret securely within a confidential computing environment.
Prepare the init data configuration
Peerpods now supports init data, you can pass the required configuration files
(aa.toml, cdh.toml, and policy.rego) via the
io.katacontainers.config.hypervisor.cc_init_data annotation. Below is an example
of the configuration and usage.
Once the pod is successfully deployed with the initdata, you can retrieve secrets from the Trustee service running inside the pod.
Use the following command to fetch a specific secret:
Workload Identity Federation only: Delete the OIDC provider and workload identity pool created during setup:
gcloud iam workload-identity-pools providers delete ${OIDC_PROVIDER_NAME}\
--location=global \
--workload-identity-pool=${WI_POOL_NAME}\
--project=${GCP_PROJECT_ID}gcloud iam workload-identity-pools delete ${WI_POOL_NAME}\
--location=global \
--project=${GCP_PROJECT_ID}
Note: Deleting a pool or provider is a soft delete; GCP retains the resource for 30 days before permanently removing it. During this window the name cannot be reused unless you undelete it.
Delete the GKE cluster and confirm deletion when prompted:
Note: SSH connection is available only for debug image, which is built with enabled SSH server and added public key to authorized_keys file.
If you want to have SSH access to the image, make sure to build debug image and upload it to Google using above instructions.
After creating debug image with enabled SSH, deploy CoCo with sample pod and use root account to access it:
Note: Remember to add your public key to Google using CLI
Export environment variable GCP_PODVM_IP using below code:
Starting with GKE version 1.27, GCP configures containerd with the discard_unpacked_layers=true flag to optimize disk
usage by removing compressed image layers after they are unpacked. However, this can cause issues with PeerPods,
as the workload may fail to locate required layers.
To avoid this, disable the discard_unpacked_layers setting in the containerd configuration.
Most of the time you will see a generic message such as the following:
Error: failed to create containerd container: error unpacking image: failed to extract layer sha256:<SHA>: failed to get reader from content store: content digest sha256:<SHA>: not found
To disable the discard_unpacked_layers setting in the containerd configuration on Google Kubernetes Engine (GKE) version 1.27 or later, follow these steps:
Delete the sample deployment and wait for its PodVM to be removed:
kubectl delete deployment nginx
Before uninstalling CAA, find any other pods that use kata-remote and delete them.
For example, the following command finds all pods that use kata-remote and deletes them:
kubectl get pods -A -o custom-columns='NAME:.metadata.name,NAMESPACE:.metadata.namespace,RUNTIMECLASS:.spec.runtimeClassName'\
| grep kata-remote \
| awk '{print $1, $2}'\
| xargs -n 2 sh -c 'kubectl delete pod -n "$2" "$1"' _
Uninstall CAA
List deployed Confidential Containers Helm chart:
Note: This command assumes that only one Helm release is deployed in the confidential-containers-system namespace.
If there are multiple releases, you may need to adjust the command to select the correct one.
exportHELM_COCO_CHART_NAME=$(helm list \
-n confidential-containers-system \
--short)
Delete Confidential Containers related Helm chart:
This does not delete the Kubernetes cluster, cloud identities, networks, images, or other provider resources.
Return to your provider guide to remove those resources.