Prepare launch inputs

Select approved images, create operator keys, and generate the documents that bind CDS and workers to your deployment.

Prepare these files on a trusted operator machine. The next step boots CDS replicas and admits the first worker using these exact files.

Before you start

You need:

  • A substrate that launches Intel TDX or AMD SEV-SNP VMs with the published measured boot artifacts, read-only launch disks, primary IPv4 addresses, and working hardware attestation.
  • For TDX, host quote generation (qgsd), platform registration, and Intel certificate collateral access.
  • Three independent CDS VM slots and at least one worker slot. Spread CDS replicas across failure domains if you need host-failure availability.
  • An encrypted scratch backing disk of at least 64 GiB for each worker.
  • An external Kubernetes control plane compatible with the worker's v1.36.4 kubelet, its admin.kubeconfig, API endpoint as host:port, and one PEM serving-CA certificate saved as ca.crt.
  • Bootstrap token authentication, kubelet CSR signing/approval/rotation, and node pod-CIDR allocation enabled on that control plane. Disable its bundled CNI, kube-proxy, CoreDNS, ingress, and service load balancer: workers supply Cilium, kube-proxy, and CoreDNS.
  • A bootstrap token in the form abcdef.0123456789abcdef, kept privately for the worker launch. For k3s/RKE2, extract the token after the K10<hash>:: prefix.
  • Worker, pod, and Service IPv4 CIDRs; a cluster DNS IP inside the Service CIDR; DNS for three CDS replicas, a CDS client endpoint, and the router's TLS name.
  • Go 1.27.3, Git, the GitHub CLI, Helm 4.2.4, ORAS, OpenSSL, sha256sum, and genisoimage on PATH. Registry credentials must permit pulling the selected artifacts.

For a k3s server, the documented control-plane settings are:

disable-agent: true
egress-selector-mode: disabled
flannel-backend: none
disable-kube-proxy: true
disable:
  - coredns
  - traefik
  - servicelb
  - local-storage
  - metrics-server

Configure those before starting the server.

RKE2 also requires its bundled ingress-nginx removed; that substrate is less tested. Keep control-plane installation and credentials under your normal Kubernetes operating procedures.

Build the matching CLI

Use a new working directory for this deployment. Build the CLI and its bundled chart from the same source revision as these instructions:

gh repo clone confidential-dot-ai/C8s c8s-source
git -C c8s-source switch --detach 640b2d64afacb5696e66752eb5c834433336e4ed
mkdir bin
(cd c8s-source && go build -o ../bin/c8s ./cmd/c8s)
export PATH="$PWD/bin:$PATH"

Keep this directory and shell for the remaining tasks.

Acquire the approved artifacts

Obtain independently approved guest OCI digests. The matching v0.99.1 release chart supplies the operator/get-cert and armtls-mesh container digests used by this worker build.

The published artifact candidates for this source are:

  • Worker: ghcr.io/confidential-dot-ai/guests/worker:v0.99.1-tdx.
  • CDS: ghcr.io/confidential-dot-ai/guests/cds:sha-640b2d6.

For SEV-SNP use worker tag v0.99.1-sev-snp. CDS has a commit tag. The -kubevirt tags contain CDI import images; the commands here pull ORAS build artifacts with their manifests and boot files.

Replace the angle-bracket values below, then run this in Bash:

set -euo pipefail
WORKER_REPO=ghcr.io/confidential-dot-ai/guests/worker
CDS_REPO=ghcr.io/confidential-dot-ai/guests/cds
WORKER_DIGEST='<APPROVED_WORKER_OCI_DIGEST>'
CDS_DIGEST='<APPROVED_CDS_OCI_DIGEST>'
helm pull oci://ghcr.io/confidential-dot-ai/charts/c8s-cloud \
  --version 0.99.1 --untar --untardir release
OPERATOR_DIGEST=$(helm show values release/c8s-cloud --jsonpath '{.image.digest}')
MESH_DIGEST=$(helm show values release/c8s-cloud --jsonpath '{.armtlsMesh.image.digest}')
test "$(oras resolve "$WORKER_REPO:v0.99.1-tdx")" = "$WORKER_DIGEST"
test "$(oras resolve "$CDS_REPO:sha-640b2d6")" = "$CDS_DIGEST"
oras pull "$WORKER_REPO@$WORKER_DIGEST" -o artifacts/worker
oras pull "$CDS_REPO@$CDS_DIGEST" -o artifacts/cds
cp artifacts/worker/manifest.json worker-manifest.json
cp artifacts/cds/manifest.json cds-manifest.json
sha256sum worker-manifest.json cds-manifest.json > manifest-checksums.txt

