Deploy on AWS
Run PandaStack on a bare-metal EC2 host with the dev Terraform environment and the make tf-* targets.
AWS requires a bare-metal instance to expose KVM — EC2 does not offer nested virtualization on standard instance types. The Terraform environment defaults to c5n.metal, and the host bootstrap installs the x86_64 Firecracker release, so the instance type must be x86_64.
There is no deploy-aws.sh. The AWS path is infra/terraform/envs/dev, driven by the make tf-* targets, followed by deploy/deploy.sh to push binaries onto the host.
Bare-metal EC2 families are not available on a new account by default. Request the vCPU quota for the family you intend to use in Service Quotas → Amazon EC2 before you plan, or terraform apply will fail on capacity.
Prereqs
- AWS CLI configured with a named profile (
aws configure --profile <name>) terraform- A Cloudflare API token with
Zone:DNS:Edit, plus the zone ID — DNS is managed by the Terraformdnsmodule - An SSH public key and the CIDR you will SSH from
Configure
aws_profile = "<your-cli-profile>"
ssh_pubkey = "ssh-ed25519 AAAA... you@host"
ssh_allowed_cidr = "1.2.3.4/32"
cloudflare_api_token = "<token>"
cloudflare_zone_id = "<zone-id>"
instance_type = "c5n.metal"
spot_max_price = "1.50"Deploy
make tf-init
make tf-plan
make tf-apply
make tf-outputThese run terraform -chdir=infra/terraform/envs/dev with -var-file=terraform.tfvars; apply and destroy pass -auto-approve.
Terraform builds four modules:
network— VPC, internet gateway, public subnet and route table, an Elastic IP, and a security group that allows SSH only fromssh_allowed_cidrand ports 80/443 only from Cloudflare's IPv4 rangesstorage— an S3 bucket for kernels, templates, and snapshots, with versioning, server-side encryption, and a lifecycle policycompute— the EC2 instance, IMDSv2 required, a 100 GB gp3 root volume, andcloud-init/user-data.shas user datadns— Cloudflare A records for the dashboard and API subdomains
Outputs are eip_address, instance_id, s3_bucket_name, app_fqdn, and api_fqdn.
The user-data script installs Firecracker and the jailer (v1.16.0), Caddy, and Node.js; creates the pandastack-agent, pandastack-api, and pandastack-dashboard systemd units; and syncs kernels and templates down from the S3 or GCS bucket if one is configured.
The dev environment does not set use_spot, so the compute module default applies and the host is provisioned as a persistent Spot instance (interruption behavior stop, capped at spot_max_price). A bare-metal Spot instance can be reclaimed, taking every sandbox on the host with it. To run on-demand, pass use_spot = false to the compute module in infra/terraform/envs/dev/main.tf — the environment does not expose it as a tfvars variable.
Defaults worth changing
| Variable | Default | Notes |
|---|---|---|
aws_region | us-east-1 | |
instance_type | c5n.metal | Must be a bare-metal x86_64 type |
spot_max_price | "1.50" | Maximum hourly Spot price |
ami_id | null | Unset resolves the latest Ubuntu 24.04 AMI; set it to pin a custom AMI |
cloudflare_zone_name | pandastack.dev | Set this to your own zone |
subdomain_app / subdomain_api | dev / api.dev | |
project_tag | pandastack-dev | Applied as the Project tag |
Push binaries to the host
Once the instance is up, build and ship the agent, API, and dashboard:
make deployThat resolves the Elastic IP from Terraform output and runs ./deploy/deploy.sh -h <ip>. The script accepts:
./deploy/deploy.sh -h HOST [--skip-build] [--skip-dashboard]HOST is the Elastic IP or an FQDN; it connects as ubuntu@HOST.
Operate
make ssh # ssh ubuntu@<eip_address>
make logs # journalctl for pandastack-agent, pandastack-api, and caddySizing
Capacity is bounded by memory. Firecracker cannot resize vCPU or RAM at snapshot restore, so each template's guest RAM is fixed when the template is baked — base and browser at 4096 MiB, code-interpreter and agent at 2048 MiB, postgres-16 at 1024 MiB. Concurrency is roughly the host's usable RAM divided by the baked RAM of the template you run. When memory runs out, sandbox creates return 502.
A single bare-metal host has no second node to fall back on. For a fleet with a load-balanced control plane and a scalable agent tier, use the multi-node topology in Deploy on GCP.
Tear down
make tf-destroy