> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tensor9.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Microsoft Azure

Microsoft Azure is a fully supported deployment platform for Tensor9 appliances. Deploying to Azure customer environments provides access to Microsoft's global cloud infrastructure with enterprise-grade security, compliance, and integration with customers' existing Azure resources.

<img src="https://mintcdn.com/tensor9/olcKQ_LnjOxL5Gwg/images/diagrams/azure-form-factor-overview-dark.png?fit=max&auto=format&n=olcKQ_LnjOxL5Gwg&q=85&s=4f3b676a58b1af13a84ac63d791a2482" className="block dark:hidden" width="2164" height="1026" data-path="images/diagrams/azure-form-factor-overview-dark.png" />

<img src="https://mintcdn.com/tensor9/olcKQ_LnjOxL5Gwg/images/diagrams/azure-form-factor-overview-light.png?fit=max&auto=format&n=olcKQ_LnjOxL5Gwg&q=85&s=62f2cc135e7da3bde11239813c51e00b" className="hidden dark:block" width="2182" height="1024" data-path="images/diagrams/azure-form-factor-overview-light.png" />

## Overview

When you deploy an application to Azure customer environments using Tensor9:

* **Customer appliances** run entirely within the customer's Azure subscription
* **Your control plane** orchestrates deployments from your dedicated Tensor9 AWS account
* **Managed identities and RBAC** enable your control plane to manage customer appliances with customer-approved permissions
* **Service equivalents** compile your origin stack into Azure-native resources

Azure appliances leverage Azure services for compute, storage, networking, and observability, providing enterprise-grade infrastructure that integrates seamlessly with your customers' existing Azure environments.

## Prerequisites

Before deploying appliances to Azure customer environments, ensure:

### Your control plane

* **Dedicated AWS account** for your Tensor9 control plane
* **Control plane installed** - See [Installing Tensor9](/fundamentals/install)
* **Origin stack published** - Your application infrastructure defined and uploaded

### Customer Azure subscription

Your customers must provide:

* **Azure subscription** where the appliance will be deployed
* **Managed identities configured** for the four-phase permissions model (Install, Steady-state, Deploy, Operate)
* **Virtual network and networking** configured according to their requirements
* **Sufficient subscription quotas** for your application's resource needs
* **Azure region** where they want the appliance deployed

### Your development environment

* **Azure CLI** installed and configured
* **kubectl** for Kubernetes operations
* **Terraform or OpenTofu** (if using Terraform origin stacks)
* **Docker** (if deploying container-based applications)

## How Azure appliances work

Azure appliances are deployed using Azure-native services orchestrated by your Tensor9 control plane.

<Steps>
  <Step title="Customer provisions managed identities">
    Your customer creates four managed identities in their Azure subscription, each corresponding to a permission phase: Install, Steady-state, Deploy, and Operate. These identities define what the Tensor9 controller can do within their environment.

    The customer configures RBAC role assignments that allow your control plane to impersonate these managed identities with appropriate conditions (time windows, approval tags, etc.).
  </Step>

  <Step title="You create a release for the customer appliance">
    You create a release targeting the customer's appliance:

    ```bash theme={null}
    tensor9 stack release create \
      -appName my-app \
      -customerName acme-corp \
      -vendorVersion "1.0.0" \
      -description "Initial production deployment"
    ```

    Your control plane compiles your origin stack into a deployment stack tailored for Azure, compiling any non-Azure resources to their Azure service equivalents. The deployment stack downloads to your local environment.
  </Step>

  <Step title="Customer grants deploy access">
    The customer approves the deployment by granting temporary deploy access. This can be manual (updating RBAC role assignments) or automated (scheduled maintenance windows).

    Once approved, the Tensor9 controller in the appliance can use the Deploy managed identity in the customer's subscription.
  </Step>

  <Step title="You deploy the release">
    You run the deployment locally against the downloaded deployment stack:

    ```bash theme={null}
    cd acme-corp-production
    tofu init
    tofu apply
    ```

    The deployment stack is configured to route resource creation through the Tensor9 controller inside the customer's appliance. The controller uses the Deploy managed identity and creates all infrastructure resources in the customer's Azure subscription:

    * Virtual networks, subnets, network security groups
    * AKS clusters, Container Instances, Azure Functions
    * Azure Database for PostgreSQL/MySQL, Azure Blob Storage, Azure Cache for Redis
    * Azure Monitor workspaces, Log Analytics, managed identities, Azure DNS zones
    * Any other Azure resources defined in your origin stack
  </Step>

  <Step title="Steady-state observability begins">
    After deployment, your control plane uses the Steady-state managed identity to continuously collect observability data (logs, metrics, traces) from the customer's appliance without requiring additional approvals.

    This data flows to your observability sink, giving you visibility into appliance health and performance.
  </Step>
</Steps>

## Service equivalents

When you deploy an origin stack to Azure customer environments, Tensor9 automatically compiles resources from other cloud providers to their Azure equivalents.

### How service equivalents work

When compiling a deployment stack for Azure:

1. **AWS resources are compiled** - AWS resources are converted to their Azure equivalents
2. **Generic resources are adapted** - Cloud-agnostic resources (like Kubernetes manifests) are adapted for Azure
3. **Configuration is adjusted** - Resource configurations are modified to match Azure conventions and best practices

### Common service equivalents

