From eae80f353d48acc2b4e2fa45a55f1daff44b9ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lio=E6=9D=8E=E6=AD=90?= Date: Tue, 28 Jul 2026 15:30:26 +0200 Subject: [PATCH] Revert "Revert Lightsail as the default 1-click template" --- .github/workflows/publish-template.yml | 97 +++++++++++-------- AGENTS.md | 2 +- README.md | 127 ++++++++++++++----------- aws/README.md | 45 ++++----- docs/index.html | 45 ++------- 5 files changed, 157 insertions(+), 159 deletions(-) diff --git a/.github/workflows/publish-template.yml b/.github/workflows/publish-template.yml index 63e8435..dc7513d 100644 --- a/.github/workflows/publish-template.yml +++ b/.github/workflows/publish-template.yml @@ -5,6 +5,7 @@ on: branches: [ master ] paths: - 'aws/template.yaml' + - 'aws/lightsail-template.yaml' - '.github/workflows/publish-template.yml' workflow_dispatch: @@ -27,7 +28,10 @@ jobs: env: AWS_REGION: ${{ vars.AWS_REGION || 'us-east-1' }} BUCKET: ${{ vars.AGENT_BOX_BUCKET }} - OBJECT_KEY: template.yaml + # Both templates ship from the same bucket: lightsail-template.yaml is + # the default the README's Launch buttons point at, template.yaml is the + # EC2 alternative. + TEMPLATES: lightsail-template.yaml template.yaml ROLE_ARN: ${{ vars.AWS_ROLE_ARN }} steps: - name: Sanity-check required variables @@ -44,20 +48,21 @@ jobs: - uses: cachix/install-nix-action@v31 - - name: Validate template locally + - name: Validate templates locally run: | pip install --quiet cfn-lint - cfn-lint aws/template.yaml + cfn-lint aws/template.yaml aws/lightsail-template.yaml - - name: Inject pinned defaults for the 1-click copy - # The source template deliberately ships without Default: values for + - name: Inject pinned defaults for the 1-click copies + # The source templates deliberately ship without Default: values for # AgentBoxRev / AgentBoxSha256 (see the params' Descriptions), and # with empty Defaults for AgentNixpkgsUrl / AgentNixpkgsSha256 (so - # source-template launches like deploy-test fall back to the AMI + # source-template launches like deploy-test fall back to the base # channel) — the 1-click Launch buttons need real values pre-filled, - # so we inject pinned values into only the S3 template copy: the + # so we inject pinned values into only the S3 template copies: the # AgentBox pair gets a Default inserted, the AgentNixpkgs pair gets - # its empty Default replaced. + # its empty Default replaced. Both templates declare the four + # parameters with identical layout, so one awk serves both. run: | rev="${{ github.sha }}" sha="sha256-$(curl -sSfL "https://raw.githubusercontent.com/${{ github.repository }}/${rev}/modules/agent-box.nix" \ @@ -69,26 +74,28 @@ jobs: echo "Sha: $sha" echo "AgentUrl: $agentUrl" echo "AgentSha: $agentSha" - awk -v rev="$rev" -v sha="$sha" -v agentUrl="$agentUrl" -v agentSha="$agentSha" ' - /^ AgentBoxRev:$/ { rf=1; print; next } - rf && /^ Type:/ { print; print " Default: " rev; rf=0; next } - /^ AgentBoxSha256:$/ { sf=1; print; next } - sf && /^ Type:/ { print; print " Default: " sha; sf=0; next } - /^ AgentNixpkgsUrl:$/ { sf2=1; print; next } - sf2 && /^ Default:/ { print " Default: " agentUrl; sf2=0; next } - /^ AgentNixpkgsSha256:$/ { sf3=1; print; next } - sf3 && /^ Default:/ { print " Default: " agentSha; sf3=0; next } - { print } - ' aws/template.yaml > aws/template.filled.yaml - if ! grep -qxF " Default: $rev" aws/template.filled.yaml \ - || ! grep -qxF " Default: $sha" aws/template.filled.yaml \ - || ! grep -qxF " Default: $agentUrl" aws/template.filled.yaml \ - || ! grep -qxF " Default: $agentSha" aws/template.filled.yaml; then - echo "::error::Default injection failed — expected lines not present in filled template." - diff aws/template.yaml aws/template.filled.yaml || true - exit 1 - fi - cfn-lint aws/template.filled.yaml + for t in $TEMPLATES; do + awk -v rev="$rev" -v sha="$sha" -v agentUrl="$agentUrl" -v agentSha="$agentSha" ' + /^ AgentBoxRev:$/ { rf=1; print; next } + rf && /^ Type:/ { print; print " Default: " rev; rf=0; next } + /^ AgentBoxSha256:$/ { sf=1; print; next } + sf && /^ Type:/ { print; print " Default: " sha; sf=0; next } + /^ AgentNixpkgsUrl:$/ { sf2=1; print; next } + sf2 && /^ Default:/ { print " Default: " agentUrl; sf2=0; next } + /^ AgentNixpkgsSha256:$/ { sf3=1; print; next } + sf3 && /^ Default:/ { print " Default: " agentSha; sf3=0; next } + { print } + ' "aws/$t" > "aws/filled-$t" + if ! grep -qxF " Default: $rev" "aws/filled-$t" \ + || ! grep -qxF " Default: $sha" "aws/filled-$t" \ + || ! grep -qxF " Default: $agentUrl" "aws/filled-$t" \ + || ! grep -qxF " Default: $agentSha" "aws/filled-$t"; then + echo "::error::Default injection failed for $t — expected lines not present in filled template." + diff "aws/$t" "aws/filled-$t" || true + exit 1 + fi + cfn-lint "aws/filled-$t" + done - name: Configure AWS credentials via OIDC uses: aws-actions/configure-aws-credentials@v4 @@ -96,10 +103,13 @@ jobs: role-to-assume: ${{ env.ROLE_ARN }} aws-region: ${{ env.AWS_REGION }} - - name: Validate template with CloudFormation - run: aws cloudformation validate-template --template-body "file://aws/template.filled.yaml" + - name: Validate templates with CloudFormation + run: | + for t in $TEMPLATES; do + aws cloudformation validate-template --template-body "file://aws/filled-$t" + done - - name: Upsert bucket + public-read policy on template.yaml + - name: Upsert bucket + public-read policy on the templates run: | if aws s3api head-bucket --bucket "$BUCKET" 2>/dev/null; then echo "Bucket $BUCKET already exists." @@ -119,10 +129,12 @@ jobs: --public-access-block-configuration \ 'BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=false,RestrictPublicBuckets=false' - # Public-read on template.yaml only — CloudFormation's Launch Stack UI - # requires an S3 URL for templateURL. The module itself is fetched by - # the box direct from raw.githubusercontent.com (dual-stack), so this - # bucket carries no other public assets. + # Public-read on the two template objects only — CloudFormation's + # Launch Stack UI requires an S3 URL for templateURL. The module + # itself is fetched by the box direct from raw.githubusercontent.com + # (dual-stack), so this bucket carries no other public assets. One + # policy covers both objects (a single PutBucketPolicy replaces the + # whole document, so the two templates must not fight over it). aws s3api put-bucket-policy --bucket "$BUCKET" --policy "$(cat <` runs an individual VM test such as `sessions` or `settings-page`. -- `cfn-lint aws/template.yaml` validates the CloudFormation template. +- `cfn-lint aws/template.yaml aws/lightsail-template.yaml` validates the CloudFormation templates. Prefer targeted checks over `nix flake check`; the intentionally filesystem-free VM configuration makes the latter unsuitable. Live browser tests require `E2E_BASE_URL` and `E2E_PASSWORD`; run `playwright test -c tests/e2e` after provisioning the nixpkgs Playwright browsers described in the config. diff --git a/README.md b/README.md index f8a7142..10a8447 100644 --- a/README.md +++ b/README.md @@ -18,74 +18,58 @@ Supported agents: ## 1-click AWS launch -Provisions one EC2 instance (NixOS 25.11) with the module + a browser terminal -(Caddy -> ttyd) already wired up. First load takes ~2-3 minutes while the AMI -provisions, `nixos-rebuild switch` applies the config, and Caddy issues a -Let's Encrypt cert against `.sslip.io`. +Provisions one AWS Lightsail instance running NixOS with the module + a +browser terminal (Caddy -> ttyd) already wired up, priced as **one flat +monthly bundle** (compute + SSD + static IPv4 + a multi-TB transfer +allowance). Lightsail has no NixOS blueprint, so the box boots the Ubuntu +24.04 blueprint and converts itself to NixOS in-place on first boot with +[nixos-infect](https://github.com/elitak/nixos-infect); expect the first +launch to take ~10-20 minutes while the conversion and the first +`nixos-rebuild switch` run and Caddy issues a Let's Encrypt cert against +`.sslip.io`. | Region | Launch | | --- | --- | -| us-east-1 (N. Virginia) | [Launch stack →](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?stackName=agent-box&templateURL=https%3A%2F%2Fdefang-agent-box.s3.us-west-2.amazonaws.com%2Ftemplate.yaml) | -| us-west-2 (Oregon) | [Launch stack →](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/quickcreate?stackName=agent-box&templateURL=https%3A%2F%2Fdefang-agent-box.s3.us-west-2.amazonaws.com%2Ftemplate.yaml) | -| eu-central-1 (Frankfurt) | [Launch stack →](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/quickcreate?stackName=agent-box&templateURL=https%3A%2F%2Fdefang-agent-box.s3.us-west-2.amazonaws.com%2Ftemplate.yaml) | -| eu-west-1 (Ireland) | [Launch stack →](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/quickcreate?stackName=agent-box&templateURL=https%3A%2F%2Fdefang-agent-box.s3.us-west-2.amazonaws.com%2Ftemplate.yaml) | +| us-east-1 (N. Virginia) | [Launch stack →](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?stackName=agent-box&templateURL=https%3A%2F%2Fdefang-agent-box.s3.us-west-2.amazonaws.com%2Flightsail-template.yaml) | +| us-west-2 (Oregon) | [Launch stack →](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/quickcreate?stackName=agent-box&templateURL=https%3A%2F%2Fdefang-agent-box.s3.us-west-2.amazonaws.com%2Flightsail-template.yaml) | +| eu-central-1 (Frankfurt) | [Launch stack →](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/quickcreate?stackName=agent-box&templateURL=https%3A%2F%2Fdefang-agent-box.s3.us-west-2.amazonaws.com%2Flightsail-template.yaml) | +| eu-west-1 (Ireland) | [Launch stack →](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/quickcreate?stackName=agent-box&templateURL=https%3A%2F%2Fdefang-agent-box.s3.us-west-2.amazonaws.com%2Flightsail-template.yaml) | Choose `Agent` (`claude` or `codex`), set a `WebPassword` (any 16–64 -characters, including password-manager symbols), pick an instance size, +characters, including password-manager symbols), pick a bundle size, launch. The stack reports CREATE_COMPLETE only after the box phones home from its first successful -rebuild — a first boot that goes wrong (issue 106 has one way) rolls the +rebuild — a first boot that goes wrong rolls the stack back visibly instead of leaving a green stack with a dead URL. The agent runs as the -`UserName` linux user (default `agent`). The template creates its own -IPv6-enabled VPC/subnet so nothing on the account has to be pre-configured. The -stack Outputs show `https://.sslip.io//` - open it, sign in +`UserName` linux user (default `agent`). Lightsail manages the networking, so +nothing on the account has to be pre-configured. The +stack Outputs show `https://.sslip.io//` - open it, sign in as the `UserName` with your `WebPassword`, complete the selected agent's one-time sign-in, done. `@` is used as the Claude Remote Control session name; rename the stack before launch if you want a friendlier label in the Claude apps. -**Cost note (Feb-2024 AWS IPv4 pricing).** The default is **IPv6-only** to -avoid the ~$3.60/mo public-IPv4 charge that AWS bills for *every* public IPv4, -elastic or not. Works if your client has IPv6 connectivity (most consumer ISPs -in NA/EU do; corporate/coffee-shop nets often don't). If IPv6 isn't reachable -for you, set `PublicIpv4: true` at launch — allocates an EIP, adds $3.60/mo, -works everywhere. IPv6-only boxes still reach IPv4-only sites — notably -`github.com` — through a free public DNS64/NAT64 service -([nat64.net](https://nat64.net)), on by default. Traffic to IPv4-only hosts -transits the NAT64 operator's gateways (TLS and SSH stay end-to-end encrypted -and authenticated); set `Nat64: false` to opt out, at the price of the box -not reaching IPv4-only hosts. - -Costs, all-in, running 24/7: the default is a **persistent Spot** instance -(`UseSpot`) — on a Spot interruption AWS stops and later restarts the *same* -instance, so the disk, IPv6 address, and TLS cert survive (a live tmux -session doesn't; RAM is lost on any stop). Spot for the default `t4g.medium` -(Graviton/aarch64, 2 vCPU / 4 GiB) is currently $0.018–0.024/hr across the -four launch regions — **~$16–20/mo** including the ~$2.40/mo default 30 GiB -gp3 root volume (`RootVolumeSize`). `t4g.small` on Spot lands around -**~$7–10/mo** all-in and works for a single light agent, though its 2 GiB is -tight during self-update rebuilds. Spot runs ~50–60% below on-demand for -these types (small Graviton instances don't see the deep 70–90% Spot -discounts); `UseSpot: false` gets you on-demand `t4g.medium` at ~$0.034/hr -(~$27/mo all-in). Networking is $0/mo in the default IPv6-only mode; with -`PublicIpv4: true` the Elastic IP adds ~$3.60/mo. Terminate the stack to -stop billing. - -Out of disk anyway? Enlarge the volume from the EC2 console (Volumes -> -Modify) and reboot the instance — NixOS grows the partition and filesystem -on boot. The box also garbage-collects the nix store automatically. - -**Root shell via SSM Session Manager.** The template ships no SSH key; the -browser terminal is an unprivileged `agent` user. For a root path onto the -box (e.g. to inspect `amazon-init` on a failed first boot, which is -journal-only and invisible to `get-console-output`), the default template -attaches an IAM instance profile with `AmazonSSMManagedInstanceCore`. Open -a shell via the AWS console (Systems Manager -> Session Manager) or -`aws ssm start-session --target `, then `sudo -i`. This adds -one CAPABILITY_IAM checkbox to the Launch Stack form; opt out with -`EnableSsm=false` to skip it. See -[aws/README.md](./aws/README.md#root-access-via-ssm-session-manager) for -details. +**Cost.** The bundle price is the whole bill — no separate EBS, transfer, or +public-IPv4 line items. The default `small_3_0` (2 vCPU / 2 GiB / 60 GiB SSD) +is **$12/mo flat**; bundles range from `micro_3_0` (1 GiB, $7/mo) to +`xlarge_3_0` (16 GiB, $84/mo). 2 GiB is tight while `nixos-rebuild` evaluates +a self-update — the template adds a 3 GiB swap file to carry it, so updates +are slow rather than fatal; pick `medium_3_0` (4 GiB, $24/mo) for comfortable +rebuild headroom. The attached static IPv4 is included, and the URL survives +a stop/start (a live tmux session doesn't; RAM is lost on any stop). Delete +the stack to stop billing. + +Out of disk? Lightsail bundles have a fixed SSD — snapshot the instance and +restore onto a larger bundle to grow. The box also garbage-collects the nix +store automatically. + +**Root shell for debugging.** The browser terminal is an unprivileged `agent` +user. For a root path onto the box (e.g. to inspect a failed first-boot +conversion — logs land in `/var/log/agent-box-infect.log`), the template +leaves port 22 open by default (`DebugSsh`) for key-only root SSH with the +Lightsail default key: download it from the Lightsail console (Account -> +SSH keys), then `ssh -i root@`. Password auth stays off; +set `DebugSsh=false` at launch to keep 22 closed. **Changing the web password.** Open the settings page (the gear icon next to the terminal), choose **Change password**, and enter the current password plus @@ -108,9 +92,36 @@ Anything that is not a fast-forward of the running revision is refused. Verifying releases against an offline signing key is tracked in [issue 46](https://github.com/defangdevs/agent-box/issues/46). -Template source: [`aws/template.yaml`](./aws/template.yaml). -See [`aws/README.md`](./aws/README.md) for the region -> AMI refresh workflow -and the S3-hosting setup. +Template source: [`aws/lightsail-template.yaml`](./aws/lightsail-template.yaml). +See [`aws/README.md`](./aws/README.md) for design notes and the S3-hosting +setup. + +### Alternative: EC2 template (Spot, IPv6-only) + +The original EC2 template is still published, for accounts that want EC2's +flexibility: arbitrary instance types, Spot pricing, IaC-native VPC +networking, root access via SSM Session Manager, and an EBS root volume that +can grow in place. + +| Region | Launch | +| --- | --- | +| us-east-1 (N. Virginia) | [Launch stack →](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?stackName=agent-box&templateURL=https%3A%2F%2Fdefang-agent-box.s3.us-west-2.amazonaws.com%2Ftemplate.yaml) | +| us-west-2 (Oregon) | [Launch stack →](https://console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/quickcreate?stackName=agent-box&templateURL=https%3A%2F%2Fdefang-agent-box.s3.us-west-2.amazonaws.com%2Ftemplate.yaml) | +| eu-central-1 (Frankfurt) | [Launch stack →](https://console.aws.amazon.com/cloudformation/home?region=eu-central-1#/stacks/quickcreate?stackName=agent-box&templateURL=https%3A%2F%2Fdefang-agent-box.s3.us-west-2.amazonaws.com%2Ftemplate.yaml) | +| eu-west-1 (Ireland) | [Launch stack →](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/quickcreate?stackName=agent-box&templateURL=https%3A%2F%2Fdefang-agent-box.s3.us-west-2.amazonaws.com%2Ftemplate.yaml) | + +It boots a NixOS 25.11 AMI directly (no conversion step; first load ~2-3 +minutes) and defaults to a **persistent Spot** `t4g.medium` on an +**IPv6-only** network — ~$16-20/mo all-in, dodging AWS's ~$3.60/mo +public-IPv4 charge. Set `PublicIpv4: true` at launch if your client has no +IPv6 connectivity (corporate/coffee-shop networks often don't; adds the +$3.60/mo EIP), and `UseSpot: false` for on-demand (~$27/mo all-in, no +interruption risk). IPv6-only boxes reach IPv4-only hosts through a free +public DNS64/NAT64 service ([nat64.net](https://nat64.net)); set +`Nat64: false` to opt out. The full cost breakdown, the Spot +stop-not-terminate behavior, SSM root access, and the other design notes +live in [aws/README.md](./aws/README.md); template source: +[`aws/template.yaml`](./aws/template.yaml). ## Why diff --git a/aws/README.md b/aws/README.md index aaa9de8..5693b4a 100644 --- a/aws/README.md +++ b/aws/README.md @@ -1,13 +1,16 @@ # AWS deployment (`aws/`) -CloudFormation template that provisions a single-agent agent-box host on -EC2 with a browser terminal (Caddy + ttyd). The deployment form lets the user -choose Claude Code or Codex. +CloudFormation templates that provision a single-agent agent-box host with a +browser terminal (Caddy + ttyd). The deployment form lets the user choose +Claude Code or Codex. -- `template.yaml` - the EC2 template. Source of truth; anything else is derived. -- `lightsail-template.yaml` - an alternative that runs the same agent-box on - **AWS Lightsail** for one flat monthly bundle price. See +- `lightsail-template.yaml` - the **default** template (the README's Launch + buttons point here): agent-box on **AWS Lightsail** for one flat monthly + bundle price. See ["Lightsail variant"](#lightsail-variant-lightsail-templateyaml) below. +- `template.yaml` - the EC2 alternative (Spot pricing, IPv6-only networking, + SSM root access, resizable EBS). Most of this document describes it; the + Lightsail section covers what differs. ## What the template does @@ -348,9 +351,11 @@ in-place on first boot with [`nixos-infect`](https://github.com/elitak/nixos-inf ### Deploying (CLI) -The 1-click S3 publish path is not wired up for this template yet (see -below), so deploy it directly. `AgentBoxRev`/`AgentBoxSha256` are a pinned -pair, exactly as for the EC2 template: +The 1-click Launch buttons in the top-level README use the S3-published copy +of this template (pinned defaults injected by `publish-template.yml`, see +["Publishing to S3"](#publishing-to-s3)). To deploy a working-tree copy +directly, note `AgentBoxRev`/`AgentBoxSha256` are a pinned pair, exactly as +for the EC2 template: ```bash REV=$(git rev-parse HEAD) # or any pushed agent-box commit @@ -377,16 +382,11 @@ bundle's initial closure build is slow) and then emits `WebURL`, `cfn-lint` passes and the generated `configuration.nix` parses as valid Nix (both checked in PR CI via `aws-ci.yml`). The end-to-end `nixos-infect` bootstrap has **not** yet been exercised by an automated live deploy — treat a -first real launch as the acceptance test. Follow-ups before this is -first-class: +first real launch as the acceptance test. Remaining follow-up: - A Lightsail leg in `deploy-test.yml` (create stack, assert `WebURL` reachable over IPv4, tear down). GitHub runners are IPv4-only and Lightsail is IPv4-native, so unlike the EC2 IPv6-only leg this can smoke-test the live URL. -- S3 publish + a Launch button. `publish-template.yml` currently publishes only - `template.yaml` and scopes the bucket policy to that one object; publishing a - second template means covering both objects in one policy (the two must not - fight over `PutBucketPolicy`). Deferred to keep this PR's diff focused. ## Refreshing the AMI map @@ -404,16 +404,17 @@ commit if anything changed. ## Publishing to S3 -CloudFormation's `templateURL` accepts only S3 URLs, so the template lives -at `s3://defang-agent-box/template.yaml`. `.github/workflows/publish-template.yml` -handles the upload on every push to `master` via GitHub OIDC (no static AWS -keys). +CloudFormation's `templateURL` accepts only S3 URLs, so the templates live +at `s3://defang-agent-box/lightsail-template.yaml` (the default Launch +buttons) and `s3://defang-agent-box/template.yaml` (the EC2 alternative). +`.github/workflows/publish-template.yml` uploads both on every push to +`master` via GitHub OIDC (no static AWS keys). ### Prerequisites (forking this repo) The workflow is self-bootstrapping - it upserts the bucket, its -public-access configuration, and an `s3:GetObject` policy scoped to -`template.yaml` (the only object in the bucket) every run. The module itself +public-access configuration, and an `s3:GetObject` policy scoped to the two +template objects (the only objects in the bucket) every run. The module itself is fetched by the box direct from `raw.githubusercontent.com` at first boot; that host is dual-stack, so an IPv6-only box needs no NAT64. It reads all deploy config from **repo-level Actions variables** (Settings > Secrets and @@ -422,7 +423,7 @@ variables > Actions > Variables). None are secrets; they're just fork-specific. | Variable | Required | Purpose | | --- | --- | --- | | `AWS_ROLE_ARN` | yes | IAM role assumed via OIDC. Trust policy must allow the GitHub environment named in `AGENT_BOX_ENVIRONMENT`. | -| `AGENT_BOX_BUCKET` | yes | S3 bucket name to publish `template.yaml` into. Global namespace. | +| `AGENT_BOX_BUCKET` | yes | S3 bucket name to publish the templates into. Global namespace. | | `AGENT_BOX_ENVIRONMENT` | no | GitHub Actions environment name. Defaults to `defang-agent-box` (must be repo-scoped since env-scoped is a chicken-and-egg). Set to any name your role's trust policy accepts. | | `AWS_REGION` | no | Region for the bucket + AWS API calls. Defaults to `us-east-1`. | diff --git a/docs/index.html b/docs/index.html index 5d2fca3..3b7a94a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -159,7 +159,7 @@

