PandaStack

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 Terraform dns module
  • An SSH public key and the CIDR you will SSH from

Configure

infra/terraform/envs/dev/terraform.tfvars
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-output

These 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 from ssh_allowed_cidr and ports 80/443 only from Cloudflare's IPv4 ranges
  • storage — an S3 bucket for kernels, templates, and snapshots, with versioning, server-side encryption, and a lifecycle policy
  • compute — the EC2 instance, IMDSv2 required, a 100 GB gp3 root volume, and cloud-init/user-data.sh as user data
  • dns — 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

VariableDefaultNotes
aws_regionus-east-1
instance_typec5n.metalMust be a bare-metal x86_64 type
spot_max_price"1.50"Maximum hourly Spot price
ami_idnullUnset resolves the latest Ubuntu 24.04 AMI; set it to pin a custom AMI
cloudflare_zone_namepandastack.devSet this to your own zone
subdomain_app / subdomain_apidev / api.dev
project_tagpandastack-devApplied as the Project tag

Push binaries to the host

Once the instance is up, build and ship the agent, API, and dashboard:

make deploy

That 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 caddy

Sizing

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

On this page