| Service Category  | AWS                         | Azure Equivalent                         |
| ----------------- | --------------------------- | ---------------------------------------- |
| **Compute**       | ECS Fargate                 | Azure Container Instances (ACI)          |
|                   | Lambda                      | Azure Functions                          |
|                   | EKS                         | AKS (Azure Kubernetes Service)           |
| **Storage**       | S3                          | Azure Blob Storage                       |
|                   | EBS                         | Azure Managed Disks                      |
| **Database**      | RDS PostgreSQL              | Azure Database for PostgreSQL            |
|                   | RDS Aurora MySQL, RDS MySQL | Azure Database for MySQL                 |
|                   | ElastiCache Redis           | Azure Cache for Redis                    |
| **Networking**    | VPC                         | Virtual Network (VNet)                   |
|                   | ALB/NLB/CLB                 | Azure Load Balancer, Application Gateway |
|                   | NAT Gateway                 | Azure NAT Gateway                        |
|                   | Route 53                    | Azure DNS                                |
| **Security**      | KMS                         | Azure Key Vault                          |
|                   | IAM Roles                   | Managed Identities                       |
| **Observability** | CloudWatch Logs             | Azure Monitor Logs                       |
|                   | CloudWatch Metrics          | Azure Monitor Metrics                    |
|                   | X-Ray                       | Application Insights                     |