Keeps your keys where you put them

Honest cost

-

Runs on a Spot instance with no network fees by default: a small box is ~$7–10/month all-in, the default size ~$16–20/month, on-demand a bit more. Terminate anytime to stop charges.

+

One flat monthly price — compute, disk, a stable address, and traffic all included, nothing metered on top: $12/month for the default size, $7/month for the smallest. Delete anytime to stop charges.

@@ -172,26 +172,26 @@

1-click AWS launch

N. Virginia
us-east-1
- Launch → + Launch →
Oregon
us-west-2
- Launch → + Launch →
Frankfurt
eu-central-1
- Launch → + Launch →
Ireland
eu-west-1
- Launch → + Launch →
- Set a password you'll remember. On the AWS form, type any 16–64 character password into WebPassword — that's what you'll use to log into the browser terminal. AWS masks it with dots after you type it and never shows it again, so save it somewhere before you launch. Choose Claude Code or Codex, pick a size, rename the stack if you want a friendlier Claude app label, and launch. The box takes about 2–3 minutes to start; your browser link then appears under Outputs in the AWS console — open it, enter the password, finish the selected agent's one-time sign-in, and you're in. (Claude Code stacks can drive the box from the Claude desktop / mobile app via Remote Control after the first browser login — the session name is <UserName>@<stack name>, agent@agent-box by default.) + Set a password you'll remember. On the AWS form, type any 16–64 character password into WebPassword — that's what you'll use to log into the browser terminal. AWS masks it with dots after you type it and never shows it again, so save it somewhere before you launch. Choose Claude Code or Codex, pick a size, rename the stack if you want a friendlier Claude app label, and launch. The box takes about 10–20 minutes to set up the first time (it installs NixOS on itself before the terminal comes up); your browser link then appears under Outputs in the AWS console — open it, enter the password, finish the selected agent's one-time sign-in, and you're in. (Claude Code stacks can drive the box from the Claude desktop / mobile app via Remote Control after the first browser login — the session name is <UserName>@<stack name>, agent@agent-box by default.)
- The optional ~$3.60/month, explained. By default the box skips the one thing AWS charges for on the network side — a public IPv4 address — so connectivity costs $0/month, using a free IPv6 address instead. That works fine if the networks you connect from support IPv6 (most home connections do), but offices and public Wi-Fi often don't. Checking PublicIpv4 on the AWS form adds a stable IPv4 for ~$3.60/month. The box itself reaches IPv4-only sites either way: in IPv6-only mode, traffic to them — notably github.com — goes through a free public NAT64 relay (nat64.net). TLS and SSH stay end-to-end encrypted, but if you'd rather not route through a third party, launch with Nat64 unchecked or pay for the IPv4. + Prefer EC2? The default above runs on AWS Lightsail for one flat monthly price. An EC2 variant — Spot pricing, IPv6-only networking, resizable disk — is available in the repository.
@@ -213,36 +213,5 @@

Other ways to run

- -