Speed and cost
Costs and idle observation
Treat cost figures as estimates and use the idle window as a prompt to sink compute manually.
Reviewed 2026-10-03
Chalupa provides cost context to help prevent forgotten compute. It is not a billing system and does not attempt to reproduce credits, taxes, or every provider adjustment.
Commands on this page assume:
export DEMO_CONFIG="/path/to/project/chalupa.yml"
The three clocks
chalupa preview separates three clocks for inference configs, offline and
without changing the config:
- Managed hours — hours the control plane observed signed activity; Chalupa's plan clock, never GPU hours or server hours. Preview reads only the local hosted-account cache and labels usage fresh or stale.
- Session — when Chalupa will try to stop compute:
session.expiresAfterMinutesafter up, plusidleShutdownMinuteswhen present. The local scheduler needs an awake, unlocked machine for expiry; withcloud.url, the configured console reaper handles expiry and idle for reported deployments, subject to the organization coverage described below. - Provider estimate — estimated hourly rate × full session length, plus
estimated retained storage per month:
inference.cache.sizeGbandpersist.sizeGbat an estimated $0.10/GiB-month. Unknown rates stay unknown; the provider invoice wins. Retained storage keeps billing after compute stops.
Cost surfaces
The Chalupa console estimates only the DigitalOcean resources it observes. A complete operating budget has additional provider-owned surfaces:
| Surface | Typical charge | Included in Chalupa estimates |
|---|---|---|
| DigitalOcean compute | Droplet runtime | yes |
| DigitalOcean block storage | Provisioned volume capacity | yes |
| Control-plane Postgres | Compute, storage, and data transfer | no |
| Hosted web runtime | Functions, builds, and bandwidth | no |
| file.cheap | Private object storage, operations, and transfer | no |
| Project dependencies | External APIs, queues, or databases used by the selected services | no |
These services have separate invoices, retention policies, and scale-to-zero behavior. Do not infer total platform spend from the compute and storage cards alone.
What Chalupa estimates
The view separates:
- Ephemeral compute: the approximate price of the size while the server exists.
- Persistent storage: volume capacity, including while compute is sunk.
- Month to date: observed cycles that intersect the current UTC month, using the observed rate for each deployment or size.
- Monthly projection: an extrapolation that changes when you sink a resource.
Every monetary figure is labeled estimated.
The default volume rate is 0.10 USD/GiB/month, matching DigitalOcean's
published Volume pricing
when this guide was reviewed. Override DO_VOLUME_USD_PER_GIB_MONTH if the
provider changes that rate.
Synthetic example
A demo environment can show:
Estimated compute $0.0417/h
Estimated storage $1.00/month
Estimated month to date $0.62
These numbers are illustrative, not a quote. Check current provider pricing before creating a server or volume.
Money formatting is adaptive, not fixed at two decimals: a whole-cent amount
like the storage or month-to-date figures above renders normally, but a
sub-cent rate — most hourly compute prices, like $0.0417 above — widens
until three significant digits are visible, down to a micro-dollar floor. A
nonzero estimate never silently rounds to $0.00.
Session receipts
Every launch and sink writes a timing ledger on your machine, next to the
launch history: ~/.chalupa/launch-history/<environment>.ndjson. Which phases
it holds depends on what ran:
- every launch records the offline gate, provisioning, the public address and the sink;
- SSH, cloud-init and the first compose start are recorded when the launch
also bootstraps the box for a console (
cloud.urlis set); - each readiness probe is recorded when you run
chalupa wait(orchalupa up --wait); - the supervised tunnel (
chalupa tunnel keep, also used bychalupa up --wait) records whether it opened, and why not when it did not; - the tunnel and your flows are recorded by
chalupa test, and any span you add withchalupa mark.
Every phase written during a session names that session, so a phase written
late within it (a timing read after the next step began) still lands in it.
Ledgers written before phases carried a session keep their file order. A
second down of a session a down already closed is not recorded at all: it
destroys nothing.
Each phase is tagged with the size and hourly price of the compute it ran on. Nothing in it leaves your machine unless the environment reports to a console, which then shows the same phases.
chalupa receipt # the last session
chalupa receipt --since 7d # every session this week, with a total
chalupa receipt --campaign latency # sessions launched with CHALUPA_CAMPAIGN=latency
chalupa receipt --json --redact # numbers only, safe to paste into a post
chalupa status --timeline --config "$DEMO_CONFIG" # the last session as a waterfall
chalupa box timeline --config "$DEMO_CONFIG" # the last session by stage, with the box's own facts
A receipt is one line, printed automatically when down and test finish:
demo · alive 40m · ready in 12m (long pole: cloud-init 7m10s) · flows 20m · wait/work 12m/20m · idle 8m50s · ≈$0.084 · retained $5.00/mo
A session that never became reachable says so instead:
demo · alive 15m · tunnel failed (supervisor-crash) after up 5m06s (long pole: cloud-init 2m23s) · wait/work 15m/0s · idle 10m · ≈$0.063 · retained $9.00/mo (data $3.00 + cache $6.00)
- alive runs from the moment provisioning started (the provider creates
the server inside it, so this is the conservative end) to the end of the
sink, the window the provider bills. When compute was destroyed by something
other than
down(the console's reaper, an idle shutdown, a manual delete), no sink closes the window: the receipt then readsalive ≥Xm (not closed by down)and≥$for the cost, a floor that stops at the last phase it saw or at the next session, whichever is first. - ready in runs from asking to the last launch phase finishing, and the
long pole is the single longest phase in it. It stops at the first flow
and at the first readiness set that passed, so a
chalupa waityou re-run before every round of a benchmark does not stretch it. "Ready" needs proof that the box answered from your machine: a readiness set that passed, a flow that started, or, when no readiness probe ran, a tunnel that opened. An open tunnel only means the local ports answer, so readiness probes that ran and never passed, or forwards the box kept refusing a minute after the tunnel opened, outweigh it. The end of cloud-init is not proof. Without it the receipt readsup in X, readiness not checked, or names what went wrong:tunnel failed (CLASS)ornot ready (probes failed). The classes aresupervisor-crash(the tunnel supervisor itself exited),sshd-throttled(the box's SSH server refused new connections),timeoutandtarget-not-listening(the forward reached the box but nothing listened on the port, again and again), in that order when several apply; see Troubleshooting. Only the first tunnel that opened counts toward ready in: reopening it later in the session does not stretch the figure. - flows is the time your own spans named
flowscovered; idle is the alive time that was neither launch nor flows. - wait/work splits the session from your side: wait runs from asking to the first piece of work (flows, or a span your harness marked), plus any rebuild, swap or probe re-run after it; work is the flows and marked spans. A session where nothing ran is all wait.
- ≈ applies the provider's billing granularity: DigitalOcean bills per second with a one-minute minimum, Hetzner bills every started hour, so a twenty-minute Hetzner box costs a full hour.
- retained is what keeps billing after the sink, at the estimated storage
rate: the persistent data volume plus the disposable image cache volume
(
<environment>-cache) when the launch attached one, split as(data $X + cache $Y).
A harness times its own phases with chalupa mark, which only appends to the
local ledger:
chalupa mark flows --start --label round=2 --config "$DEMO_CONFIG"
# ... run the flows ...
chalupa mark flows --end --label round=2 --config "$DEMO_CONFIG"
Some span names mean something to the receipt and the box timeline. Mark them from your harness when it builds images or runs flows itself:
chalupa mark image-build --start --label image=api --config "$DEMO_CONFIG"
chalupa mark image-build --end --label image=api --outcome ok --config "$DEMO_CONFIG"
chalupa mark apps-up --start --config "$DEMO_CONFIG" # and --end once the apps listen
chalupa mark first-flow --point --config "$DEMO_CONFIG" # an instant: the first flow began
chalupa mark flows --start --config "$DEMO_CONFIG" # and --end after the last one
Without them a 30-minute image build reads as idle time. Image and service
names stay on your machine: a console receives them as ordinals (img-1,
svc-1).
Box timeline
chalupa box timeline shows one session stage by stage: compute, cache,
infra, tunnel, apps, work and release. It runs offline: it reads the local
ledger and the snapshot down takes of the box before destroying it, and
never opens SSH or calls the provider. From the snapshot it adds what only the
box knows: when each container started (and whether it restarted or was
OOM-killed), when the box's own health gate passed, whether its SSH server
throttled connections or refused forwards to ports nothing listened on, and
units stuck in a restart loop. The provider's boot, between provisioning and
cloud-init, also gets its own span.
chalupa box timeline --config "$DEMO_CONFIG" # the newest session
chalupa box timeline --config "$DEMO_CONFIG" --session 2026-10-03T02:24 # one session by its start
chalupa box timeline --config "$DEMO_CONFIG" --json # box-timeline v1
chalupa box timeline --config "$DEMO_CONFIG" --backfill # keep the box facts of past sessions
Only the newest five snapshots are kept per environment. down saves the box
facts of the session it closes next to the ledger
(<environment>.derived.ndjson); --backfill does the same for older closed
sessions whose snapshot is still on disk. Running it twice adds nothing, and it
refuses while the newest session is still open.
Prices come from the provider's list prices when a launch could read them,
cached in ~/.cache/chalupa/prices.json, and otherwise from Chalupa's offline
reference table. A size neither knows prints price unknown; a receipt never
shows a guessed or zero price.
On an environment that reports to a console, the environment page shows the latest session as a waterfall with its receipt, a time-to-ready trend across sessions, and an estimated cost beside each test run whose window is known.
Sunk environment
When a deployment ends:
- its estimated hourly compute cost becomes zero;
- its historical month-to-date estimate remains;
- persistent storage can continue to incur estimated cost;
- the total must not automatically be described as zero.
This distinction prevents the false impression that chalupa down deletes the
data volume.
How estimates are updated
A scheduled poll queries Chalupa-tagged resources with a read-only credential. It associates the observed size, volume, and deployment without using the token that can create or delete resources.
Updates are not instantaneous. The capture timestamp shows how fresh an estimate is. A short-lived deployment that starts and ends between two inventory polls might never provide a rate and can be absent from the month-to-date estimate. The provider invoice remains authoritative.
Configure idle observation
idleShutdownMinutes: 45
This value makes the agent report an idle window. It never gives the server a deletion credential. When the external control-plane reaper and its dedicated write credential are configured, it also supplies the persisted deadline for automatic compute shutdown.
Omitting the value disables idle observation. The schema accepts values from 5 to 10,080 minutes.
How the countdown is derived
The agent reports:
idleChecks;checkIntervalSeconds;limitMinutes;- the latest activity time, when available.
The console calculates remaining minutes from these fields. The value is approximate because heartbeats, checks, and UI rendering do not occur at the same instant.
What counts as activity
The v1 implementation observes established SSH sessions. An active tunnel resets the counter; closing it begins consecutive idle checks.
This does not measure business activity inside each service. Remote work that does not use SSH may require a different policy or a larger window.
What is destroyed by idle observation
With no external reaper configured, the console only shows a warning and the operator decides when to run:
chalupa down --config "$DEMO_CONFIG"
For reported deployments (those with cloud.url and an ingest key), the
configured console reaper destroys compute at session expiry as well as at
the idle deadline. Customer organizations opt in with their own
DigitalOcean or Hetzner token, matching the deployment's own provider;
without one, they still rely on the local scheduler. A DO credential never
acts on a Hetzner deployment or vice versa. Session expiry wins
over fresh heartbeats, an open
SSH tunnel, and a live CI suite. Live CI still defers idle shutdown.
The worker uses a dedicated write credential, refuses compute without a Chalupa tag, and never targets the protected data stack. A failed provider call leaves the deployment active so the next scheduled tick can retry.
This is not a hard spend cap. Cron delays, missing reports, and provider or control-plane failures can extend billable runtime beyond the deadline. Persistent storage continues billing after compute is destroyed.
For separately configured demo or test sessions, an explicit, operator-local
expiry runner can call the same chalupa down path after rechecking a registered
config digest and provider ID. It is independent from the idle countdown, has
no Vercel or server credentials, and cannot target the data stack.
Arm the console reaper
For DigitalOcean, chalupa setup --provider digitalocean (with or without --inference) asks whether
to arm the console reaper. A later chalupa up on a linked BYOC environment prints a one-line
warning when the reaper is not armed. A new BYOC GPU launch is the one blocking
case: if the console confirms the reaper is not armed and the local teardown
schedule is not installed and loaded, Chalupa refuses before starting the host
and names the hourly rate. Arm the reaper, install the local schedule, or
re-run with --accept-local-expiry to accept that expiry then depends on this
machine staying awake, online, and unlocked. Supply a DigitalOcean custom token with only droplet:read and
droplet:delete scopes.
The token can read and delete droplets throughout that account. Chalupa
restricts its use to destroying Chalupa-tagged droplets registered to your
organization after session expiry or an idle deadline. It does not delete
persistent volumes.
For Hetzner, chalupa setup --provider hetzner offers the same arming once
the console account is linked (no --inference needed — GPU inference stays
DigitalOcean-only). Hetzner API tokens are project-wide and cannot be
scoped: a token can read and delete every server in its project, not just
Chalupa's. Use a Hetzner project dedicated to Chalupa, and generate a Read &
Write token from it, so the blast radius of a leaked or misused token stays
bounded to Chalupa's own compute. Chalupa restricts its use to destroying
servers labeled chalupa=true (or carrying a chalupa-env label) registered
to your organization.
Chalupa stores either provider's token encrypted at rest with AES-256-GCM
under CHALUPA_INGEST_MASTER_KEY and never shows it again. The environment's
idle window card shows Console reaper: armed or not armed for the
deployment's own provider. Opt-in works while your laptop sleeps, provided
the deployment reports to the console.
Revoke with DELETE /api/account/provider-token?provider=digitalocean or
?provider=hetzner using an account session bearer, or delete the token at
the provider to revoke it instantly. Console revocation stops future sweeps;
an already-started provider request may finish. A 401/403 from either
provider marks that provider's credential invalid and records a provider
warning — a Hetzner token that can list servers but lacks write access still
passes arming and is only caught this way, the first time the reaper tries to
delete a server with it. Replace the credential to arm the reaper again;
Chalupa never falls back to another organization's credential, or to the
other provider's credential for the same organization.
This remains a best-effort shutdown, not a billing cap.
Recommended practice
Use the signal as operational discipline:
- configure a reasonable window;
- keep the informational warning visible;
- run
chalupa downwhen your work ends; - verify that the environment appears sunk;
- review retained storage separately;
- review Postgres, hosted runtime, and artifact-storage usage in their own provider consoles.