Terraform for Windows Server on AWS EC2: Complete Guide
Provision Windows Server 2025 on AWS EC2 with Terraform. Includes AMI selection, password retrieval, RDP, IIS, and joining Active Directory.
DevOps
Deploy OpenClaw AI on AWS EC2 with Terraform: Ubuntu 24.04, gp3 EBS for persistent agent data, SSH key pair, security group, and user-data bootstrap.
OpenClaw AI is a personal AI agent that works best when it has persistent storage for sessions, models, and tool state. Running it on a fresh laptop loses that context every reboot. The fix: a small EC2 instance backed by an EBS volume, fully provisioned by Terraform.
This guide shows the complete pattern — VPC defaults, key pair, security group, Ubuntu 24.04 instance, gp3 EBS volume mounted at /opt/openclaw_data, and a user-data script that installs OpenClaw on first boot.
| Component | Resource |
|---|---|
| Compute | aws_instance (t3.medium, Ubuntu 24.04) |
| Persistent storage | aws_ebs_volume (8 GB, gp3) |
| Attachment | aws_volume_attachment |
| Access | aws_key_pair (RSA 4096) + aws_security_group |
| Bootstrap | EC2 user data script |
terraform {
required_version = ">= 1.6"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.70" }
tls = { source = "hashicorp/tls", version = "~> 4.0" }
}
}
provider "aws" {
region = "eu-central-1"
}resource "tls_private_key" "openclaw" {
algorithm = "RSA"
rsa_bits = 4096
}
resource "aws_key_pair" "openclaw" {
key_name = "openclaw-key"
public_key = tls_private_key.openclaw.public_key_openssh
}
resource "local_sensitive_file" "private_key" {
content = tls_private_key.openclaw.private_key_pem
filename = "${path.module}/openclaw-key.pem"
file_permission = "0400"
}resource "aws_security_group" "openclaw" {
name = "openclaw-sg"
description = "OpenClaw EC2 access"
ingress {
description = "SSH from admin IP"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.admin_cidr] # e.g. "203.0.113.10/32"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = { Name = "openclaw-sg" }
}Lock the ingress to your public IP — never 0.0.0.0/0.
data "aws_ami" "ubuntu_2404" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
}
}resource "aws_instance" "openclaw" {
ami = data.aws_ami.ubuntu_2404.id
instance_type = "t3.medium"
key_name = aws_key_pair.openclaw.key_name
vpc_security_group_ids = [aws_security_group.openclaw.id]
root_block_device {
volume_size = 20
volume_type = "gp3"
encrypted = true
}
user_data = <<-EOT
#!/bin/bash
set -eux
# Wait for the EBS volume to attach
while [ ! -e /dev/nvme1n1 ]; do sleep 1; done
# Format and mount /opt/openclaw_data
if ! blkid /dev/nvme1n1; then
mkfs -t xfs /dev/nvme1n1
fi
mkdir -p /opt/openclaw_data
mount /dev/nvme1n1 /opt/openclaw_data
echo '/dev/nvme1n1 /opt/openclaw_data xfs defaults,nofail 0 2' >> /etc/fstab
chown -R ubuntu:ubuntu /opt/openclaw_data
# Install OpenClaw
apt-get update -y
apt-get install -y curl git xfsprogs
curl -fsSL https://openclaw.ai/install.sh -o /tmp/install.sh
bash /tmp/install.sh
# Persist OPENCLAW_HOME for the ubuntu user
cat <<'PROFILE' >> /home/ubuntu/.bashrc
export OPENCLAW_HOME=/opt/openclaw_data
export PATH="$HOME/.npm-global/bin:$PATH"
PROFILE
chown ubuntu:ubuntu /home/ubuntu/.bashrc
EOT
tags = { Name = "openclaw-ai" }
}resource "aws_ebs_volume" "openclaw_data" {
availability_zone = aws_instance.openclaw.availability_zone
size = 8
type = "gp3"
encrypted = true
tags = { Name = "openclaw-data" }
}
resource "aws_volume_attachment" "openclaw_data" {
device_name = "/dev/sdf" # surfaces as /dev/nvme1n1 on Nitro
volume_id = aws_ebs_volume.openclaw_data.id
instance_id = aws_instance.openclaw.id
}The device_name you set (/dev/sdf) surfaces on Nitro instances as /dev/nvme1n1, which is what the user-data script formats and mounts.
output "ssh_command" {
value = "ssh -i openclaw-key.pem ubuntu@${aws_instance.openclaw.public_ip}"
}ssh -i openclaw-key.pem ubuntu@<EC2_PUBLIC_IP>
openclaw --version
# OpenClaw 2026.3.13 (61d171a)
df -h /opt/openclaw_data
# /dev/nvme1n1 8.0G ... /opt/openclaw_dataencrypted = true) — it costs nothing and keeps data-at-rest controls auditable.most_recent = true data source is fine for dev, surprising for prod.OPENCLAW_HOME into Terraform: pass it via templatefile() user data so the agent never starts pointing at the wrong path.Provision Windows Server 2025 on AWS EC2 with Terraform. Includes AMI selection, password retrieval, RDP, IIS, and joining Active Directory.
Use the AWS IAM Policy Simulator to validate Terraform IAM policies before applying. Automate permission testing with Terraform data sources and avoid AccessDenied errors.
Deploy real infrastructure on AWS Free Tier with Terraform. Includes EC2, S3, RDS, Lambda, and DynamoDB examples — all within free tier limits. No charges if you follow this guide.
Provision multiplayer game server backends with Terraform: AWS GameLift fleets, FlexMatch matchmaking, queues, and player session APIs.