Skip to main content
This page collects practical Terraform examples for common Phala Cloud deployment scenarios. Each example is self-contained and can be adapted to your use case.

Basic Deployment

The simplest deployment: one app, one replica, public endpoint.
terraform {
  required_providers {
    phala = {
      source  = "phala-network/phala"
      version = "0.2.0-beta.1"
    }
  }
}

provider "phala" {}

resource "phala_app" "hello" {
  name      = "hello-phala"
  size      = "tdx.medium"
  region    = "US-WEST-1"
  image     = "dstack-dev-0.5.7-9b6a5239"
  disk_size = 40
  replicas  = 1

  docker_compose = <<-YAML
    services:
      web:
        image: nginx:stable
        ports:
          - "80:80"
  YAML

  wait_for_ready       = true
  wait_timeout_seconds = 900
}

output "app_id" {
  value = phala_app.hello.app_id
}

output "endpoint" {
  value = phala_app.hello.endpoint
}

Dynamic Discovery

Instead of hardcoding sizes, regions, and images, use data sources to discover valid values at plan time.
data "phala_sizes" "all" {}
data "phala_regions" "all" {}
data "phala_images" "all" {}

resource "phala_app" "web" {
  name      = "dynamic-app"
  size      = data.phala_sizes.all.sizes[0].slug
  region    = data.phala_regions.all.regions[0].slug
  image     = data.phala_images.all.images[0].slug
  disk_size = 40
  replicas  = 1

  docker_compose = <<-YAML
    services:
      web:
        image: nginx:stable
        ports:
          - "80:80"
  YAML

  wait_for_ready       = true
  wait_timeout_seconds = 900
}

Multi-Replica Deployment

Scale an application horizontally by setting replicas. All replicas share the same compose file and environment.
resource "phala_app" "api" {
  name      = "api-service"
  size      = "tdx.medium"
  region    = "US-WEST-1"
  replicas  = 3

  env = {
    LOG_LEVEL = "info"
  }

  docker_compose = <<-YAML
    services:
      api:
        image: myregistry/api:latest
        ports:
          - "8080:8080"
  YAML

  wait_for_ready       = true
  wait_timeout_seconds = 900
}

output "all_cvm_ids" {
  value = phala_app.api.cvm_ids
}

Cross-App Wiring

Deploy one app and pass its outputs (app ID, endpoint) as environment variables to a second app. Terraform handles the dependency ordering automatically.
data "phala_sizes" "all" {}
data "phala_regions" "all" {}

resource "phala_app" "api" {
  name     = "api-app"
  size     = data.phala_sizes.all.sizes[0].slug
  region   = data.phala_regions.all.regions[0].slug
  replicas = 2

  docker_compose = <<-YAML
    services:
      api:
        image: nginx:stable
        ports:
          - "80:80"
  YAML

  wait_for_ready       = true
  wait_timeout_seconds = 900
}

resource "phala_app" "consumer" {
  name     = "consumer-app"
  size     = data.phala_sizes.all.sizes[0].slug
  region   = data.phala_regions.all.regions[0].slug
  replicas = 1

  env = {
    UPSTREAM_APP_ID   = phala_app.api.app_id
    UPSTREAM_ENDPOINT = phala_app.api.endpoint
  }

  docker_compose = <<-YAML
    services:
      app:
        image: nginx:stable
        ports:
          - "80:80"
  YAML

  wait_for_ready       = true
  wait_timeout_seconds = 900
}

SSH Access

Inject SSH keys at deploy time for direct access into CVM instances. You can also manage account-level SSH keys separately with phala_ssh_key.
resource "phala_ssh_key" "laptop" {
  name       = "laptop"
  public_key = file("~/.ssh/id_ed25519.pub")
}

resource "phala_app" "web" {
  name     = "ssh-enabled-app"
  size     = "tdx.medium"
  region   = "US-WEST-1"
  replicas = 1

  ssh_authorized_keys = [
    file("~/.ssh/id_ed25519.pub"),
  ]

  env = {
    APP_SECRET = "replace-me"
  }

  docker_compose = <<-YAML
    services:
      web:
        image: nginx:stable
        ports:
          - "80:80"
  YAML

  wait_for_ready       = true
  wait_timeout_seconds = 900
}
The ssh_authorized_keys attribute is force-new. Changing the key list after initial deployment requires recreating the app. For keys you want to manage independently, use phala_ssh_key at the account level.

GPU Deployment

For GPU workloads, use a size from the GPU family. Discover available GPU sizes with the family filter.
data "phala_sizes" "gpu" {
  family = "gpu"
}

output "gpu_sizes" {
  value = data.phala_sizes.gpu.sizes[*].slug
}

resource "phala_app" "ml" {
  name     = "ml-inference"
  size     = data.phala_sizes.gpu.sizes[0].slug
  region   = "US-WEST-1"
  replicas = 1

  docker_compose = <<-YAML
    services:
      inference:
        image: myregistry/ml-model:latest
        ports:
          - "8000:8000"
  YAML

  wait_for_ready       = true
  wait_timeout_seconds = 900
}

Node Pinning

Pin a deployment to a specific worker node when you need deterministic hardware placement.
data "phala_nodes" "west" {
  region = "us-west"
}

resource "phala_app" "pinned" {
  name    = "pinned-app"
  size    = "tdx.medium"
  node_id = data.phala_nodes.west.nodes[0].node_id

  docker_compose = <<-YAML
    services:
      web:
        image: nginx:stable
        ports:
          - "80:80"
  YAML

  wait_for_ready       = true
  wait_timeout_seconds = 900
}

Power Management

Use phala_cvm_power to stop and start CVMs independently of the app lifecycle. This is useful for cost control or scheduled maintenance.
resource "phala_app" "web" {
  name      = "power-managed-app"
  size      = "tdx.medium"
  region    = "US-WEST-1"
  disk_size = 40
  replicas  = 1

  docker_compose = <<-YAML
    services:
      web:
        image: nginx:stable
        ports:
          - "80:80"
  YAML

  wait_for_ready       = true
  wait_timeout_seconds = 900
}

# Stop the CVM after deployment
resource "phala_cvm_power" "web" {
  cvm_id = phala_app.web.primary_cvm_id
  state  = "stopped"

  wait_for_state       = true
  wait_timeout_seconds = 900
}
To restart, change state to "running" and run terraform apply.

Attestation Verification

After deployment, fetch TEE attestation data to verify the CVM’s integrity.
resource "phala_app" "secure" {
  name     = "secure-app"
  size     = "tdx.medium"
  region   = "US-WEST-1"
  replicas = 1

  docker_compose = <<-YAML
    services:
      web:
        image: nginx:stable
        ports:
          - "80:80"
  YAML

  wait_for_ready       = true
  wait_timeout_seconds = 900
}

data "phala_attestation" "secure" {
  cvm_id = phala_app.secure.primary_cvm_id
}

output "is_online" {
  value = data.phala_attestation.secure.is_online
}

output "tcb_info" {
  value = data.phala_attestation.secure.tcb_info_json
}

Compose Runtime Settings

Control visibility and behavior flags at the compose level. Changing these triggers a compose update and CVM restart.
resource "phala_app" "monitored" {
  name     = "monitored-app"
  size     = "tdx.medium"
  region   = "US-WEST-1"
  replicas = 1

  public_logs     = true
  public_sysinfo  = true
  public_tcbinfo  = true
  gateway_enabled = true
  secure_time     = true

  docker_compose = <<-YAML
    services:
      web:
        image: nginx:stable
        ports:
          - "80:80"
  YAML

  wait_for_ready       = true
  wait_timeout_seconds = 900
}