TerraformPilot

DevOps

Terraform for XR Computing and Spatial Platforms on AWS

Provision XR and spatial computing backends with Terraform: 3D content pipelines, streaming infrastructure, device management, and analytics.

LLuca Berton1 min read

Advanced displays and XR computing — spatial headsets, AR glasses, light-field displays — are reaching real consumer scale in 2026. The infrastructure behind them is unglamorous but unforgiving: 3D asset pipelines, low-latency streaming, device fleet management, and analytics. Terraform stitches the cloud half together so XR teams can focus on rendering.

This guide shows how to provision an XR / spatial computing backend on AWS.

Architecture

#
LayerAWS service
3D asset pipelineS3 + Lambda + Batch (compression / LOD)
Cloud renderingEC2 G6 / G6e + NICE DCV
StreamingCloudFront + MediaPackage
Device managementIoT Core + IoT Device Management
Spatial dataLocation Service + DynamoDB
AnalyticsKinesis Firehose → S3 + Athena

Asset Pipeline

#
resource "aws_s3_bucket" "assets_raw" {
  bucket = "acme-xr-raw"
}
 
resource "aws_s3_bucket" "assets_processed" {
  bucket = "acme-xr-processed"
}
 
resource "aws_s3_bucket_notification" "raw" {
  bucket = aws_s3_bucket.assets_raw.id
 
  lambda_function {
    lambda_function_arn = aws_lambda_function.dispatcher.arn
    events              = ["s3:ObjectCreated:*"]
    filter_suffix       = ".usdz"
  }
  lambda_function {
    lambda_function_arn = aws_lambda_function.dispatcher.arn
    events              = ["s3:ObjectCreated:*"]
    filter_suffix       = ".glb"
  }
}
 
resource "aws_batch_job_definition" "lod_generator" {
  name = "xr-lod-generator"
  type = "container"
 
  container_properties = jsonencode({
    image      = "${aws_ecr_repository.lod.repository_url}:latest"
    vcpus      = 4
    memory     = 16384
    jobRoleArn = aws_iam_role.lod_job.arn
  })
}

Cloud Rendering Hosts

#
resource "aws_launch_template" "render_host" {
  name_prefix   = "xr-render-"
  image_id      = data.aws_ami.dcv.id
  instance_type = "g6e.4xlarge"
 
  iam_instance_profile {
    arn = aws_iam_instance_profile.render.arn
  }
 
  block_device_mappings {
    device_name = "/dev/sda1"
    ebs {
      volume_size = 200
      volume_type = "gp3"
      iops        = 6000
      throughput  = 500
      encrypted   = true
    }
  }
 
  user_data = base64encode(templatefile("${path.module}/scripts/render-host.sh", {
    asset_bucket = aws_s3_bucket.assets_processed.bucket
  }))
 
  tag_specifications {
    resource_type = "instance"
    tags = { Component = "xr-render" }
  }
}
 
resource "aws_autoscaling_group" "render" {
  name             = "xr-render"
  min_size         = 0
  desired_capacity = 4
  max_size         = 50
  vpc_zone_identifier = var.private_subnet_ids
 
  mixed_instances_policy {
    launch_template {
      launch_template_specification {
        launch_template_id = aws_launch_template.render_host.id
        version            = "$Latest"
      }
    }
    instances_distribution {
      on_demand_base_capacity                  = 2
      on_demand_percentage_above_base_capacity = 0
      spot_allocation_strategy                 = "capacity-optimized"
    }
  }
}

Global Streaming Edge

#
resource "aws_cloudfront_distribution" "xr" {
  enabled             = true
  is_ipv6_enabled     = true
  http_version        = "http2and3"
  price_class         = "PriceClass_All"
  default_root_object = "manifest.mpd"
 
  origin {
    domain_name              = aws_s3_bucket.assets_processed.bucket_regional_domain_name
    origin_id                = "assets"
    origin_access_control_id = aws_cloudfront_origin_access_control.assets.id
  }
 
  default_cache_behavior {
    target_origin_id       = "assets"
    viewer_protocol_policy = "redirect-to-https"
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]
    cache_policy_id        = data.aws_cloudfront_cache_policy.optimized.id
    compress               = true
  }
 
  restrictions { geo_restriction { restriction_type = "none" } }
  viewer_certificate { cloudfront_default_certificate = true }
}

Device Management

#
resource "aws_iot_thing_type" "headset" {
  name = "xr-headset-v3"
  thing_type_properties {
    description           = "Generation-3 spatial headset"
    searchable_attributes = ["fw_version", "model", "tenant"]
  }
}
 
resource "aws_iot_thing_group" "tenant" {
  for_each = var.tenants
  name     = "tenant-${each.key}"
}

Spatial Telemetry → Athena

#
resource "aws_kinesis_firehose_delivery_stream" "spatial_events" {
  name        = "xr-events"
  destination = "extended_s3"
 
  extended_s3_configuration {
    role_arn   = aws_iam_role.firehose.arn
    bucket_arn = aws_s3_bucket.events.arn
    prefix     = "events/y=!{timestamp:yyyy}/m=!{timestamp:MM}/d=!{timestamp:dd}/"
    error_output_prefix = "errors/"
    buffering_interval  = 60
    compression_format  = "GZIP"
 
    data_format_conversion_configuration {
      enabled = true
      input_format_configuration {
        deserializer { open_x_json_ser_de {} }
      }
      output_format_configuration {
        serializer { parquet_ser_de {} }
      }
      schema_configuration {
        role_arn      = aws_iam_role.firehose.arn
        database_name = aws_glue_catalog_database.xr.name
        table_name    = aws_glue_catalog_table.events.name
      }
    }
  }
}

Best Practices

#
  • Pre-bake LOD chains in S3 — generating LODs at request time spikes p99 latency.
  • CloudFront HTTP/3 for headsets on cellular — fewer reconnect storms.
  • Separate render fleets per tenant when license terms restrict asset commingling.
  • Convert telemetry to Parquet at ingest — XR events are huge; Athena scans become 10× cheaper.
#
#Terraform#XR#AWS#Streaming#Spatial Computing

Share this article