Each digest has the form sha256: followed by 64 lowercase hex digits. ORAS checks downloaded content against its OCI digests.

Approve the producer and immutable digest independently before downloading: deriving pins from an unapproved manifest only pins an unapproved image.

Verify the release statement using the release verification instructions, then compare the chart digest printed by helm pull with its chart field. A downloaded chart alone does not establish publisher approval.

Retain that statement, the chart, OCI references, manifests, and boot artifacts together.

Create the operator key

The private key authorizes CDS policy changes and signed cluster decrees. Keep it on the trusted operator machine, with a secure offline recovery copy. Only its public half belongs in VM launch inputs.

umask 077
openssl ecparam -name prime256v1 -genkey -noout -out operator.key
openssl ec -in operator.key -pubout -out operator.pub

Generate the launch documents

Set these values from your control plane and network plan. The example DNS names in the commands must resolve in your deployment; replace them together. NODE_CIDR contains worker primary addresses and must not overlap pod or Service ranges.

NODE_CIDR='<WORKER_IPV4_CIDR>'
POD_CIDR='<POD_IPV4_CIDR>'
SERVICE_CIDR='<SERVICE_IPV4_CIDR>'
CLUSTER_DNS='<CLUSTER_DNS_IPV4>'
API_SERVER='<KUBERNETES_API_HOST:PORT>'

c8s node-image render \
  --hardware-platform tdx --kube-version v1.36.4 \
  --image-digest "$OPERATOR_DIGEST" \
  --armtls-mesh-image-digest "$MESH_DIGEST" --output-dir render

c8s cds config new --out cds \
  --cluster-id example --operator-keys operator.pub \
  --allowlist-seed render/allowlist-seed.json \
  --node-cidr "$NODE_CIDR" --pod-cidr "$POD_CIDR" \
  --service-cidr "$SERVICE_CIDR" --leaf-dns-name api.example.com \
  --replica cds-1.example.com --replica cds-2.example.com \
  --replica cds-3.example.com --voters 3 \
  --image-manifest cds-manifest.json --platform tdx

c8s worker new --out worker.yaml \
  --cds-address cds.example.com:8443 --cds-pin cds/cds-pin.json \
  --tls-san api.example.com --api-server "$API_SERVER" \
  --ca-certificate ca.crt --pod-cidr "$POD_CIDR" \
  --service-cidr "$SERVICE_CIDR" --cluster-dns "$CLUSTER_DNS"

c8s measurements derive --tee tdx \
  --out worker-image.json worker-manifest.json
c8s measurements lint worker-image.json
c8s measurements lint cds/cds-pin.json
c8s allowlist lint render/allowlist-seed.json --strict

cds/ and worker.yaml must not already exist. Use the same CLI/chart and component digests as the selected worker build.

For SEV-SNP, change tdx to sev-snp, including the artifact tag check, and add --vcpus 4 to cds config new only if that CDS manifest contains the four-vCPU variant. Select the matching IGVM variant when launching each VM.

The generated documents have distinct roles:

  • render/allowlist-seed.json supplies platform workload admission.
  • The CDS configuration fixes operator public keys, replica discovery, permitted networks, and leaf DNS names.
  • cds/cds-pin.json binds the approved CDS image to those exact configuration bytes.
  • worker.yaml embeds that CDS pin and the external API's CA, endpoint, and cluster settings.

worker-image.json contains the worker build's expected measurements without a launch binding. Genesis adds the digest of worker.yaml when it admits the first worker. Keep that document byte-for-byte unchanged after approval.

Keep these generated files together:

  • operator.key and operator.pub.
  • Both artifact directories, worker-manifest.json, and cds-manifest.json.
  • render/allowlist-seed.json.
  • cds/cds-config.yaml and cds/cds-pin.json.
  • worker.yaml and worker-image.json.

Keep admin.kubeconfig, ca.crt, and the bootstrap token for the worker step.

Continue to Start policy enforcement.