Skip to main content
Quick start

Terraform

Define a sandbox group as code and create an Ubuntu sandbox with one Terraform configuration.

Before you begin
  • An Azure subscription with permission to create resource groups and role assignments.
  • Terraform 1.11 or later.
  • Git, required to download the pinned Sandbox Group module.
  • Azure CLI (az) installed and signed in with a user account.
  • Bash with curl and unzip on Linux or macOS, or PowerShell on Windows.
  • Permission to create resource groups and role assignments.
Preview

Azure Container Apps Terraform provider is in public preview. It is distributed as a GitHub prerelease, not through the Terraform Registry.

What you can define​

The sandbox_groups module provides the same group-level controls as Bicep:

  • Set default CPU, memory, and disk, a maximum sandbox count, and a default timeout.
  • Attach a system- or user-assigned managed identity.
  • Connect the group to a delegated VNet subnet.
  • Grant data-plane access with the Container Apps SandboxGroup Data Owner role.

The native Azure/aca provider also creates individual sandboxes and private disk images through the regional Sandbox data plane. The end-to-end example creates an Ubuntu sandbox with 1 vCPU, 2 GiB of memory, a 20 GiB disk, denied outbound traffic, and memory-preserving auto-suspend after 10 minutes.

Core objects​

ObjectPurposeTerraform implementation
Resource groupContainer for the quickstart resources.azurerm_resource_group
Microsoft.App/sandboxGroupsHolds sandboxes, snapshots, disk images, connections, and egress policies.Pinned sandbox_groups module using AzAPI
Data-plane role assignmentGrants permission to create and manage sandboxes.data_plane_operators in the sandbox_groups module
Public Ubuntu imageResolves the service-provided Ubuntu disk image.aca_sandbox_public_disk_image
Individual sandboxDefines resources, lifecycle, egress, labels, and startup command.aca_sandbox

Sandbox group examples​

These patterns match the group-level examples on the Bicep quickstart. Apply them to the module "sandbox_group" block in main.tf.

Example 1 - Minimal sandbox group​

Omit the rich preview settings to use the service defaults:

module "sandbox_group" {
source = "git::https://github.com/Azure/terraform-provider-aca.git//modules/sandbox_groups?ref=debaefd1c08d8156a3291c48432dd4ca63e0fbed"

name = var.sandbox_group_name
resource_group_id = azurerm_resource_group.this.id
location = azurerm_resource_group.this.location
}

Example 2 - Resource defaults and tags​

Set CPU, memory, disk, maximum Sandbox count, timeout, and tags. These values apply when a data-plane client does not override them.

module "sandbox_group" {
# Keep the source, name, resource_group_id, and location arguments.

api_profile = "rich_preview"
default_cpu = "1"
default_memory = "2Gi"
default_disk = "20Gi"
max_sandbox_count = 50
default_timeout_seconds = 3600

tags = {
environment = "production"
team = "platform"
}
}

Example 3 - System-assigned managed identity​

Add this argument to the module:

identity = {
type = "SystemAssigned"
}

Example 4 - User-assigned managed identity​

Create an identity and attach it to the group:

resource "azurerm_user_assigned_identity" "sandbox" {
name = "aca-sandbox-identity"
resource_group_name = azurerm_resource_group.this.name
location = azurerm_resource_group.this.location
}

module "sandbox_group" {
# Keep the source, name, resource_group_id, location, and rich preview settings.

identity = {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.sandbox.id]
}
}

Example 5 - Sandbox group with a VNet connection​

Create a delegated subnet, then pass it to vnet_connections. One subnet maps to one sandbox group, and the subnet ID cannot be changed after deployment.

resource "azurerm_virtual_network" "sandbox" {
name = "aca-sandbox-vnet"
resource_group_name = azurerm_resource_group.this.name
location = azurerm_resource_group.this.location
address_space = ["10.0.0.0/16"]
}

resource "azurerm_subnet" "sandbox" {
name = "sandbox-subnet"
resource_group_name = azurerm_resource_group.this.name
virtual_network_name = azurerm_virtual_network.sandbox.name
address_prefixes = ["10.0.0.0/23"]

delegation {
name = "sandbox-delegation"
service_delegation {
name = "Microsoft.App/environments"
actions = ["Microsoft.Network/virtualNetworks/subnets/action"]
}
}

lifecycle {
ignore_changes = [delegation]
}
}

module "sandbox_group" {
# Keep the source, name, resource_group_id, location, and rich preview settings.

vnet_connections = {
default = {
subnet_id = azurerm_subnet.sandbox.id
}
}
}
note

The subnet ID is immutable once set. Size the subnet for peak sandbox concurrency before you deploy.

End-to-end example - Sandbox group and sandbox​

Create an empty directory and save the following configuration as main.tf. The Sandbox Group module is pinned to the same commit used to publish the provider release.

terraform {
required_version = ">= 1.11.0"

required_providers {
aca = {
source = "Azure/aca"
version = "= 0.5.0-preview"
}
azapi = {
source = "Azure/azapi"
version = ">= 2.12.0, < 3.0.0"
}
azurerm = {
source = "hashicorp/azurerm"
version = ">= 4.81.0, < 5.0.0"
}
}
}

provider "azurerm" {
subscription_id = var.subscription_id
features {}
}

provider "azapi" {
subscription_id = var.subscription_id
}

provider "aca" {
use_azure_cli = true
}

variable "subscription_id" {
description = "Azure subscription used by this quickstart."
type = string
}

variable "location" {
description = "Azure region that supports ACA Sandboxes."
type = string
default = "westus2"
}

variable "resource_group_name" {
description = "Resource group created by this quickstart."
type = string
default = "aca-sandbox-terraform"
}

