Modern engineering organizations — particularly in automotive, manufacturing, and embedded systems — face a common set of challenges:
- Hardware scarcity: Physical prototypes and development boards are expensive and in short supply during early development stages.
- Environment complexity: Setting up toolchains, simulators, and IDEs for embedded development takes days or weeks per developer.
- Collaboration barriers: Distributed teams need shared, reproducible environments that work identically regardless of location.
- Slow feedback loops: Validating software against virtual targets should happen continuously, not only when hardware is available.
VEW addresses these by providing a unified platform where platform engineers define and publish ready-to-use development environments (workbenches), and developers provision them on demand through a web portal.
| Use case | How VEW helps |
|---|---|
| Cloud-based development environments | Pre-configured workbenches with IDEs, compilers, and debuggers — launched in minutes |
| Virtual target simulation | Run software-in-the-loop (SiL) tests against virtual hardware models in the cloud |
| AMI factory | Automated, repeatable image creation using EC2 Image Builder with versioned components and recipes |
| Multi-tenant project isolation | Separate environments, access controls, and cost tracking per project and team |
| Self-service provisioning | Developers browse a product catalog and launch workbenches without filing tickets |
| Remote desktop access | Secure browser-based access to Windows and Linux workbenches via Amazon DCV |
- Tools — Pre-configured development environments, IDEs, and toolchains
- Targets — Platform abstraction, virtualization, and target configurations
- Environments — Validation infrastructure, simulation, and test data
- Automation — CI/CD, environment provisioning, and artifact management
- Self-Service Portal — RBAC, project management, and workflow orchestration
VEW is built on AWS serverless technologies using hexagonal architecture with Domain-Driven Design (DDD) patterns. The platform separates business logic from infrastructure through bounded contexts, each independently deployable.
VEW supports two deployment modes:
- Public — CloudFront + Lambda@Edge + S3 for the frontend, public API Gateway endpoints. Suitable when users access the platform over the internet.
- Private — Application Load Balancer within a VPC for the frontend, private API Gateway endpoints via VPC endpoints. Suitable when the platform must remain within a corporate network.
| Layer | Technology |
|---|---|
| Backend | Python 3.13, AWS Lambda, Lambda Powertools |
| API | Amazon API Gateway (REST) with both Lambda and IAM authorization |
| Database | Amazon DynamoDB |
| Frontend | React 18, TypeScript, Vite, Cloudscape Design System |
| Authentication | Amazon Cognito with OIDC/SAML federation |
| CDN and protection | CloudFront, Lambda@Edge, WAF |
| Events | Amazon EventBridge (bus, pipes, scheduler) |
| Orchestration | AWS Step Functions, AWS Service Catalog |
| Image building | EC2 Image Builder |
| Infrastructure | AWS CDK v2 (Python for backend, TypeScript for frontend) |
Each bounded context is an independent service with its own API, data store, and deployment:
| Context | Purpose |
|---|---|
| Authorization | API authorization and permission validation using Cedar policies |
| Projects | Project lifecycle, membership, enrollment, and resource allocation |
| Packaging | AMI components, recipes, EC2 Image Builder pipelines and images |
| Publishing | Service Catalog products, versions, and portfolio management |
| Provisioning | Product launch/terminate, workbench lifecycle management |
| Usecase | Minimal skeleton demonstrating how a new bounded context plugs into a use-case account (template/reference) |
| Shared | Common utilities, DDD helpers, middleware, instrumentation |
- Hexagonal architecture — Domain logic at the center, ports define contracts, adapters implement infrastructure
- CQRS — Separate command handlers (writes) from query services (reads)
- Repository + Unit of Work — Transactional consistency for DynamoDB operations
- Domain Events — EventBridge for cross-context communication
- Value Objects — Self-validating DTOs with factory methods
├── backend/ # Python Lambda application + CDK infrastructure
│ ├── app/ # Application code (bounded contexts)
│ ├── infra/ # CDK stacks, constructs, and configuration
│ └── setup/ # CloudFormation prerequisites (spoke account bootstrap)
│
├── frontend/ # React web application + CDK infrastructure
│ ├── infrastructure/ # CDK stacks (Cognito, CloudFront, S3, WAF)
│ └── web/ # Main web application (Vite, Cloudscape)
│
├── deploy.sh # Automated deployment script (10 phases)
└── config.env # Sample deployment configuration
See backend/README.md and frontend/README.md for detailed structure, setup, and development instructions.
Region constraint (public deployments only): Public deployments must use us-east-1, because WAFv2 WebACLs with
CLOUDFRONTscope must be created there and the public frontend stack does not handle cross-region WAF deployment. Private deployments (PRIVATE_DEPLOYMENT=true) use an internal ALB with no CloudFront/WAF and can be deployed to any region — setAWS_REGIONto your target region.
| Requirement | Version | Purpose |
|---|---|---|
| AWS CLI | v2 | AWS resource management |
| AWS CDK | v2 | Infrastructure deployment |
| Node.js | 24+ | Frontend build, CDK (TypeScript) |
| Python | 3.13+ | Backend application, CDK (Python) |
| uv | 0.11+ | Python dependency management |
| Yarn | 4.x | Frontend package management |
| jq | any | JSON processing in deploy script |
| openssl | any | Only for private deployments without CERT_ARN (generates the self-signed certificate). Preinstalled on macOS and in the Docker deployer image |
| Docker | any | Required on macOS/Windows for Lambda bundling and ECS image builds. On Linux, only needed for ECS task image builds. |
You also need:
- An AWS account with credentials configured (admin or PowerUser access for initial deployment)
- The account must be part of an AWS Organization (the deploy script reads the Organization ID)
- TLS certificates in ACM (optional — for custom domains)
- OIDC client credentials (optional — for corporate login federation)
# System packages
sudo apt update && sudo apt install -y software-properties-common jq docker.io unzip curl openssl
# Python 3.13
sudo add-apt-repository -y ppa:deadsnakes/ppa
sudo apt update && sudo apt install -y python3.13 python3.13-venv python3.13-dev
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.13 2
# Node 24 + Yarn 4
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
sudo npm install -g aws-cdk
sudo corepack enable
corepack prepare yarn@4.14.1 --activate
# uv (Python package manager)
curl -LsSf https://astral.sh/uv/install.sh | sh
# AWS CLI v2
cd /tmp
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip && sudo ./aws/install && rm -rf aws awscliv2.zip
# Docker permissions
sudo usermod -aG docker $USER && newgrp dockerWSL users: Copy the repository to the Linux filesystem before running the deploy script. Python venvs and Yarn fail on /mnt/c (Windows filesystem).
cp -r /mnt/c/path/to/vew /tmp/vew && cd /tmp/vewIf you'd rather skip local prerequisite setup entirely, build and run the deployer image. First, create a config file from the provided sample and fill in your values (see Configuration reference for all available parameters):
cp config.env deploy-config.env
# Edit deploy-config.env with your valuesThen build and run:
docker build -t vew-deployer .
docker run --rm -it \
-v "$(pwd)":/app \
-v /var/run/docker.sock:/var/run/docker.sock \
-e AWS_ACCESS_KEY_ID \
-e AWS_SECRET_ACCESS_KEY \
-e AWS_SESSION_TOKEN \
-e AWS_SPOKE_ACCESS_KEY_ID \
-e AWS_SPOKE_SECRET_ACCESS_KEY \
-e AWS_SPOKE_SESSION_TOKEN \
-e AWS_DEFAULT_REGION=us-east-1 \
vew-deployer --config /app/deploy-config.envThe Docker socket mount (-v /var/run/docker.sock:/var/run/docker.sock) is required for ECS task image builds. Pass credentials via environment variables — no AWS profile configuration needed inside the container.
chmod +x deploy.sh
./deploy.shThe script prompts for all required parameters interactively, then executes 10 phases:
- Validates prerequisites (
aws,cdk,node,python,uv,jq,yarn) - Patches source configuration files with your org/app prefix and region
- CDK bootstraps the target account (and
us-east-1if deploying to another region) - Creates prerequisite resources (SSM parameters, VPC, service-linked roles)
- Configures identity federation (OIDC secret in Secrets Manager)
- Deploys frontend infrastructure (Cognito, CloudFront, S3, WAF)
- Deploys backend infrastructure (Lambda, API Gateway, DynamoDB, EventBridge)
- Builds and uploads the frontend web application to S3
- Seeds DynamoDB with an admin user and default program
- Optionally bootstraps a spoke account for workbench provisioning
cp config.env deploy-config.env
# Edit deploy-config.env with your values
./deploy.sh --config deploy-config.envThe script saves a config file after the first run at .deploy-config-{env} for re-runs.
Parameters used by deploy.sh (prompted interactively or loaded from config file):
| Parameter | Default | Description |
|---|---|---|
AWS_ACCOUNT_ID |
— | 12-digit AWS account ID |
AWS_REGION |
us-east-1 |
Deployment region |
ENVIRONMENT |
dev |
Environment name (dev, qa, prod) |
ORG_PREFIX |
proserve |
Organization prefix for resource naming |
APP_PREFIX |
wb |
Application prefix for resource naming |
ADMIN_EMAIL |
— | Email for the initial admin user |
ADMIN_USER_ID |
— | User ID for the initial admin (uppercase) |
AWS_PROFILE_HUB |
default |
AWS CLI profile for the hub (main) account |
AWS_PROFILE_SPOKE |
— | AWS CLI profile for the spoke account |
OIDC_CLIENT_ID |
— | OIDC client ID (empty = manual Cognito users) |
OIDC_CLIENT_SECRET |
— | OIDC client secret |
OIDC_ISSUER_URL |
— | OIDC issuer URL |
CERT_ARN |
— | TLS certificate ARN in deployment region |
CERT_ARN_US_EAST_1 |
— | TLS certificate ARN in us-east-1 (required if deploying to another region) |
CUSTOM_DOMAIN |
— | Custom domain for the web app (e.g., dev.workbench.company.com) |
API_CUSTOM_DOMAIN |
— | Custom domain for the API (e.g., dev.api.workbench.company.com) |
PRIVATE_DEPLOYMENT |
false |
true for an internal ALB-based private deployment (no CloudFront). Requires CUSTOM_DOMAIN, API_CUSTOM_DOMAIN; deployable to any AWS_REGION |
PRIVATE_DNS_AUTOMATE |
true |
When private, create a Route 53 private hosted zone (associated with the hub VPC) with alias records to both ALBs |
PRIVATE_DNS_ZONE |
— | Private hosted zone name (empty = derived from CUSTOM_DOMAIN) |
SPOKE_ACCOUNT_ID |
— | Spoke account ID for workbench provisioning |
SPOKE_VPC_ID |
— | VPC ID in the spoke account |
Additional configuration not managed by deploy.sh (edit manually for advanced tuning):
| File | Setting | Default | Description |
|---|---|---|---|
frontend/infrastructure/cdk.json |
RequireCustomUserLogin2FA |
true |
Require MFA for Cognito-native users |
frontend/infrastructure/cdk.json |
CustomLoginDNSEnabled |
false |
Enable custom DNS for Cognito login page |
backend/infra/config.py |
rest-api-cors-origins |
* |
Restrict to your domain in production |
backend/infra/config.py |
retain_resources |
False |
True in production to prevent data loss on stack deletion |
backend/infra/config.py |
backup-resources |
False |
True in production to enable DynamoDB PITR and S3 versioning |
backend/infra/config.py |
enabled-workbench-regions |
["us-east-1"] |
Regions where workbenches can be provisioned |
backend/infra/config.py |
allowed-cidrs-for-private-api-endpoint |
RFC 1918 ranges | CIDR ranges allowed to reach private API endpoints |
backend/infra/config.py |
user-role-stage-access |
all roles → ["dev"] |
Which spoke account stages each VEW role can provision into |
backend/infra/config.py |
disabled-components |
[] |
Bounded contexts to disable per environment |
backend/infra/config.py |
product-limit-version |
5 |
Max product versions in the publishing catalog |
backend/infra/config.py |
Lambda concurrency settings | 10 reserved, 1 provisioned |
Per-function concurrency (tune for expected load) |
The deploy script replaces default values in source files before deploying:
| File | What changes |
|---|---|
backend/infra/config.py |
Org/app prefix, Cognito region, enabled workbench regions |
backend/infra/constants.py |
Lambda architecture (ARM/x86), local bundling flag, private API endpoint toggle |
frontend/infrastructure/cdk.json |
App name, deployment qualifier, OIDC secret name, private deployment flag, VPC name |
frontend/infrastructure/lib/public-access-deployment-stack.ts |
Monitoring resource name prefix |
If you deploy with the defaults (proserve/wb/us-east-1), no patching occurs.
Set PRIVATE_DEPLOYMENT=true (in your config file or the interactive prompt) to deploy behind internal Application Load Balancers in a VPC instead of CloudFront. In this mode the platform is never exposed to the internet; users reach it over a corporate network path (VPN, Direct Connect, or Transit Gateway).
Private mode requires CUSTOM_DOMAIN and API_CUSTOM_DOMAIN, and a TLS certificate. It is not pinned to us-east-1 — set AWS_REGION to your target region. The certificate is handled by CERT_ARN:
- If
CERT_ARNis set, the script uses your regional ACM certificate inAWS_REGION(issued/imported however your organization manages certificates). - If
CERT_ARNis empty, the script generates a self-signed certificate with SANs for both domains and imports it into ACM, settingCERT_ARNautomatically. This is free and fully self-contained, but clients must trust the certificate: distribute it to user trust stores (e.g. via MDM), otherwise browsers reject it and the web app's API calls fail. The certificate is valid for 397 days (under the 398-day browser limit); re-run the deploy to rotate it.
The script then:
- Sets
PRIVATE_API_ENDPOINT = True(backend) andPrivateDeployment/VPCName(frontend) automatically. - Skips the us-east-1 bootstrap and the us-east-1 Cognito certificate (both are CloudFront-only concerns).
- Provisions private API Gateway endpoints (via VPC interface endpoints) fronted by internal ALBs, and an internal ALB serving the web app.
- When
PRIVATE_DNS_AUTOMATE=true(default), creates a Route 53 private hosted zone associated with the hub VPC and alias records pointingCUSTOM_DOMAINandAPI_CUSTOM_DOMAINat the two ALBs. Set it tofalseto manage DNS externally (the script then prints the records to create).
The deployment uses the VPC named vpc-{org}-{app}-{env}. If it does not exist, Phase 4 creates one; for qa/prod it provisions one NAT gateway per Availability Zone. Tighten allowed-cidrs-for-private-api-endpoint in backend/infra/config.py from the default RFC 1918 ranges to your corporate CIDRs.
In private mode, any Lambda that calls a cross-bounded-context API (declared via cross_bc_api_access on its AppEntryPoint) is automatically attached to that VPC so it can resolve the private API Gateway endpoint through the VPC interface endpoint. Public deployments keep Lambdas outside the VPC to avoid the associated NAT/endpoint cost.
After deployment completes, the script prints:
- The CloudFront URL for the web application
- Admin credentials
- The
aws cognito-idp admin-create-usercommand (if no OIDC was configured)
DNS records for custom domains must be created manually after the first deployment.
Approximate monthly cost for a minimal deployment (us-east-1, no active workbenches):
| Service | Estimated cost |
|---|---|
| CloudFront | ~$1 (minimal traffic) |
| Cognito | Free tier (< 50k MAU) |
| Lambda | Free tier (< 1M requests/month) |
| API Gateway | ~$3.50 per million requests |
| DynamoDB | ~$5 (on-demand, minimal usage) |
| S3 | < $1 (static assets) |
| WAF | ~$11 (3 WebACLs × $5 + rules) |
| EventBridge | < $1 |
| Step Functions | < $1 |
| Total (idle) | ~$25–35/month |
Active usage with workbenches adds EC2 instance costs in the spoke account. Use AWS Cost Explorer with the {org}-{app}-* tag pattern to track VEW-specific spending.
Workbenches are provisioned in spoke accounts, separate from the main platform account. To onboard a spoke account, either provide the spoke account ID during the interactive prompt, or deploy the bootstrap template manually:
aws cloudformation deploy \
--template-file backend/setup/prerequisites/vew-spoke-account-bootstrap.yml \
--stack-name VEW-Spoke-Bootstrap \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides \
WebApplicationAccountId=<MAIN_ACCOUNT_ID> \
WebApplicationEnvironment=dev \
VPCIdParameterValue=<SPOKE_VPC_ID>This must be deployed into the spoke account, not the main account.
VEW comes pre-configured with a tag based VPC subnet selection strategy to provision workbenches. Subnet selection is defined in backend/infra/config.py, provisioning-subnet-selector setting. Available subnet selection strategies are:
- TaggedSubnet (default) - filters for subnets tagged with a tag specified in the
provisioning-subnet-selector-tagsetting. - PublicSubnet - selects a public subnet. Use this for testing purposes only.
- PrivateSubnetWithTransitGateway - selects a private subnet that has a transit gateway route. Use it when workbenches need to be accessible only from the corporate network.
All of the subnet selection strategies pick a subnet with the most remaining IPs left.
If you specify the SPOKE_ACCOUNT_ID parameter and leave the SPOKE_VPC_ID blank, the deploy script will create a new VPC with public subnets ready for provisioning. If you specify a SPOKE_VPC_ID value, then you must tag the subnets on the spoke account using ProvisioningEnabled key and True value.
Once deployed, VEW follows a three-stage workflow to deliver development environments to end users:
Platform engineers define what goes into a workbench image:
- Create components — individual software installation and test steps (mapped to EC2 Image Builder components)
- Assemble components into recipes — ordered lists of software to include (mapped to Image Builder recipes)
- Create pipelines — automated builds that produce AMIs from recipes (mapped to Image Builder pipelines)
- Run the pipeline to produce a tested, versioned image
Platform engineers make images available to users:
One-time setup: Products publish into a Service Catalog portfolio that is created when you onboard an AWS account to a technology. Do this once per technology before creating products — see Onboard a technology account.
- Create a product template — CloudFormation template defining network, security groups, instance type, storage, and the AMI reference
- Create a product from the template and publish it to the Service Catalog portfolio
- Create product versions as images evolve
Developers use the self-service portal to:
- Browse available products in the catalog
- Launch a product — VEW provisions the underlying EC2 instance via Service Catalog
- Start/stop provisioned products to control costs
- Connect to running workbenches via browser (Amazon DCV) or SSH
- Terminate products when no longer needed
If you just deployed VEW and want to see it in action, start here:
- Onboard a technology account — the one-time platform setup that creates the Service Catalog portfolio products publish into. Do this before building your first product.
- Build your first product — create a FreeRTOS virtual target from scratch: component, recipe, pipeline, product, and a running instance you can SSH into.
- Launch a product — provision any product from the catalog.
- Connect to a product — reach your running product via browser, DCV client, or SSH.
More examples (Android Cuttlefish, QEMU/KVM) are in the examples/ folder.
See backend/README.md — Extending the backend for the full walkthrough with code examples covering entrypoints, command handlers, bootstrappers, domain events, and CDK stacks.
Key architectural rules enforced by automated fitness function tests:
- Dependencies point inward only — domain code must never import from adapters or entrypoints
- Bounded contexts are isolated — no imports between contexts
- Ports are owned by the domain — adapters implement domain-defined interfaces
- Cross-context communication uses EventBridge events, not direct imports
See frontend/README.md — Extending the frontend for adding pages, generating API clients, and UI patterns.
- JWT and IAM authorization on API Gateway
- JWT validation via Lambda@Edge on every request
- Corporate identity federation through Cognito OIDC/SAML
- WAF on CloudFront, Cognito, and API Gateway with AWS managed rule groups (anonymous IP, IP reputation, known bad inputs, common exploits)
- KMS encryption for AMI artifacts and SNS alarm topics
- Cedar-based fine-grained authorization (Amazon Verified Permissions)
See CONTRIBUTING and SECURITY.md for more information.
- Stellantis' SDV transformation with the Virtual Engineering Workbench on AWS
- How Stellantis built the serverless self-service portal for the Virtual Engineering Workbench
- How Schaeffler AG accelerates automotive software development by leveraging the AWS Virtual Engineering Workbench Framework
- How Schaeffler uses generative AI to accelerate automotive software testing
- How Stellantis streamlines floating license management with serverless orchestration on AWS
- Building hexagonal architectures on AWS
- Cloudscape Design System
See AUTHORS.md.
This project is licensed under the Apache-2.0 License.

