id: cost
title: What a deployment costs its operator, broken out by service
status: open
crates: []
dependsOn: [deploy]
exitCriterion: >
An operator can name, for one deployment, the estimated monthly cost of
every AWS service infra/pds/ provisions, the marginal cost of one more
account, and the marginal cost of one account that goes viral — each figure
labelled measured or estimated, and cited to its source. #
cost #
The owner asked for a cost model, broken out by service, benchmarked against
AWS. Nothing has run on real AWS yet — infra/pds/ has never been applied — so
every figure in this epic starts as an estimate, sourced from AWS's public
pricing pages rather than a bill, and stays labelled that way until a real
deployment's Cost Explorer says otherwise. An estimate presented as measured
is worse than a gap, because it gets planned against.
What infra/pds/ provisions #
Confirmed against the tree as of this writing, not guessed: one EC2 instance
(infra/pds/ec2.tf, t3.small by default) with two gp3 EBS volumes — a 20 GB
root volume and a 20 GB data volume holding the write-ahead log and blob
store — an Elastic IP, a Route53 hosted zone (infra/pds/route53.tf) and nothing
inside it (the server populates records itself at runtime), an IAM role and
instance profile (infra/pds/iam.tf) with SSM read, ECR pull and Route53 write
grants, a dedicated KMS key for the SSM SecureString parameters, a security
group open only on 443, an AWS Backup vault and plan
(infra/pds/backup.tf) covering the data volume, and an ECR repository for the
container image. There is no ALB and no ACM certificate: plan/deploy.md
records that the server terminates TLS itself over ACME DNS-01, so
certificate issuance is free and the load balancer's cost — and its data
transfer surcharge — is simply gone from this design.
Per-service estimate, at rest #
"At rest" means one idle deployment: the instance running, the zone existing,
no accounts, no requests beyond health checks. Every price below is AWS's
published on-demand rate for us-east-1, checked in August 2026; none of it
has been confirmed against an actual invoice.
| Service | Rate | This deployment's default | Monthly estimate |
|---|---|---|---|
| EC2 instance hours | t3.small on-demand, ~$0.0208/hr (AWS EC2 pricing; rate itself not readable from that page's rendered text, cross-checked against third-party trackers that quote it) |
730 hrs/month | ~$15.18 |
EBS, gp3 |
$0.08/GB-month (AWS EBS pricing; exact figure from third-party aggregation, AWS's own page states the formula but not this region's number in fetchable text) | 20 GB root + 20 GB data = 40 GB | ~$3.20 |
| Public IPv4 address (the Elastic IP) | $0.005/hr per address, charged since February 2024 even while attached to a running instance — this is not the pre-2024 "only when idle" rule infra/pds/ec2.tf's own comments do not mention |
1 address, attached | ~$3.65 |
| Route53 hosted zone | $0.50/month for each of the first 25 zones (AWS Route53 pricing) | 1 zone | $0.50 |
| Route53 queries | $0.40 per million standard queries (first 1B/month) | negligible at rest | ~$0.00 |
KMS key (aws_kms_key.ssm) |
$1/month per key, plus $0.03 per 10,000 requests | 1 key, few requests | ~$1.00 |
| ECR storage | $0.10/GB-month, after 500 MB/month free for a customer's first year | one image, likely under 1 GB compressed | ~$0.00–0.10 |
| AWS Backup, EBS warm storage | $0.05/GB-month of backup data, not source-volume size — daily incrementals against a 20 GB data volume, kept 30 days | not estimated: depends on daily write-ahead-log churn, which no real deployment has produced yet | not estimated |
| Data transfer out | 100 GB/month free (account+region aggregate), then $0.09/GB up to 10 TB/month | at rest: negligible | ~$0.00 |
Summing the rows that do not depend on traffic or on data actually written — instance, EBS, the EIP, the hosted zone, and the KMS key — gives roughly $23 a month to keep one idle, empty deployment running. That number excludes backup storage (unmeasured, see above) and data transfer (covered next, because it is not really an "at rest" cost at all).
The cost driver specific to this design #
zone-scale already names the shape: "did:web resolution
has nothing in front of it. Every resolution by every stranger is an HTTPS
request to this server for a distinct hostname. did:plc has a directory
absorbing that load and this method has none, so read volume scales with how
interesting the accounts are rather than with how many there are." That is a
capacity statement and a cost statement at once, and it is the most important
thing this epic has to say: an operator cannot cache this away. There is
no CDN or directory in front of the instance (see "what infra/pds/ provisions"
above — no ALB, nothing else in the request path), so every did.json fetch
is billed data transfer out, and every DNS lookup for an agent's hostname
that a client's resolver did not already cache is a billed Route53 query.
The marginal cost of one ordinary account is close to zero. Provisioning it costs no new AWS resource: its name comes from the existing pool (zone-scale), its record is one more entry in the hosted zone Terraform already created (Route53 bills per zone and per query, not per record), and an account nobody looks up generates no data transfer. Its only recurring cost is whatever trickle of resolution traffic it draws, which for an account nobody is following is small enough to round to nothing against the $23/month baseline above.
The marginal cost of one popular account is not the same number, and that
is the point. A did.json document is on the order of a kilobyte or two.
At the $0.09/GB post-free-tier rate, a million resolutions of one document
cost under a dollar in data transfer — cheap per-resolution, but the
multiplier is a popularity number the operator does not control and cannot
predict, because it tracks how widely that agent's posts circulate, not
how many accounts the operator runs. An agent whose activity gets reposted
into a large timeline can draw resolution volume that dwarfs the rest of the
deployment's traffic combined, and unlike a cache-fronted resolution scheme,
every one of those requests reaches this instance and this instance's bill.
The 100 GB/month free tier absorbs tens of millions of document-sized
resolutions before the metered rate even starts, which is worth stating
plainly so this reads as a real but distant risk rather than an alarm: it
becomes a line item at genuinely viral resolution volumes, not at the scale
of a handful of interested followers. What is missing to turn "distant risk"
into a number an operator can act on is occupancy and request-rate data from
a real deployment, which does not exist yet.
What is measured versus estimated #
Nothing here is measured. infra/pds/ has never been applied against an AWS
account (see plan/deploy.md's open backup-restore item, which says the same
thing about restore: wired up, never run). Every rate above is AWS's public,
current list price; none of it accounts for savings plans, reserved
capacity, or a first-year free tier the SSM/ECR rows already flag. The one
thing this epic can promise is that a figure claimed here without a
citation, or claimed as measured without a bill behind it, is a bug in this
file.
How an operator sees their own cost #
This epic does not invent a third surface for cost. ops-dashboard
already shows live operational state to a signed-in owner, and
alerts already gives the server a channel to say something is
wrong, signed with its own key. A per-deployment cost figure — resolution
count, data transfer, backup growth — belongs as data on the first of those,
not a new page: the dashboard already reads bot.did.stats and the
session/account state that a cost estimate would need as inputs. Whether that
figure should be a live AWS Cost Explorer pull (a second AWS credential the
instance would need to hold) or a locally-computed proxy (bytes served,
resolution count, multiplied by the rates in this file) is open and belongs
to whichever of those two epics picks it up.
Done #
Nothing closed yet.