variable "sandbox_group_name" {
description = "Sandbox group created by this quickstart."
type = string
default = "aca-sandbox-terraform"
}

variable "sandbox_name" {
description = "Logical Terraform name for the sandbox."
type = string
default = "ubuntu"
}

data "azurerm_client_config" "current" {}

resource "azurerm_resource_group" "this" {
name = var.resource_group_name
location = var.location
tags = {
environment = "dev"
managed_by = "terraform"
}
}

module "sandbox_group" {
source = "git::https://github.com/Azure/terraform-provider-aca.git//modules/sandbox_groups?ref=debaefd1c08d8156a3291c48432dd4ca63e0fbed"

name = var.sandbox_group_name
resource_group_id = azurerm_resource_group.this.id
location = azurerm_resource_group.this.location
api_profile = "rich_preview"

default_cpu = "1"
default_memory = "2Gi"
default_disk = "20Gi"
max_sandbox_count = 5
default_timeout_seconds = 3600

data_plane_operators = {
terraform_caller = {
principal_id = data.azurerm_client_config.current.object_id
principal_type = "User"
}
}

tags = {
environment = "dev"
managed_by = "terraform"
}
}

data "aca_sandbox_public_disk_image" "ubuntu" {
sandbox_group_id = module.sandbox_group.id
location = module.sandbox_group.location
name = "ubuntu"

depends_on = [module.sandbox_group]
}

resource "aca_sandbox" "ubuntu" {
sandbox_group_id = module.sandbox_group.id
location = module.sandbox_group.location
name = var.sandbox_name

source = {
public_disk_image = data.aca_sandbox_public_disk_image.ubuntu.name
}

resources = {
cpu = "1000m"
memory = "2048Mi"
disk = "20Gi"
}

entrypoint = ["/bin/sh", "-c"]
command = ["while true; do sleep 3600; done"]

auto_suspend = {
enabled = true
interval_seconds = 600
mode = "Memory"
}

egress_policy = {
default_action = "Deny"
}

labels = {
quickstart = "sandbox"
}

deletion_policy = "Delete"

timeouts = {
create = "20m"
delete = "10m"
}
}

output "sandbox_group_id" {
value = module.sandbox_group.id
}

output "sandbox_id" {
value = aca_sandbox.ubuntu.id
}

output "sandbox_state" {
value = aca_sandbox.ubuntu.state
}

output "sandbox_resource_url" {
value = aca_sandbox.ubuntu.resource_url
}

Install the preview provider​

The canonical installers are maintained in the ACA Terraform provider repository and pinned here to a specific commit. The installer detects the platform used by your Terraform executable, downloads the matching release archive and checksum manifest, verifies the SHA-256 hash, and writes terraform.rc. The provider is stored under .terraform-provider-mirror in the current directory.

INSTALLER_COMMIT="${ACA_TF_INSTALLER_COMMIT:-c4e104628fce8c975935a790c364a9ea9692c85c}"
INSTALLER_URL="https://raw.githubusercontent.com/Azure/terraform-provider-aca/${INSTALLER_COMMIT}/scripts/install-provider.sh"
TEMP_INSTALLER="$(mktemp)"
trap 'rm -f "$TEMP_INSTALLER"' EXIT

curl -fsSL "$INSTALLER_URL" -o "$TEMP_INSTALLER"
bash "$TEMP_INSTALLER"

Deploy the sandbox​

The configuration grants your signed-in user the Container Apps SandboxGroup Data Owner role. Role assignment propagation can take up to one minute; the provider retries initial data-plane reads during that interval.

if ! az account show --only-show-errors >/dev/null 2>&1; then
az login
fi

ACCOUNT_TYPE="$(az account show --query user.type -o tsv)"
if [[ "$(printf '%s' "$ACCOUNT_TYPE" | tr '[:upper:]' '[:lower:]')" != "user" ]]; then
echo "This quickstart requires Azure CLI authentication with a user account." >&2
exit 1
fi

SUBSCRIPTION_ID="${ACA_TF_SUBSCRIPTION_ID:-$(az account show --query id -o tsv)}"
az account set --subscription "$SUBSCRIPTION_ID"

export ARM_SUBSCRIPTION_ID="$SUBSCRIPTION_ID"
export TF_CLI_CONFIG_FILE="$PWD/terraform.rc"

terraform init
terraform apply -var="subscription_id=$ARM_SUBSCRIPTION_ID"
terraform output

After the apply finishes, sandbox_state should be Running. After 10 idle minutes, the service applies the configured memory-preserving auto-suspend policy.

Grant data-plane access​

Creating the group does not grant access to the regional Sandbox APIs. The complete configuration assigns the signed-in user the Container Apps SandboxGroup Data Owner role through the module:

data_plane_operators = {
terraform_caller = {
principal_id = data.azurerm_client_config.current.object_id
principal_type = "User"
}
}

This quickstart rejects non-user Azure CLI sessions so the role assignment is not created with the wrong principal type. For automation, replace terraform_caller with the service principal object ID and set principal_type = "ServicePrincipal".

Clean up​

The quickstart sets deletion_policy = "Delete", so Terraform deletes the data-plane sandbox before deleting the sandbox group and resource group.

SUBSCRIPTION_ID="${ACA_TF_SUBSCRIPTION_ID:-$(az account show --query id -o tsv)}"
az account set --subscription "$SUBSCRIPTION_ID"

export ARM_SUBSCRIPTION_ID="$SUBSCRIPTION_ID"
export TF_CLI_CONFIG_FILE="$PWD/terraform.rc"

terraform destroy -var="subscription_id=$ARM_SUBSCRIPTION_ID"

Learn more​

  • Sandbox groups - concepts behind Sandbox Groups.
  • Identity - how sandboxes use managed identities.
  • VNet - using VNet connections with Sandboxes.

Next steps​