<Note>
  Some popular AWS services (EC2, DynamoDB, EFS) are not currently supported. See [**Unsupported AWS services**](/fundamentals/service-equivalents#unsupported-aws-services) for the full list and recommended alternatives.
</Note>

### Example: Compiling an AWS origin stack

If your origin stack defines a Lambda function:

```terraform theme={null}
# Origin stack (AWS)
resource "aws_lambda_function" "api" {
  function_name = "myapp-api-${var.instance_id}"
  handler       = "index.handler"
  runtime       = "nodejs18.x"
  role          = aws_iam_role.api_role.arn

  environment {
    variables = {
      INSTANCE_ID = var.instance_id
    }
  }
}
```

Tensor9 compiles it to an Azure Function:

```terraform theme={null}
# Deployment stack (Azure)
resource "azurerm_linux_function_app" "api" {
  name                = "myapp-api-${var.instance_id}"
  location            = var.location
  resource_group_name = var.resource_group_name
  service_plan_id     = azurerm_service_plan.main.id

  site_config {
    application_stack {
      node_version = "18"
    }
  }

  app_settings = {
    INSTANCE_ID = var.instance_id
  }

  identity {
    type = "SystemAssigned"
  }
}
```

## Permissions model

Azure appliances use a four-phase managed identity permissions model that balances operational capability with customer control.

### The four permission phases

| Phase            | Managed Identity                     | Purpose                                         | Access Pattern                  |
| ---------------- | ------------------------------------ | ----------------------------------------------- | ------------------------------- |
| **Install**      | `tensor9-install-${instance_id}`     | Initial setup, major infrastructure changes     | Customer-approved, rare         |
| **Steady-state** | `tensor9-steadystate-${instance_id}` | Continuous observability collection (read-only) | Active by default               |
| **Deploy**       | `tensor9-deploy-${instance_id}`      | Deployments, updates, configuration changes     | Customer-approved, time-bounded |
| **Operate**      | `tensor9-operate-${instance_id}`     | Remote operations, troubleshooting, debugging   | Customer-approved, time-bounded |

### Managed identity structure

Each managed identity is created in the customer's Azure subscription with RBAC role assignments that allow your control plane to use it.

**Example: Deploy managed identity with conditional access**

```hcl theme={null}
# Deploy managed identity
resource "azurerm_user_assigned_identity" "deploy" {
  name                = "tensor9-deploy-${var.instance_id}"
  resource_group_name = var.resource_group_name
  location            = var.location

  tags = {
    instance-id = var.instance_id
    phase       = "deploy"
  }
}

# Grant Deploy identity permissions in customer subscription
resource "azurerm_role_assignment" "deploy_contributor" {
  scope                = data.azurerm_subscription.current.id
  role_definition_name = "Contributor"
  principal_id         = azurerm_user_assigned_identity.deploy.principal_id

  condition_version = "2.0"
  condition         = <<-EOT
    (
      @Resource[Microsoft.Resources/tags:instance-id] StringEquals '${var.instance_id}'
    )
  EOT
}

# Allow vendor control plane to use this identity
resource "azurerm_role_assignment" "deploy_identity_operator" {
  scope                = azurerm_user_assigned_identity.deploy.id
  role_definition_name = "Managed Identity Operator"
  principal_id         = var.vendor_control_plane_identity_id
}
```

Your control plane can only use the Deploy managed identity when:

* The customer has granted the Managed Identity Operator role
* Resources being created are tagged with the correct `instance-id`
* The time window hasn't expired (enforced via conditional access policies)

Customers control when and how long deploy access is granted.

**Example: Steady-state managed identity (read-only observability)**

```hcl theme={null}
# Steady-state managed identity
resource "azurerm_user_assigned_identity" "steadystate" {
  name                = "tensor9-steadystate-${var.instance_id}"
  resource_group_name = var.resource_group_name
  location            = var.location

  tags = {
    instance-id = var.instance_id
    phase       = "steadystate"
  }
}

# Grant read-only permissions scoped to appliance resources
resource "azurerm_role_assignment" "steadystate_monitoring_reader" {
  scope                = data.azurerm_subscription.current.id
  role_definition_name = "Monitoring Reader"
  principal_id         = azurerm_user_assigned_identity.steadystate.principal_id

  condition_version = "2.0"
  condition         = <<-EOT
    (
      @Resource[Microsoft.Resources/tags:instance-id] StringEquals '${var.instance_id}'
    )
  EOT
}

resource "azurerm_role_assignment" "steadystate_log_reader" {
  scope                = data.azurerm_subscription.current.id
  role_definition_name = "Log Analytics Reader"
  principal_id         = azurerm_user_assigned_identity.steadystate.principal_id

  condition_version = "2.0"
  condition         = <<-EOT
    (
      @Resource[Microsoft.Resources/tags:instance-id] StringEquals '${var.instance_id}'
    )
  EOT
}

# Allow vendor control plane to use this identity (no time restriction)
resource "azurerm_role_assignment" "steadystate_identity_operator" {
  scope                = azurerm_user_assigned_identity.steadystate.id
  role_definition_name = "Managed Identity Operator"
  principal_id         = var.vendor_control_plane_identity_id
}
```

The Steady-state managed identity:

* Can read observability data from resources tagged with the appliance's `instance-id`
* Cannot modify, delete, or terminate any resources
* Cannot change RBAC role assignments

### Deployment workflow with managed identities

<Steps>
  <Step title="Customer grants deploy access">
    Customer approves a deployment by granting the Managed Identity Operator role and setting up conditional access policies. This can be done manually or through automated approval workflows.
  </Step>

  <Step title="You execute deployment locally">
    You run the deployment locally against the downloaded deployment stack:

    ```bash theme={null}
    cd acme-corp-production
    tofu init
    tofu apply
    ```

    The deployment stack is configured to route resource creation through the Tensor9 controller in the appliance.
  </Step>

  <Step title="Controller uses Deploy identity and creates resources">
    For each resource Terraform attempts to create, the Tensor9 controller inside the appliance uses the Deploy managed identity and creates the resource in the customer's subscription.

    All infrastructure changes occur within the customer's subscription using their Deploy managed identity permissions.
  </Step>

  <Step title="Deploy access expires">
    After the time window expires or the role assignment is removed, the Deploy identity can no longer be used. Your control plane automatically reverts to using only the Steady-state identity for observability.
  </Step>
</Steps>

See [Permissions Model](/fundamentals/permissions-model) for detailed information on all four phases.

## Networking

Azure appliances use an isolated networking architecture with a Tensor9 controller that manages communication with your control plane.

### Tensor9 controller VNet

When an appliance is deployed, Tensor9 creates an isolated VNet containing the Tensor9 controller. This VNet is configured with:

* **Azure NAT Gateway**: Provides outbound internet connectivity
* **Route to control plane**: Establishes a secure channel to your Tensor9 control plane
* **No inbound NSG rules**: The controller VNet does not accept inbound connections - all communication is outbound-only

The Tensor9 controller uses this secure channel to:

* **Receive deployments**: Deployment stacks are pushed from your control plane to the appliance
* **Configure observability pipeline**: Set up log, metric, and trace forwarding to your observability sink
* **Receive operational commands**: Execute remote operations initiated from your control plane

### Outbound-only security model

The Tensor9 controller in your customer's appliance is designed to only make outbound connections and not require ingress ports to be opened in your customer's network perimeter:

```terraform theme={null}
# Example: Controller VNet configuration (managed by Tensor9)
resource "azurerm_virtual_network" "tensor9_controller" {
  name                = "tensor9-controller-${var.instance_id}"
  location            = var.location
  resource_group_name = var.resource_group_name
  address_space       = ["10.0.0.0/24"]

  tags = {
    instance-id = var.instance_id
    managed-by  = "tensor9"
  }
}

resource "azurerm_subnet" "controller" {
  name                 = "controller-subnet"
  resource_group_name  = var.resource_group_name
  virtual_network_name = azurerm_virtual_network.tensor9_controller.name
  address_prefixes     = ["10.0.0.0/26"]
}

# NAT Gateway for outbound connectivity
resource "azurerm_public_ip" "nat" {
  name                = "tensor9-nat-ip-${var.instance_id}"
  location            = var.location
  resource_group_name = var.resource_group_name
  allocation_method   = "Static"
  sku                 = "Standard"

  tags = {
    instance-id = var.instance_id
    managed-by  = "tensor9"
  }
}

resource "azurerm_nat_gateway" "controller" {
  name                = "tensor9-nat-${var.instance_id}"
  location            = var.location
  resource_group_name = var.resource_group_name
  sku_name            = "Standard"

  tags = {
    instance-id = var.instance_id
    managed-by  = "tensor9"
  }
}

resource "azurerm_nat_gateway_public_ip_association" "controller" {
  nat_gateway_id       = azurerm_nat_gateway.controller.id
  public_ip_address_id = azurerm_public_ip.nat.id
}

resource "azurerm_subnet_nat_gateway_association" "controller" {
  subnet_id      = azurerm_subnet.controller.id
  nat_gateway_id = azurerm_nat_gateway.controller.id
}

# NSG: egress only, no ingress
resource "azurerm_network_security_group" "controller" {
  name                = "tensor9-controller-nsg-${var.instance_id}"
  location            = var.location
  resource_group_name = var.resource_group_name

  # Allow outbound HTTPS
  security_rule {
    name                       = "AllowHTTPSOutbound"
    priority                   = 100
    direction                  = "Outbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "443"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

  # No inbound rules - controller never accepts inbound connections

  tags = {
    instance-id = var.instance_id
    managed-by  = "tensor9"
  }
}

resource "azurerm_subnet_network_security_group_association" "controller" {
  subnet_id                 = azurerm_subnet.controller.id
  network_security_group_id = azurerm_network_security_group.controller.id
}
```

This architecture ensures that the customer's appliance cannot be compromised via inbound network attacks on the controller.

### Application VNet topology

Your application resources run in their own VNet(s), completely separate from the Tensor9 controller VNet. The application VNet topology is defined entirely by your origin stack - whatever VPC resources you define in your origin stack will be compiled to Azure VNet resources in the appliance.

**Example: Application VNet with internet-facing load balancer**

If your origin stack defines an AWS VPC with public subnets and a load balancer, that topology will compile to Azure VNet resources in the customer's appliance:

```terraform theme={null}
# AWS origin stack - Application VPC
resource "aws_vpc" "application" {
  cidr_block           = "10.1.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name          = "myapp-vpc-${var.instance_id}"
    "instance-id" = var.instance_id
  }
}

# Public subnet for load balancer
resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.application.id
  cidr_block              = "10.1.0.0/24"
  availability_zone       = data.aws_availability_zones.available.names[0]
  map_public_ip_on_launch = true

  tags = {
    Name          = "myapp-public-${var.instance_id}"
    "instance-id" = var.instance_id
  }
}

# Private subnet for application servers
resource "aws_subnet" "private" {
  vpc_id            = aws_vpc.application.id
  cidr_block        = "10.1.1.0/24"
  availability_zone = data.aws_availability_zones.available.names[0]

  tags = {
    Name          = "myapp-private-${var.instance_id}"
    "instance-id" = var.instance_id
  }
}

# Application Load Balancer
resource "aws_lb" "application" {
  name               = "myapp-lb-${var.instance_id}"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.lb.id]
  subnets            = [aws_subnet.public.id]

  tags = {
    "instance-id" = var.instance_id
  }
}
```

This application VPC topology is deployed alongside the Tensor9 controller VNet, but they remain completely separate. The controller VNet manages the control plane connection, while the application VNet handles your application's traffic and resources.

## Resource naming and tagging

All Azure resources should use the `instance_id` variable to ensure uniqueness across multiple customer appliances.

### Parameterization pattern

```terraform theme={null}
variable "instance_id" {
  type        = string
  description = "Uniquely identifies the instance to deploy into"
}

# Storage accounts
resource "azurerm_storage_account" "data" {
  name                     = "myappdata${var.instance_id}"
  resource_group_name      = var.resource_group_name
  location                 = var.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

# Azure Database for PostgreSQL
resource "azurerm_postgresql_flexible_server" "main" {
  name                = "myapp-db-${var.instance_id}"
  resource_group_name = var.resource_group_name
  location            = var.location
  version             = "15"
  sku_name            = "B_Standard_B1ms"
}

# Azure Functions
resource "azurerm_linux_function_app" "api" {
  name                = "myapp-api-${var.instance_id}"
  resource_group_name = var.resource_group_name
  location            = var.location
  service_plan_id     = azurerm_service_plan.main.id
}

# Managed identities
resource "azurerm_user_assigned_identity" "app" {
  name                = "myapp-${var.instance_id}"
  resource_group_name = var.resource_group_name
  location            = var.location
}
```

### Required tags

Tag all resources with `instance-id` to enable permissions scoping and observability:

```terraform theme={null}
resource "azurerm_storage_account" "data" {
  name                     = "myappdata${var.instance_id}"
  resource_group_name      = var.resource_group_name
  location                 = var.location
  account_tier             = "Standard"
  account_replication_type = "LRS"

  tags = {
    instance-id = var.instance_id
    application = "my-app"
    managed-by  = "tensor9"
  }
}

resource "azurerm_kubernetes_cluster" "main" {
  name                = "myapp-aks-${var.instance_id}"
  location            = var.location
  resource_group_name = var.resource_group_name
  dns_prefix          = "myapp-${var.instance_id}"

  default_node_pool {
    name       = "default"
    node_count = 2
    vm_size    = "Standard_D2_v2"
  }

  tags = {
    instance-id = var.instance_id
    application = "my-app"
    managed-by  = "tensor9"
  }
}
```

The `instance-id` tag:

* Enables RBAC condition expressions to scope permissions to specific appliances
* Allows Azure Monitor filters to isolate telemetry by appliance
* Helps customers track costs per appliance
* Facilitates resource discovery by Tensor9 controllers

## Observability

Azure appliances provide comprehensive observability through Azure Monitor, Log Analytics, and Application Insights.

### Azure Monitor Logs

Application and infrastructure logs flow to Log Analytics workspaces:

```terraform theme={null}
# Log Analytics workspace
resource "azurerm_log_analytics_workspace" "main" {
  name                = "myapp-logs-${var.instance_id}"
  location            = var.location
  resource_group_name = var.resource_group_name
  sku                 = "PerGB2018"
  retention_in_days   = 30

  tags = {
    instance-id = var.instance_id
  }
}

# Enable diagnostics for AKS
resource "azurerm_monitor_diagnostic_setting" "aks" {
  name                       = "aks-diagnostics"
  target_resource_id         = azurerm_kubernetes_cluster.main.id
  log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id

  enabled_log {
    category = "kube-apiserver"
  }

  enabled_log {
    category = "kube-controller-manager"
  }

  enabled_log {
    category = "kube-scheduler"
  }

  metric {
    category = "AllMetrics"
    enabled  = true
  }
}
```

Your control plane uses the Steady-state managed identity to continuously fetch logs:

```bash theme={null}
az monitor log-analytics query \
  --workspace customer-workspace-id \
  --analytics-query "AzureDiagnostics | where tags_s contains 'instance-id=${var.instance_id}' | take 100" \
  --identity tensor9-steadystate-000000007e
```

Logs are forwarded to your observability sink for centralized monitoring.

### Azure Monitor Metrics

Infrastructure metrics are automatically collected:

* **Virtual Machines**: CPU percentage, network in/out, disk operations
* **Azure Database for PostgreSQL**: Database connections, CPU percent, storage used
* **Azure Functions**: Execution count, execution units, errors
* **AKS**: Node CPU/memory, pod counts, API server metrics
* **Azure Load Balancer**: Data path availability, health probe status, packet count

### Application Insights

Enable distributed tracing for Azure Functions and containerized applications:

```terraform theme={null}
resource "azurerm_application_insights" "main" {
  name                = "myapp-insights-${var.instance_id}"
  location            = var.location
  resource_group_name = var.resource_group_name
  application_type    = "web"
  workspace_id        = azurerm_log_analytics_workspace.main.id

  tags = {
    instance-id = var.instance_id
  }
}

resource "azurerm_linux_function_app" "api" {
  name                = "myapp-api-${var.instance_id}"
  location            = var.location
  resource_group_name = var.resource_group_name
  service_plan_id     = azurerm_service_plan.main.id

  app_settings = {
    APPINSIGHTS_INSTRUMENTATIONKEY             = azurerm_application_insights.main.instrumentation_key
    APPLICATIONINSIGHTS_CONNECTION_STRING      = azurerm_application_insights.main.connection_string
    ApplicationInsightsAgent_EXTENSION_VERSION = "~3"
    INSTANCE_ID                                = var.instance_id
  }
}
```

Application Insights traces are accessible through the Steady-state identity and forwarded to your observability sink.

### Azure Activity Log

All API calls within the customer's Azure subscription are logged to Activity Log, providing a complete audit trail of what your control plane does:

* Managed identity usage
* Resource creation, modification, deletion
* Permission denials
* Role assignment changes

Customers have full visibility into your control plane's actions through their Activity Log.

## Artifacts

Azure appliances automatically provision private artifact repositories to store container images and application files deployed by your deployment stacks.

### Container images (Azure Container Registry)

When you deploy an appliance, Tensor9 automatically provisions a private Azure Container Registry in the customer's Azure subscription to store your container images.

**Example: Origin stack with container service**

Your AWS origin stack references container images from your vendor's Amazon ECR:

```terraform theme={null}
# ECS Fargate service in your origin stack
resource "aws_ecs_task_definition" "api" {
  family                   = "myapp-api-${var.instance_id}"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = "256"
  memory                   = "512"

  container_definitions = jsonencode([
    {
      name  = "api"
      # Reference to your vendor ECR registry
      image = "123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp/api:1.0.0"
      portMappings = [
        {
          containerPort = 8080
          protocol      = "tcp"
        }
      ]
      environment = [
        {
          name  = "INSTANCE_ID"
          value = var.instance_id
        }
      ]
    }
  ])

  tags = {
    "instance-id" = var.instance_id
  }
}
```

**Container copy during deployment**

When you deploy the deployment stack, Tensor9 automatically:

1. **Detects the container image reference** in your ECS task definition
2. **Provisions a private Azure Container Registry** in the appliance (e.g., `myappacr000000007e.azurecr.io`)
3. **Copies the container image** from your vendor ECR registry to the appliance's private ACR
4. **Rewrites the deployment stack** to reference the appliance-local registry

The compiled deployment stack will contain an Azure Container Instances or AKS deployment with the rewritten image reference:

```terraform theme={null}
# Azure Container Instances
image = "myappacr000000007e.azurecr.io/api:1.0.0"
```

This ensures the container image is stored locally in the customer's subscription and the application doesn't depend on cross-subscription access to your vendor registry.

**Artifact lifecycle**

Container artifacts are tied to the deployment stack lifecycle:

* **Deploy (tofu apply)**: Tensor9 copies the container image from your vendor registry to the appliance's private registry
* **Destroy (tofu destroy)**: Deleting the deployment stack also deletes the copied container artifact from the appliance's private registry

This ensures that artifacts are cleaned up when deployments are removed, preventing orphaned resources.

### Function source code

For Lambda functions in your AWS origin stack, Tensor9 automatically handles copying function source code to the customer's Azure environment:

```terraform theme={null}
# Lambda function in your AWS origin stack
resource "aws_lambda_function" "processor" {
  function_name = "myapp-processor-${var.instance_id}"
  handler       = "processor.process_event"
  runtime       = "python3.11"
  role          = aws_iam_role.processor.arn

  # Reference to function code in your vendor S3 bucket
  s3_bucket = "vendor-lambda-sources"
  s3_key    = "processor-v1.0.0.zip"

  environment {
    variables = {
      INSTANCE_ID = var.instance_id
    }
  }

  tags = {
    "instance-id" = var.instance_id
  }
}
```

During deployment, Tensor9:

1. Provisions a private Azure Storage Account in the appliance for function sources
2. Copies the Lambda source archive from your vendor S3 bucket to the appliance's Storage Account
3. Compiles the Lambda function to an Azure Function with the appliance-local source reference

Like container images, destroying the deployment stack (tofu destroy) removes the copied function source archives.

See [Artifacts](/fundamentals/artifacts) for comprehensive documentation on artifact management, including immutability requirements and supported artifact types.

## Secrets management

Store secrets in AWS Secrets Manager or AWS Systems Manager Parameter Store in your AWS origin stack, then pass them to your application as environment variables.

### Secret naming and injection

Always use parameterized secret names and inject them as environment variables:

```terraform theme={null}
# AWS Secrets Manager secret
resource "aws_secretsmanager_secret" "db_password" {
  name = "${var.instance_id}/prod/db/password"

  tags = {
    "instance-id" = var.instance_id
  }
}

resource "aws_secretsmanager_secret_version" "db_password" {
  secret_id     = aws_secretsmanager_secret.db_password.id
  secret_string = var.db_password
}

# ECS Fargate task - inject secret as environment variable
resource "aws_ecs_task_definition" "app" {
  family = "myapp-${var.instance_id}"

  container_definitions = jsonencode([
    {
      name  = "app"
      image = "myapp:latest"

      # Inject secret as environment variable
      secrets = [
        {
          name      = "DB_PASSWORD"
          valueFrom = aws_secretsmanager_secret.db_password.arn
        }
      ]
    }
  ])

  tags = {
    "instance-id" = var.instance_id
  }
}
```

Your application reads secrets from environment variables:

```python theme={null}
import os

# Read secret from environment variable
db_password = os.environ['DB_PASSWORD']
```

<Note>
  If your application dynamically fetches secrets using AWS SDK calls (e.g., `boto3.client('secretsmanager').get_secret_value()`), those calls will NOT be automatically mapped by Tensor9. Always pass secrets as environment variables.
</Note>

See [Secrets](/fundamentals/secrets) for detailed secret management patterns.

## Operations

Perform remote operations on Azure appliances using the Operate managed identity.

### kubectl on AKS

Execute kubectl commands against AKS clusters:

```bash theme={null}
tensor9 ops kubectl \
  -appName my-app \
  -customerName acme-corp \
  -originResourceId "azurerm_kubernetes_cluster.main" \
  -command "kubectl get pods -n my-app-namespace"
```

Output:

```
NAME                     READY   STATUS    RESTARTS   AGE
api-7d9f8b5c6d-9k2lm    1/1     Running   0          2h
worker-5c8d7b4f3-8h4km  1/1     Running   0          2h
```

### Azure CLI operations

Execute Azure CLI commands:

```bash theme={null}
# List storage container contents
tensor9 ops az \
  -appName my-app \
  -customerName acme-corp \
  -originResourceId "azurerm_storage_account.data" \
  -command "az storage blob list --account-name myappdata000000007e --container-name files"

# Invoke Azure Function
tensor9 ops az \
  -appName my-app \
  -customerName acme-corp \
  -originResourceId "azurerm_linux_function_app.api" \
  -command "az functionapp function invoke --name myapp-api-000000007e --function-name processor"

# View PostgreSQL status
tensor9 ops az \
  -appName my-app \
  -customerName acme-corp \
  -originResourceId "azurerm_postgresql_flexible_server.main" \
  -command "az postgres flexible-server show --name myapp-db-000000007e --resource-group myapp-rg"
```

### Database queries

Execute SQL queries against Azure Database for PostgreSQL:

```bash theme={null}
tensor9 ops db \
  -appName my-app \
  -customerName acme-corp \
  -originResourceId "azurerm_postgresql_flexible_server.main" \
  -command "SELECT count(*) FROM users WHERE created_at > NOW() - INTERVAL '24 hours'"
```

### Operations endpoints

Create temporary operations endpoints for interactive access:

```bash theme={null}
# Create kubectl endpoint
tensor9 ops endpoint create \
  -appName my-app \
  -customerName acme-corp \
  -originResourceId "azurerm_kubernetes_cluster.main" \
  -endpointType kubectl \
  -ttl 3600

# Output:
# Endpoint created: https://ops.tensor9.io/kubectl/abc123
# Expires in: 1 hour
# Use: kubectl --server=https://ops.tensor9.io/kubectl/abc123 get pods
```

See [Operations](/fundamentals/operations) for comprehensive operations documentation.

## Example: Complete Azure appliance

Here's a complete example of a deployment stack for an Azure appliance, compiled from an AWS origin stack:

### main.tf

```terraform theme={null}
# Virtual Network
resource "azurerm_virtual_network" "main" {
  name                = "myapp-vnet-${var.instance_id}"
  location            = var.location
  resource_group_name = var.resource_group_name
  address_space       = ["10.0.0.0/16"]

  tags = {
    instance-id = var.instance_id
  }
}

# Subnets
resource "azurerm_subnet" "private" {
  name                 = "private-subnet"
  resource_group_name  = var.resource_group_name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = ["10.0.1.0/24"]
}

# AKS Cluster
resource "azurerm_kubernetes_cluster" "main" {
  name                = "myapp-aks-${var.instance_id}"
  location            = var.location
  resource_group_name = var.resource_group_name
  dns_prefix          = "myapp-${var.instance_id}"
  kubernetes_version  = "1.28.3"

  default_node_pool {
    name       = "default"
    node_count = 2
    vm_size    = "Standard_D2_v2"
    vnet_subnet_id = azurerm_subnet.private.id
  }

  identity {
    type = "SystemAssigned"
  }

  oms_agent {
    log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
  }

  tags = {
    instance-id = var.instance_id
  }
}

# Azure Database for PostgreSQL
resource "azurerm_postgresql_flexible_server" "main" {
  name                = "myapp-db-${var.instance_id}"
  resource_group_name = var.resource_group_name
  location            = var.location
  version             = "15"
  sku_name            = "B_Standard_B1ms"
  storage_mb          = 32768

  administrator_login    = "adminuser"
  administrator_password = var.db_password

  zone = "1"

  tags = {
    instance-id = var.instance_id
  }
}

resource "azurerm_postgresql_flexible_server_database" "main" {
  name      = "myapp"
  server_id = azurerm_postgresql_flexible_server.main.id
  charset   = "UTF8"
  collation = "en_US.utf8"
}

# Storage Account
resource "azurerm_storage_account" "data" {
  name                     = "myappdata${var.instance_id}"
  resource_group_name      = var.resource_group_name
  location                 = var.location
  account_tier             = "Standard"
  account_replication_type = "LRS"

  blob_properties {
    versioning_enabled = true
  }

  tags = {
    instance-id = var.instance_id
  }
}

resource "azurerm_storage_container" "data" {
  name                  = "application-data"
  storage_account_name  = azurerm_storage_account.data.name
  container_access_type = "private"
}

# Azure Cache for Redis
resource "azurerm_redis_cache" "main" {
  name                = "myapp-redis-${var.instance_id}"
  location            = var.location
  resource_group_name = var.resource_group_name
  capacity            = 0
  family              = "C"
  sku_name            = "Basic"
  redis_version       = "6"

  tags = {
    instance-id = var.instance_id
  }
}

# Log Analytics Workspace
resource "azurerm_log_analytics_workspace" "main" {
  name                = "myapp-logs-${var.instance_id}"
  location            = var.location
  resource_group_name = var.resource_group_name
  sku                 = "PerGB2018"
  retention_in_days   = 30

  tags = {
    instance-id = var.instance_id
  }
}

# Application Insights
resource "azurerm_application_insights" "main" {
  name                = "myapp-insights-${var.instance_id}"
  location            = var.location
  resource_group_name = var.resource_group_name
  workspace_id        = azurerm_log_analytics_workspace.main.id
  application_type    = "web"

  tags = {
    instance-id = var.instance_id
  }
}
```

### variables.tf

```terraform theme={null}
variable "instance_id" {
  type        = string
  description = "Uniquely identifies the instance to deploy into"
}

variable "resource_group_name" {
  type        = string
  description = "Azure resource group name"
}

variable "location" {
  type        = string
  description = "Azure region"
  default     = "eastus"
}

variable "db_password" {
  type        = string
  description = "Database administrator password"
  sensitive   = true
}
```

### outputs.tf

```terraform theme={null}
output "aks_cluster_endpoint" {
  description = "AKS cluster endpoint"
  value       = azurerm_kubernetes_cluster.main.kube_config[0].host
  sensitive   = true
}

output "database_fqdn" {
  description = "PostgreSQL database FQDN"
  value       = azurerm_postgresql_flexible_server.main.fqdn
  sensitive   = true
}

output "redis_hostname" {
  description = "Redis cache hostname"
  value       = azurerm_redis_cache.main.hostname
}

output "storage_account_name" {
  description = "Storage account name"
  value       = azurerm_storage_account.data.name
}

output "application_insights_key" {
  description = "Application Insights instrumentation key"
  value       = azurerm_application_insights.main.instrumentation_key
  sensitive   = true
}
```

## Best practices

<AccordionGroup>
  <Accordion title="Use instance_id for all resource names">
    Every Azure resource with a name should include `${var.instance_id}` to prevent conflicts across customer appliances:

    ```terraform theme={null}
    # ✓ CORRECT
    resource "azurerm_storage_account" "data" {
      name = "myappdata${var.instance_id}"
    }

    resource "azurerm_kubernetes_cluster" "main" {
      name = "myapp-aks-${var.instance_id}"
    }

    # ✗ INCORRECT - Will cause collisions
    resource "azurerm_storage_account" "data" {
      name = "myappdata"
    }
    ```

    Note: Storage account names must be globally unique and can only contain lowercase letters and numbers (no hyphens).
  </Accordion>

  <Accordion title="Tag all resources with instance-id">
    Apply the `instance-id` tag to every resource:

    ```terraform theme={null}
    tags = {
      instance-id = var.instance_id
      application = "my-app"
      managed-by  = "tensor9"
    }
    ```

    This enables:

    * RBAC condition expressions for permission scoping
    * Azure Monitor filtering
    * Cost tracking
    * Resource discovery
  </Accordion>

  <Accordion title="Enable Azure Monitor diagnostics for all services">
    Configure diagnostics for AKS, Azure Functions, databases, and other services:

    ```terraform theme={null}
    resource "azurerm_monitor_diagnostic_setting" "aks" {
      name                       = "aks-diagnostics"
      target_resource_id         = azurerm_kubernetes_cluster.main.id
      log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id

      enabled_log {
        category = "kube-apiserver"
      }

      metric {
        category = "AllMetrics"
        enabled  = true
      }
    }
    ```

    This ensures observability data flows to your control plane.
  </Accordion>

  <Accordion title="Use AWS Secrets Manager for sensitive data">
    Never hardcode secrets. Use AWS Secrets Manager or SSM Parameter Store with parameterized names in your AWS origin stack:

    ```terraform theme={null}
    # AWS Secrets Manager (recommended)
    resource "aws_secretsmanager_secret" "api_key" {
      name = "${var.instance_id}/prod/api/key"

      tags = {
        "instance-id" = var.instance_id
      }
    }

    # Or AWS Systems Manager Parameter Store
    resource "aws_ssm_parameter" "db_password" {
      name  = "/${var.instance_id}/prod/db/password"
      type  = "SecureString"
      value = var.db_password

      tags = {
        "instance-id" = var.instance_id
      }
    }
    ```

    Pass secrets to your application as environment variables. Runtime SDK calls to fetch secrets are not automatically mapped by Tensor9.
  </Accordion>
</AccordionGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Deployment fails with permission errors">
    **Symptom**: Terraform apply fails with "AuthorizationFailed" or "Forbidden" errors.

    **Solutions**:

    * Verify the Tensor9 controller has successfully authenticated with the Deploy managed identity
    * Check the Deploy identity's role assignments include necessary permissions for the resources being created
    * Ensure the RBAC conditional access policies allow the operation
    * Verify resources are tagged with the correct `instance-id`
    * Review Azure Activity Log in the customer subscription to see which specific API call was denied
  </Accordion>

  <Accordion title="Resources fail to create due to naming conflicts">
    **Symptom**: "ResourceExists" or "NameNotAvailable" errors during deployment.

    **Solutions**:

    * Ensure all resource names include `${var.instance_id}`
    * Verify the `instance_id` variable is being passed correctly
    * Check that no hardcoded resource names exist in your origin stack
    * For storage accounts, remember names must be globally unique and only contain lowercase letters and numbers
    * For storage accounts, ensure the name is between 3-24 characters
  </Accordion>

  <Accordion title="Observability data not flowing to control plane">
    **Symptom**: Azure Monitor logs and metrics aren't appearing in your observability sink.

    **Solutions**:

    * Verify the Steady-state identity has Monitoring Reader and Log Analytics Reader permissions
    * Check that all resources are tagged with `instance-id`
    * Ensure diagnostic settings are configured for all resources
    * Verify Log Analytics workspace retention is set appropriately
    * Check that the control plane is successfully using the Steady-state identity
  </Accordion>

  <Accordion title="Subscription quota limits exceeded">
    **Symptom**: "QuotaExceeded" or "OperationNotAllowed" errors when creating resources.

    **Solutions**:

    * Ask the customer to request quota increases from Azure Portal
    * Consider deploying appliances in separate Azure regions
    * Review and clean up unused resources in the customer's subscription
    * For virtual machine quotas, consider using different VM sizes
  </Accordion>

  <Accordion title="AKS cluster creation fails">
    **Symptom**: Kubernetes cluster creation times out or fails.

    **Solutions**:

    * Verify the region supports AKS
    * Check that the Kubernetes version is supported in the region
    * Ensure the VM SKU is available in the region
    * Verify VNet and subnet configuration is correct
    * Check that service principal or managed identity has necessary permissions
    * Review Azure Service Health for service incidents
  </Accordion>

  <Accordion title="Storage account name validation errors">
    **Symptom**: "StorageAccountNameInvalid" errors.

    **Solutions**:

    * Ensure storage account names only contain lowercase letters and numbers (no hyphens)
    * Verify the name is between 3-24 characters
    * Check that `${var.instance_id}` doesn't contain invalid characters
    * Consider shortening the app name prefix if the full name is too long
  </Accordion>

  <Accordion title="Need help?">
    If you're experiencing issues not covered here or need additional assistance with Azure deployments, we're here to help:

    * **Slack**: Join our community Slack workspace for real-time support
    * **Email**: Contact us at [support@tensor9.com](mailto:support@tensor9.com)

    Our team can help with deployment troubleshooting, managed identity configuration, service equivalents, and best practices for Azure environments.
  </Accordion>
</AccordionGroup>

## Next steps

Now that you understand deploying to Azure customer environments, explore these related topics:

* [**Permissions Model**](/fundamentals/permissions-model): Deep dive into the four-phase permissions model
* [**Deployments**](/fundamentals/deployments): Learn how to create releases and deploy to customer appliances
* [**Operations**](/fundamentals/operations): Execute remote operations on Azure appliances
* [**Observability**](/fundamentals/observability): Set up comprehensive monitoring and logging
* [**Terraform Origin Stacks**](/origin-stack/terraform): Write Terraform origin stacks optimized for Azure
