← All posts

· ShipRust

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

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.

CapabilityArchitectureImplementationTestingShippingTotal
Authentication and accounts2–4 h5–10 h4–8 h1–2 h12–24 h
Stripe billing1–2 h2–4 h2–4 h1–2 h6–12 h
Files and vector search1–2 h2–4 h1–2 h1–2 h5–10 h
Background jobs0.5–1 h1.5–3 h1.5–3 h0.5–1 h4–8 h
Analytics and monitoring0.5–1 h1.5–3 h1–2 h1–2 h4–8 h
API and MCP1–2 h2–4 h1.5–3 h0.5–1 h5–10 h
Blog and documentation0.5–1 h1.5–3 h0.5–1 h0.5–1 h3–6 h
App foundation and deployment1.5–3 h1.5–3 h1–2 h1–2 h5–10 h
Total8–16 h17–34 h12.5–25 h6.5–13 h44–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:

ModelUncached input / million tokensOutput / million tokensOne input passOne 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.

RouteRequests/s, min / median / maxWorst p95Worst p99
Public homepage88,194 / 101,760 / 111,9990.69 ms1.50 ms
Documentation index61,977 / 94,652 / 106,8361.61 ms3.25 ms
Signed-in dashboard22,061 / 23,524 / 28,1073.30 ms7.23 ms
Authenticated JSON API8,488 / 8,725 / 9,57214.34 ms24.73 ms
Authenticated MCP tool11,547 / 11,563 / 16,2426.08 ms15.02 ms
Health check (reference)78,554 / 99,965 / 109,3440.92 ms2.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.

RouteOffered requests/sAchieved requests/sCorrected p95Corrected p99
Public homepage44,09744,0921.35 ms3.35 ms
Signed-in dashboard11,03011,0291.83 ms2.06 ms
Authenticated JSON API4,2434,1241.35 ms259.39 ms
JSON API repeat 24,2434,2431.11 ms1.48 ms
JSON API repeat 34,2434,2431.12 ms1.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):

Route1 connection, requests/s128 connections, requests/s
Public homepage21,267132,849
Signed-in dashboard8,31530,805
Authenticated JSON API5,30917,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 routePeak sampled app RSSAverage app CPU, cores
Public homepage24.25 MiB3.26
Documentation index24.06 MiB2.88
Signed-in dashboard24.23 MiB2.27
Authenticated JSON API24.16 MiB1.49
Authenticated MCP tool23.98 MiB2.38
Health check (reference)23.81 MiB2.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.

OperationConnectionsRequests/s, min / median / maxWorst p95Worst p99
Create project record12,879.91 / 2,965.98 / 3,070.880.48 ms0.62 ms
Create project record89,488.28 / 9,952.66 / 10,613.231.12 ms1.91 ms
Create project record3211,661.32 / 11,760.73 / 11,981.603.49 ms4.66 ms
Generate and transfer ZIP171.46 / 71.76 / 72.5814.49 ms18.80 ms
Generate and transfer ZIP8153.83 / 154.32 / 154.4853.09 ms58.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.