The numbers behind ShipRust
How we estimated 44–88 engineering hours, counted 179,000 tokens of code and tests, and benchmarked a generated ShipRust app.
Before asking someone to pay $999 for a starter, we wanted to make the work it contains visible. We looked at engineering effort, source size and runtime performance. Those numbers answer different questions: effort is an estimate, while source size and performance come from measurements. All figures below were compiled on October 11, 2026, using the dated snapshots below. The headline figures cover the customer template. A separate section at the end measures the ShipRust generator site; those results are not part of the template claims.
The headline figures are 44–88 estimated engineering hours, 178,944 source tokens, 80,000+ homepage requests per second, and 20,000+ signed-in dashboard requests per second in our local benchmark. Here is what went into each figure, and what it does and does not tell you.
- Engineering effort
- Your time calculator
- Token size and API costs
- Runtime performance and raw results
- Generator API measurements
Engineering effort
Our planning estimate is 44–88 engineering hours to rebuild the listed capabilities together, starting with a blank app. We assume one experienced Rust web developer using AI for scaffolding, implementation and test generation, familiar with Axum, Postgres and the providers involved.
This is an engineering judgment based on the work present in the template, not recorded development time, a customer study, or a guaranteed saving. Each upper bound is twice its lower bound to allow for debugging and integration work. This is a planning allowance, not a statistical confidence interval. The hours describe active engineering work, not elapsed calendar time.
| Capability | Architecture | Implementation | Testing | Shipping | Total |
|---|---|---|---|---|---|
| Authentication and accounts | 2–4 h | 5–10 h | 4–8 h | 1–2 h | 12–24 h |
| Stripe billing | 1–2 h | 2–4 h | 2–4 h | 1–2 h | 6–12 h |
| Files and vector search | 1–2 h | 2–4 h | 1–2 h | 1–2 h | 5–10 h |
| Background jobs | 0.5–1 h | 1.5–3 h | 1.5–3 h | 0.5–1 h | 4–8 h |
| Analytics and monitoring | 0.5–1 h | 1.5–3 h | 1–2 h | 1–2 h | 4–8 h |
| API and MCP | 1–2 h | 2–4 h | 1.5–3 h | 0.5–1 h | 5–10 h |
| Blog and documentation | 0.5–1 h | 1.5–3 h | 0.5–1 h | 0.5–1 h | 3–6 h |
| App foundation and deployment | 1.5–3 h | 1.5–3 h | 1–2 h | 1–2 h | 5–10 h |
| Total | 8–16 h | 17–34 h | 12.5–25 h | 6.5–13 h | 44–88 h |
Architecture covers the data model, boundaries and failure cases. Implementation covers working routes, UI and provider integration. Testing covers success, authorization, error and retry behavior. Shipping covers configuration, documentation and smoke checks for that capability. Shared Docker and CI work appears only in the app-foundation row.
The scope behind each row:
- Authentication and accounts: email verification, password hashing, sessions, passkeys, two-factor recovery, account settings and admin access.
- Stripe billing: hosted Checkout, recurring and one-time prices, portal, webhook verification and local access state. No bespoke invoicing or tax engine.
- Files and vector search: local/S3 storage, avatar validation, Qdrant per-user filtering and cleanup. Embedding generation is outside the template.
- Background jobs: a durable Postgres queue, retry handling, worker lifecycle and operator inspection. Product-specific jobs still need writing.
- Analytics and monitoring: the included PostHog/Sentry capabilities, browser instrumentation and local collector tests.
- API and MCP: personal keys, bearer authentication, JSON errors, protocol handling and the example account endpoint/tool. Your product API is additional.
- Blog and documentation: Markdown rendering, navigation, RSS, metadata, sitemap and indexing notifications. Writing your content is additional.
- App foundation and deployment: Axum/Maud app structure, Postgres migrations, responsive shell, Docker, CI and deployment configuration.
The estimate assumes these capabilities share one app foundation. It is not the sum of eight independent projects. Selecting fewer capabilities reduces the work you avoid; existing internal starters and libraries can reduce it further. Learning Rust, custom product design, external security audits, production data migrations and ongoing maintenance are excluded.
Buying ShipRust still leaves setup and customization work. You configure providers and deployment, review the code for your needs, and build your product. The 44–88 hour range estimates a rebuild; it does not subtract your adoption work or claim that every buyer saves those hours. We have not timed customer adoption.
Your time calculator
The landing-page calculator uses your assumptions:
Value of time = hours saved per project × hourly cost × number of projects
Value after purchase = value of time − $999
Break-even time across all projects = $999 ÷ hourly cost
The default example uses 20 hours saved, $100/hour, and one project: $2,000 of engineering time, or $1,001 after the lifetime price. The 20 hours and $100 rate are illustrative inputs, not observed averages. Enter net hours you expect to avoid after setup, customization and reuse of tools you already have. The calculator rounds break-even time up to a tenth of an hour so it never understates the threshold. It counts the license once.
Engineering capacity freed up is not necessarily cash saved. Provider fees, hosting, taxes and changes in your product's revenue are outside this arithmetic. Reusing the starter across products does not mean every project saves equal time; use an average appropriate to your work.
Token size and API costs
We counted tracked .rs, .js, .cjs, .css and .sql files under template/,
including inline tests and separate test files. The snapshot contains 85 files
and 178,944 tokens using tiktoken's o200k_base reference encoding.
Source files, including inline tests, account for 139,046 tokens; separate
test files account for 39,898 tokens.
The count excludes the site, generator, Markdown documentation, manifests, lockfiles, SQLx metadata, dependency source, build output and binary assets. It includes optional feature code whether or not a buyer enables that feature. It is a source-size comparison, not either provider's exact billing count. The count manifest records the source revision, template tree, tokenizer version, file hashes and counts.
At the standard API rates checked on October 11, 2026:
| Model | Uncached input / million tokens | Output / million tokens | One input pass | One output pass |
|---|---|---|---|---|
| GPT-6 Astra | $10 | $50 | $1.79 | $8.95 |
| Claude Fable 5.1 | $10 | $50 | $1.79 | $8.95 |
Sources: OpenAI model pricing and Anthropic model pricing. These are standard list rates, excluding caching, batch/priority adjustments, long-context premiums, subscriptions, taxes and tool fees. Prices can change.
The arithmetic is 178,944 ÷ 1,000,000 × rate, rounded to cents. An output
pass is an equivalent amount of text, not a claim that one model request can
generate the whole project. Rebuilding also involves reasoning tokens, repeated
prompts, tool results, discarded code and revisions. Cached reads have different
rates. Source size does not reveal how much of any of those a rebuild requires.
We have not measured total development-token cost or customer token savings. The count shows how much existing code and test text your agent can start from.
To repeat the count on your current checkout, install the manifest's tiktoken
version and run:
python3 scripts/estimates/count_tokens.py > /tmp/template-tokens.json
The script reads local files and does not send code to a model API. Compare the template tree and file hashes with the manifest to confirm the same snapshot. Running it against a newer template may produce a different count.
Runtime performance
On October 10, 2026, we generated a new project through the hosted ShipRust
service, selected every optional capability, and built the downloaded code in
release mode. These measurements describe that starter app at template revision
fbe1a54. They are a local baseline, not a production capacity guarantee or a
comparison with other frameworks.
This study predates the Qdrant option; vector search is not included in these measurements. Results remain pinned to the revision stated above.
All optional features were compiled; external integrations were unconfigured. Stripe, Mailgun, Sentry and PostHog had no credentials, so no provider calls or telemetry exports ran during load. File storage used local disk. The built-in job worker remained enabled with an idle queue. Browser JavaScript was not executed by the load generator.
Throughput and latency
Each route ran three times for 30 seconds, with 32 concurrent HTTP/1.1 connections and keep-alive. Every scenario received a five-second warmup. The order was shuffled between repetitions. Throughput is reported as minimum / median / maximum; latency columns show the worst p95 and p99 among the three runs, not pooled percentiles.
| Route | Requests/s, min / median / max | Worst p95 | Worst p99 |
|---|---|---|---|
| Public homepage | 88,194 / 101,760 / 111,999 | 0.69 ms | 1.50 ms |
| Documentation index | 61,977 / 94,652 / 106,836 | 1.61 ms | 3.25 ms |
| Signed-in dashboard | 22,061 / 23,524 / 28,107 | 3.30 ms | 7.23 ms |
| Authenticated JSON API | 8,488 / 8,725 / 9,572 | 14.34 ms | 24.73 ms |
| Authenticated MCP tool | 11,547 / 11,563 / 16,242 | 6.08 ms | 15.02 ms |
| Health check (reference) | 78,554 / 99,965 / 109,344 | 0.92 ms | 2.96 ms |
Public HTML routes render the page without querying Postgres. Dashboard
requests resolve a real session through an indexed database query, then render
HTML. The API and MCP routes authenticate a personal API key against Postgres;
MCP also parses JSON-RPC and executes get_account. The health route is a
small-response reference only; it is not the basis of our landing-page claim.
The 18 baseline runs completed 29,798,333 requests, all HTTP 200, with zero transport errors or timeouts. Content and authentication were checked before load; oha counts status codes during load, rather than asserting every body.
These are saturated, closed-loop runs: each connection waits for its previous response. Their latency can understate queueing during a traffic surge. We also tested explicitly paced requests with oha's latency correction, at half the slowest baseline throughput for each route.
| Route | Offered requests/s | Achieved requests/s | Corrected p95 | Corrected p99 |
|---|---|---|---|---|
| Public homepage | 44,097 | 44,092 | 1.35 ms | 3.35 ms |
| Signed-in dashboard | 11,030 | 11,029 | 1.83 ms | 2.06 ms |
| Authenticated JSON API | 4,243 | 4,124 | 1.35 ms | 259.39 ms |
| JSON API repeat 2 | 4,243 | 4,243 | 1.11 ms | 1.48 ms |
| JSON API repeat 3 | 4,243 | 4,243 | 1.12 ms | 1.55 ms |
Each paced run lasted 30 seconds and returned only HTTP 200, with no transport errors. The first paced API run had a 259 ms p99 and achieved less than its offered rate. We retained it and repeated that case twice. These results do not support a blanket sub-millisecond latency claim. They do not isolate the cause of the long tail on this shared machine.
Concurrency and sustained load
Single 15-second follow-up runs (not repeated estimates):
| Route | 1 connection, requests/s | 128 connections, requests/s |
|---|---|---|
| Public homepage | 21,267 | 132,849 |
| Signed-in dashboard | 8,315 | 30,805 |
| Authenticated JSON API | 5,309 | 17,895 |
A 180-second dashboard soak at 32 connections completed 5,045,305 requests at 28,029 requests/s, with p95 1.43 ms, p99 2.43 ms, and zero HTTP errors or timeouts. Sampled app RSS went from 35.73 MiB to 33.86 MiB. Three minutes is a short soak, not proof of long-term memory stability.
Memory and build size
| Baseline route | Peak sampled app RSS | Average app CPU, cores |
|---|---|---|
| Public homepage | 24.25 MiB | 3.26 |
| Documentation index | 24.06 MiB | 2.88 |
| Signed-in dashboard | 24.23 MiB | 2.27 |
| Authenticated JSON API | 24.16 MiB | 1.49 |
| Authenticated MCP tool | 23.98 MiB | 2.38 |
| Health check (reference) | 23.81 MiB | 2.83 |
Across all native-Postgres runs, including the higher-concurrency tests, the highest sampled app RSS was 40.52 MiB.
RSS is the app process's resident memory, sampled once per second. It excludes Postgres, the load generator, browser and operating system; short peaks between samples may be missed. CPU usage is process CPU time divided by wall time, expressed as cores occupied on average. It excludes the database and client, and does not imply linear scaling per core. The stripped macOS ARM64 release binary was 10,116,976 bytes (9.65 MiB). Linux images and binaries built with debug symbols will have different sizes.
Test conditions
- Apple M2 Max, 12 CPU cores, 32 GiB RAM; macOS 26.5.2.
- Rust 1.99.0,
aarch64-apple-darwin; the generated release profile: optimization level 3, fat LTO, one codegen unit, aborting panics and stripping. - Postgres 18.4 running natively in a dedicated local cluster with default settings. The app's unchanged pool allows 16 connections.
- oha 1.16.0, 12 load-generator worker threads, five-second request timeout, waiting for in-flight requests at the end of each run.
- App, database and load generator on the same developer laptop. HTTP over loopback, no TLS, reverse proxy, CDN or compression. Desktop background activity and unrelated builds were not controlled; no benchmark-owned compilation ran during load. These are not isolated peak-throughput measurements.
- One synthetic account, one session and one API key reused throughout, with warm database caches. No large tables, tenant diversity, mixed writes or password hashing in the throughput scenarios.
An earlier warmup used a shared Docker Postgres instance. Docker became unavailable and authenticated requests returned timeouts/500s. That experiment was stopped, retained separately in the evidence, and excluded from the reported native-Postgres series. All repetitions of the new series are included.
Reproduce the measurements
Generate an all-options project, retain .shiprust.toml, and follow its setup
instructions with a disposable local Postgres database. Leave external-provider
credentials blank to match this baseline. Bind to 127.0.0.1:38080, set
SITE_URL=http://127.0.0.1:38080, and disable IndexNow. Build and run the normal
application binary:
SQLX_OFFLINE=true cargo build --release --locked
# Run target/release/<your-project-slug> with the local environment.
With oha 1.16.0, warm each route before three repetitions:
oha --no-tui --output-format json --http-version 1.1 \
--disable-compression -w -t 5s -c 32 -z 5s http://127.0.0.1:38080/
oha --no-tui --output-format json --http-version 1.1 \
--disable-compression -w -t 5s -c 32 -z 30s http://127.0.0.1:38080/
Use /docs for the content scenario. For /dashboard, supply a valid local
session with -H "Cookie: $BENCH_COOKIE". For /api/v1/me, use
-H "Authorization: Bearer $BENCH_API_KEY". These must be credentials from
your generated app, not the hosted generator. For MCP, POST a JSON-RPC
tools/call for get_account, with the same app key and protocol version
2025-06-18. Verify account content and isError before measuring; a redirect,
HTTP 200 carrying a tool error, or an HTML error page is not a successful sample.
For paced runs, add -q RATE --latency-correction; use half your own lowest
baseline throughput. Repeat with -c 1 and -c 128 for concurrency checks,
and -z 180s for the dashboard soak. Inspect every status and transport error,
retain slow runs, and avoid publishing only the fastest result.
Download the full measurement evidence (JSON), including raw oha output, resource samples summarized per run, environment, checksums and the interrupted Docker experiment.
Functional validation and scope
The downloaded project passed 127 Rust tests and 17 JavaScript tests, plus formatting, Clippy, SQLx metadata and offline compilation checks. A local HTTP signup test used the bundled mail double, required the verification code, and created a session and personal API key through the app's actual routes. Browser checks covered password sign-in and account settings; HTTP smoke checks covered public content, authorization and avatar upload/read/removal. The measured application source was unchanged; the seeding harness was a separate test target. Cargo only reordered lockfile entries without changing the dependency graph.
Provider integrations are tested with local doubles/collectors, not live payments, delivery or telemetry ingestion. Passkey coverage uses software test authenticators, not a physical security-key browser ceremony.
Use these figures to assess the starter's baseline overhead. Your data, queries, enabled telemetry, provider calls, network, TLS and deployment hardware will change the result. We have not measured concurrent human users, hosting cost per request, energy consumption, full browser page-load speed, or scaling across multiple replicas. Test a representative workload on your intended infrastructure before choosing capacity.
Generator API: creating projects and downloading ZIPs
On October 11, 2026, we separately measured the local ShipRust generator
site, revision 1999d45, serving template 46f1228. These are different
endpoints from the generated app's GET /api/v1/me above. The site ran its
stock release binary against a fresh native Postgres database on the same
M2 Max laptop, with the same 16-connection pool limit and external providers
unconfigured.
POST /api/v1/projects authenticates a bearer key, checks the account's
entitlement, validates all selected options, inserts a project row, and returns
HTTP 201 with the project metadata. It does not generate the source ZIP.
GET /api/v1/projects/{id}/download authenticates and checks access, then
renames and compresses the embedded template and transfers the ZIP. Generation
uses the app's normal limit of two concurrent CPU jobs; extra requests wait.
Three repetitions per case; creation runs lasted 30 seconds and ZIP runs 15 seconds, after five-second warmups. The table reports min / median / max throughput and the worst p95/p99 across repetitions.
| Operation | Connections | Requests/s, min / median / max | Worst p95 | Worst p99 |
|---|---|---|---|---|
| Create project record | 1 | 2,879.91 / 2,965.98 / 3,070.88 | 0.48 ms | 0.62 ms |
| Create project record | 8 | 9,488.28 / 9,952.66 / 10,613.23 | 1.12 ms | 1.91 ms |
| Create project record | 32 | 11,661.32 / 11,760.73 / 11,981.60 | 3.49 ms | 4.66 ms |
| Generate and transfer ZIP | 1 | 71.46 / 71.76 / 72.58 | 14.49 ms | 18.80 ms |
| Generate and transfer ZIP | 8 | 153.83 / 154.32 / 154.48 | 53.09 ms | 58.78 ms |
Across all 18 runs, including warmups and the sustained run, 2,984,031 project creations returned HTTP 201 and 10,916 ZIP downloads returned HTTP 200, with zero HTTP errors, transport errors, timeouts or response validation failures. Independent database checks found exactly 2,984,031 unique all-options project rows. The project table and indexes grew to 1.56 GiB; the whole database reached 1.57 GiB. These are PostgreSQL relation/database sizes, not total cluster disk usage including WAL.
The final 60-second creation run at 32 connections committed 702,907 projects at 11,714/s, with p95 3.45 ms and p99 4.31 ms. This is a short sustained-write test, not a long-term capacity test.
Peak sampled app RSS across the entire generator series was 33.59 MiB, excluding Postgres, the load generator and operating system. During creation runs, app CPU averaged 0.46–3.64 cores. The single-process Node driver's highest run-average CPU usage was 0.65 cores. App RSS was sampled once per second, so brief peaks may be missed; app CPU deltas approximately span each driver's startup and run.
Each creation had a distinct name and slug, with CapRover, blog, docs, Stripe,
API, MCP, storage, and every Sentry/PostHog capability selected. Every response
was checked for status, options, unique project ID and links. After each run,
an independent SQL query checked the persisted row count, distinct slugs and
enabled options. Postgres kept fsync=on and synchronous_commit=on.
The fixture account had a synthetic local paid entitlement; signup, payment
processing and password hashing were outside the measured path.
ZIP runs regenerated a real project's archive on every request. The preflight archive contained 266 entries and about 375 KiB; all entry CRCs and the project manifest were checked before load. During load, the driver checked HTTP 200, content type, ZIP signatures, entry count and directory bounds, and consumed the full response. It did not recheck every entry's CRC during load.
The dependency-free Node 24.16.0 driver used HTTP/1.1 keep-alive, a ten-second request deadline, no retries, and one outstanding request per connection. It included JSON preparation and response validation in achieved throughput; latency spans request dispatch through full-body validation. One-second completion counts and load-generator CPU/memory are retained in the evidence. The app, Postgres and driver shared a developer laptop; desktop activity was not controlled, and no benchmark-owned compilation ran during load.
These are closed-loop observations, not a production capacity or latency SLO. The driver can limit throughput, and waiting for each response hides queueing under excess offered load. One account/key and sequential unique slugs are not a realistic multi-tenant workload. Rows accumulated throughout; we did not reset the table between repetitions. The measurements exclude TLS, reverse proxies, live telemetry, signup/payment provider latency, deployment, and compilation of downloaded projects. The ZIP figures describe source archive generation, not complete app builds.
Download the generator API evidence (JSON)
for every run, database verification, resource measurements, release-binary
checksum, PostgreSQL settings and archive preflight. Reproduction uses
scripts/benchmarks/generator.py and generator-load.mjs in the ShipRust
repository; the runner creates a new local database and never loads a hosted
ShipRust API key. It retains that database for inspection and stops its own
server after the experiment.