Terraform + Python toolkit to deploy and operate Amazon WorkSpaces Applications (formerly AppStream 2.0) — streams Windows applications or a full desktop to any user via browser, with zero VPN and zero client install.
AWS Console name: WorkSpaces Applications | API/CLI name:
appstream| AWS Documentation
| Use Case | Mode |
|---|---|
| Remote training sessions — stream tools to attendees | APP or DESKTOP |
| Customer demos — run your software in a controlled browser session | APP |
| Contractor / vendor access — no VPN, no install, time-limited | APP |
| Secure dev/test environments — isolated Windows desktop in browser | DESKTOP |
| Any Windows app accessible to external users without exposing your network | APP or DESKTOP |
WorkSpaces Applications runs a Fleet of Windows EC2 instances from a pre-built Image. Users get a streaming URL — they click it, authenticate, and get the app in their browser. The fleet instance is recycled on disconnect — nothing persists.
External User (browser — zero install)
│ HTTPS via VPC Interface Endpoints (AWS backbone, no public subnet)
▼
WorkSpaces Applications Fleet (Windows Server 2022 — Private Subnet)
│ Private IP only
▼
Your App Servers / EC2s (Private Subnet)
│
▼
Your On-Premises network (via VGW / SD-WAN if needed)
Two streaming modes — both run from the same image, no re-provisioning to switch:
| Mode | User sees | Terraform flag |
|---|---|---|
| APP (default) | Individual app windows only | stream_view = "APP" |
| DESKTOP | Full Windows desktop, all apps | stream_view = "DESKTOP" |
AWS reference: Fleet types | Streaming modes
aws-workspaces-applications/
├── README.md — this file
├── ops.md — day-to-day training session runbook
├── add_application.md — how to add new apps to an existing image
├── python/
│ ├── manage_training.py — start/stop fleet, generate URLs, manage users
│ ├── config.json — all parameters (account, VPC, fleet settings)
│ └── requirements.txt — pip install -r python/requirements.txt
└── terraform/
├── main.tf — all AWS resources
├── variables.tf — all variables with defaults + descriptions
├── outputs.tf — fleet/stack names, next steps
└── example.tfvars — current Customer Example values (copy to replicate)
External User (browser — zero install)
│ HTTPS via VPC Interface Endpoints (AWS backbone)
▼
AppStream Fleet (Windows Server 2022 — Private Subnet)
│ Private IP
▼
App Servers — YourApp1, YourApp2, YourApp3 EC2s (Private Subnet)
│
▼
VGW → Hub Firewall → SD-WAN → On-Premises
No public subnet. No VPN. Fits org standard VPC with zero exceptions.
1. terraform/example.tfvars — infrastructure parameters
project = "your-project-name" # used as prefix for all resource names
region = "us-west-2"
aws_profile = "YourSSOProfile" # your AWS SSO profile name (see below)
vpc_id = "vpc-xxxx"
private_subnet_id = "subnet-xxxx"
vpc_cidr = "10.x.x.x/xx"
appstream_custom_image = "your-image-name"
test_user_email = "you@yourcompany.com"2. python/config.json — day-to-day ops parameters
"project": "your-project-name", ← must match tfvars
"account_id": "123456789012", ← your AWS account ID
"region": "us-west-2",
"profile": "YourSSOProfile", ← your AWS SSO profile name (see below)
"network": { "vpc_id", "private_subnet", "vpc_cidr" },
"users": { "app": ["user@company.com"], "desktop": [...] }AWS Credentials — three options
| Method | Python script | Terraform |
|---|---|---|
| SSO profile (recommended for org accounts) | Set "profile" in config.json → aws sso login --profile YourProfile |
Set aws_profile in .tfvars |
| IAM keys (CI/CD or simple setups) | --key AKID --secret SECRET flags, or set AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY env vars |
Set env vars — leave aws_profile empty in .tfvars |
| Assume Role | Configure a named profile in ~/.aws/config with role_arn + source_profile, set that profile name in config.json |
Same — set that profile name in .tfvars |
SSO setup (one-time):
aws configure sso
# Follow prompts: enter your org SSO start URL, region, pick account + role, give the profile a name
# Then set that profile name in config.json "profile" and terraform/example.tfvars aws_profileAWS reference: IAM Identity Center (SSO) | Named profiles | Assume Role with external ID
# SSO login — use the profile name you configured in config.json
aws sso login --profile YourProfileName
aws sts get-caller-identity --profile YourProfileName # verify
# Python dependency (for manage_training.py)
pip install -r python/requirements.txt
# Terraform
terraform -version # requires >= 1.0Both modes run from the same image and share the same Phase 1 infra. They coexist — no teardown needed.
| Mode | User sees | When to use | Terraform flag |
|---|---|---|---|
| APP (default) | Individual app windows | Controlled training — one app at a time | stream_view = "APP" |
| DESKTOP | Full Windows desktop, all apps at once | Power users, multi-app workflows | stream_view = "DESKTOP" |
Switching a user between modes = send them a different streaming URL. No re-provisioning.
cd terraform
cp example.tfvars customer2.tfvars
# Edit: project, region, vpc_id, private_subnet_id, vpc_cidrResource names auto-derive from project — no conflicts between deployments.
| Phase | What it does | Run once or every time? |
|---|---|---|
| Phase 1 | Shared infra — SGs, VPC Endpoints, Image Builder. Install apps manually, create image. | Once per account — shared by APP and DESKTOP |
| Phase 2 | Fleet + Stack from the built image. | Once per mode — run again with stream_view=DESKTOP for DESKTOP fleet |
cd terraform
terraform init
# Phase 1 — Shared infra + Image Builder (once per account)
terraform plan \
-target=aws_iam_role.appstream_service \
-target=aws_iam_role_policy_attachment.appstream_service \
-target=aws_security_group.appstream_sg \
-target=aws_security_group.endpoint_sg \
-target=aws_vpc_endpoint.appstream_streaming \
-target=aws_vpc_endpoint.appstream_api \
-target=aws_appstream_image_builder.poc_builder \
-var-file=example.tfvars \
-out=phase1.tfplan
terraform apply phase1.tfplan- Console → WorkSpaces Applications → Image Builders →
your-project-image-builder→ Connect - Windows desktop opens in browser. Install:
- Chrome — Edge →
https://www.google.com/chrome - PuTTY —
https://www.putty.org - Java (Amazon Corretto) — required for JMeter, install first
- Apache JMeter —
https://jmeter.apache.org→ zip → extract toC:\JMeter→ runjmeter.bat
- Chrome — Edge →
- Open Image Assistant (desktop shortcut) → add each app → set name → Disconnect and Create Image
- Wait ~20-30 min → image status AVAILABLE (Image Builder goes to Stopped when done)
JMeter has no installer — it's a zip. Save test plans to
C:\Users\PhotonUser\Downloads\. Avoid switching users or dual-screen — causes resolution issues.
# Phase 2 — Fleet + Stack — APP mode (individual apps)
terraform plan -var-file=example.tfvars -out=phase2.tfplan
terraform apply phase2.tfplan
# Phase 2 — DESKTOP mode (full Windows — parallel, APP fleet untouched)
terraform plan -var-file=example.tfvars -var="stream_view=DESKTOP" -out=phase2-desktop.tfplan
terraform apply phase2-desktop.tfplan
# Different customer
terraform plan -var-file=customer2.tfvars -out=phase2.tfplan
terraform apply phase2.tfplan
# Teardown
terraform destroy -var-file=example.tfvarsNotes:
AmazonAppStreamServiceAccessIAM role is not auto-created in fresh accounts — Terraform handles it with 20s propagation wait. SG descriptions must use ASCII only.
See ops.md for the full training session runbook.
Quick reference — all commands default to APP fleet, add --desktop for DESKTOP fleet:
cd python
python manage_training.py --start --users 10 # Start APP fleet
python manage_training.py --start --users 10 --desktop # Start DESKTOP fleet
python manage_training.py --get-urls a@b.com b@c.com # APP streaming URLs
python manage_training.py --get-urls a@b.com b@c.com --desktop # DESKTOP streaming URLs
python manage_training.py --status # Both fleet states + sessions
python manage_training.py --stop # Stop APP fleet
python manage_training.py --stop --desktop # Stop DESKTOP fleet| Layer | Configuration |
|---|---|
| Identity | AppStream User Pool — invite by email, user sets password |
| Session | Clipboard enabled, file upload/download/print disabled |
| Network | Fleet SG: outbound only to VPC CIDR (ports 22, 80, 443, 8080) |
| Time | Max session 4 hrs, idle disconnect 5 min |
| Data | On-Demand fleet — instance recycled on disconnect, nothing persists |
| Component | Cost |
|---|---|
| AppStream fleet | $0.20/hr per instance — only when RUNNING |
| VPC Interface Endpoints | |
| 5 users × 8hr training day | ~$8.50 total |
Zero cost when fleet is stopped. Always run --stop after each session.
| Image | OS | Use |
|---|---|---|
AppStream-WinServer2022-11-10-2025 |
Windows Server 2022 | Recommended |
AppStream-WinServer2025-12-18-2025 |
Windows Server 2025 | After app compatibility confirmed |
AppStream-WinServer2019-11-10-2025 |
Windows Server 2019 | Fallback |
# Refresh list
aws appstream describe-images --type PUBLIC \
--query 'Images[?contains(Name, `WinServer`)].{Name:Name,State:State}' \
--output table --profile YourSSOProfile --region us-west-2See add_application.md for the full guide. Quick summary:
- Create Image Builder from existing image (console)
- Connect → install new app → Image Assistant → new image name (e.g.
your-project-image-v2) - Update
appstream_custom_imagein.tfvars→terraform apply