F FourIA GitHub ↗

Cloudflare Memory Expense Report — Cutting Allocated-Memory Cost

On this page

Date: 2026-09-04 · Account: 35afea16440634aa2350331d2a736eec (Paso4) Scope: Last 31 days (2026-08-04 → 2026-09-04), sampled via the Cloudflare GraphQL workersInvocationsAdaptive and containersUsageAdaptiveGroups datasets, cross-checked against the Containers API and the deployment configs in this repo.


1. TL;DR

  1. Plain Workers (dispatch + tenant dispatch workers) are not the memory problem — their duration/CPU sit comfortably inside the included tier. Their enormous wall-time (≈19.6 days of open connections) costs $0 on the Standard usage model.
  2. The expense is Cloudflare Containers. Every sandbox (OpenClaw gateway) is billed for every 10 ms it runs, on provisioned vCPU/memory/disk of its instance type.
  3. Three cost levers, in order of impact:
    • Instance-type overprovisioning — several tenants are deployed at standard-1 (4 GiB) / standard-2 (6 GiB) while the platform default is basic (1 GiB) / lite (0.25 GiB). Right-sizing alone cuts memory billing ~44% (→ basic) or ~87% (→ lite).
    • max_instances = 3 on every app — including abandoned staging/test tenants. Provisioned capacity is 3× what most tenants need.
    • Runtime (SANDBOX_SLEEP_AFTER) — memory cost scales linearly with awake time; confirm every tenant worker actually has a sleep-after set (default is never).

2. Data: what the account actually spent

2.1 Plain Workers (workersInvocationsAdaptive, 31 days)

MetricTotal (month)Included (Standard)Overage
Requests28,94010,000,0000
CPU time112,284 ms30,000,000 ms0
Duration (GB·s)29,877 GB·s400,000 GB·s0
Estimated extra cost$0.00

Breakdown by named worker (duration, i.e. GB·s):

WorkerDuration (GB·s)Wall timeRequests
fouria-dispatch8,385849,802 s (≈9.8 d)11,530
moltbot-sandbox-e2e-1787041788-…26121,733 s300
moltbot-sandbox-e2e-1786969669-…463,706 s250
moltbot-sandbox-e2e-33785097313-1-base362,909 s30
fouria-dispatch-staging1296,087 s950
mila-gemini-proxy0.51,724 s1,120
__unknown__ (dispatch-tenant + sandbox e2e)~21,135~18.7 d~14,760

Key observation: 28,940 requests generate 112 CPU-seconds but 1.69 M seconds of wall time — the workers are alive almost entirely to hold idle WebSockets open. On the Standard usage model duration is not billed, so this is free today, but it becomes expensive the moment these are moved to unbound-style billing or Containers keep them warm. Fixing idle-connection handling also shrinks container awake time (see §4.3).

2.2 Containers (containersUsageAdaptiveGroups / containersMetricsAdaptiveGroups, 31 days)

Billing model: billed for every 10 ms actively running, on provisioned resources.

ComponentTotal (month)Free tierEst. cost
Memory4,242,369 GiB·s (1,178 GiB-h)25 GiB-h$10.38
CPU (allocated vCPU)582,382 vCPU·s (9,706 vCPU-min)375 vCPU-min$11.20
Disk9,305,172 GB·s (2,585 GB-h)200 GB-h$0.60
Total (this month)≈ $22.20

The GraphQL containersUsageAdaptiveGroups dataset is the same one the Cloudflare dashboard uses for billing estimates, so these are close to invoice figures (sampled; small months may drift). Memory and CPU dominate in roughly equal parts; disk is negligible.

Provisioned capacity is the real headline number

There are 14 container apps (1 production tenant, several dev/test/staging tenants, 1 live e2e app). All are configured with max_instances: 3. If every instance ran at capacity simultaneously, the account is provisioned for:

ResourceTotal provisioned
Memory68.3 GiB
vCPU11.06
Disk186 GB

Worst-case (all 14 apps × max_instances × 24/7 × 30 d) would bill ≈ $1,050/mo. The actual bill is ~$22 because containers sleep most of the time — but that gap is the entire opportunity: every hour a container stays awake costs money, and every GiB provisioned multiplies it.

Instance-type mix (from Containers API)

AppInstance typeMemoryvCPU30 d @ 1 inst 24/7
tenant-tio-sandboxstandard-26 GiB1$92.90
tenant-tarjetero-sandboxstandard-14 GiB½$53.29
tenant-tarjetero-dev-sandboxstandard-14 GiB½$53.29
tenant-mcabo-sandboxstandard-14 GiB½$53.29
tenant-arua-sandboxbasic1 GiB¼$20.17
tenant-test2-sandboxbasic1 GiB¼$20.17
moltbot-sandbox-e2e-33865737117-1-base-sandboxbasic1 GiB¼$20.17
7 × lite tenants (nocf-8/13/900, draft-11084/1220, sadsadsa, test2-dev)lite0.25 GiB1/16$5.22 each

Monthly multiplier vs lite (the platform default):

Type$/mo (1 inst 24/7)× lite
lite$5.22
basic$20.173.9×
standard-1$53.2910.2×
standard-2$92.9017.8×
standard-3$158.4230.4×
standard-4$288.7555.3×

3. Root causes

  1. Instance type chosen at provisioning time overrides the platform default. apps/fouria/wrangler.jsonc templates basic, and lerma defaults to lite (tenant_provisioner.ex:1103), but tarjetero, tarjetero-dev and mcabo were provisioned at standard-1 (4 GiB) and tio at standard-2 (6 GiB). Each 4–6 GiB tenant costs 10–18× a lite instance for the same work. OpenClaw in a sandbox does not need 6 GiB; basic (1 GiB) or lite is enough for the gateway process.
  2. max_instances: 3 everywhere (container_max_instances default = 3, and every app in the account shows 3). Staging/test tenants (nocf-*, draft-*, sadsadsa, test2*) carry the same 3-instance provisioned capacity as production, tripling warm memory headroom for tenants with near-zero traffic.
  3. Idle WebSockets keep containers warm. The 19.6 days of worker wall-time are open Control-UI / gateway connections. Each open connection prevents the container from sleeping, directly billing memory+CPU the whole time (SANDBOX_SLEEP_AFTER default is never).
  4. Stale e2e / test artifacts. moltbot-sandbox-e2e-* workers/apps and the -dev sandbox apps persist after CI runs and keep allocated (if sleeping) capacity.

4. Recommendations (ordered by impact)

4.1 Right-size tenant instance types — largest, immediate win

TenantCurrentProposedMemory cut
tiostandard-2 (6 GiB)basic (1 GiB)83%
tarjetero, tarjetero-dev, mcabostandard-1 (4 GiB)basic (1 GiB)75%

Measured from this month’s metrics, capping every instance at basic (1 GiB) cuts container memory billing ≈ 44%; capping at lite (0.25 GiB) cuts it ≈ 87%.

Implementation:

  • In lerma: Instances.change_instance_type/2 (already wired to the Containers API) → set the four tenants above to basic.
  • Guard rails: enforce default_instance_type/1 (lite) and cap standard-* behind a plan gate so a standard-2 never appears without explicit operator + plan justification.

4.2 Cut max_instances per tenant

  • Production tenant: keep 2 (for a quick warm failover), not 3.
  • Dev/test/staging tenants (nocf-*, draft-*, sadsadsa, test2*, -dev sandboxes): set max_instances: 1.
  • Add a per-client max_instances field in lerma instead of a global default of 3 (container_max_instances, tenant_provisioner.ex:1147).

4.3 Force aggressive sleep + close idle connections

  • Ensure every tenant worker sets SANDBOX_SLEEP_AFTER (e.g. 10m); audit the dispatch namespace bindings (the three production tenant scripts currently expose zero plain-text vars — confirming no sleep policy is deployed there).
  • Add an idle WebSocket timeout in the worker proxy so long-lived gateway/Control-UI connections are closed after N minutes without traffic, letting the container sleep.
  • Tighten SANDBOX_SLEEP_AFTER in apps/fouria/wrangler.jsonc from 15m10m and add a per-tenant override in the provisioning metadata.

4.4 Decommission stale apps

  • Remove the abandoned tenants: tenant-sadsadsa, tenant-draft-11084, tenant-draft-1220, tenant-nocf-8, tenant-nocf-13, tenant-nocf-900, tenant-test2-dev, and tenant-tarjetero-dev if unused.
  • Delete the old moltbot-sandbox-e2e-1787041788-c89ea982 / -1786969669-7d925be5 workers and apps; extend the cleanup-stale-images.yml workflow to also delete e2e container apps older than a day.

4.5 Monitoring

  • Track containersUsageAdaptiveGroups memory/CPU per applicationId daily (GraphQL query in §5) so instance-type changes are verified against the invoice.
  • The existing container-active Analytics Engine events already carry instanceType and action — wire the estimateContainerCost/3 helper (in lerma cost_calc.ex) into a weekly drift report comparing provisioned vs. billed.

5. Appendix — reusable queries

# Container memory + CPU per app (billing estimates)
query {
  viewer {
    accounts(filter: { accountTag: "35afea16440634aa2350331d2a736eec" }) {
      containersUsageAdaptiveGroups(
        limit: 50
        filter: { datetime_geq: "2026-08-04T00:00:00Z", datetime_lt: "2026-09-04T00:00:00Z" }
        orderBy: [sum_allocatedMemory_DESC]
      ) {
        sum {
          allocatedMemory
          allocatedDisk
          cpuTimeSec
        }
        dimensions {
          applicationId
          instanceId
        }
      }
    }
  }
}

# Worker duration (GB·s) — verify plain Workers stay inside the free tier
query {
  viewer {
    accounts(filter: { accountTag: "35afea16440634aa2350331d2a736eec" }) {
      workersInvocationsAdaptive(
        limit: 100
        filter: { datetime_geq: "2026-08-04T00:00:00Z", datetime_lt: "2026-09-04T00:00:00Z" }
        orderBy: [sum_duration_DESC]
      ) {
        sum {
          requests
          duration
          cpuTimeUs
          wallTime
        }
        dimensions {
          scriptName
          usageModel
        }
      }
    }
  }
}

Pricing references (Cloudflare docs, retrieved 2026-09-04):

  • Workers Standard: 10 M requests/mo + $0.30/M, 30 M CPU ms/mo + $0.02/M, no duration charge.
  • Containers (Workers Paid): 25 GiB-h/mo + $0.0000025/GiB-s; 375 vCPU-min/mo + $0.000020/vCPU-s; 200 GB-h/mo + $0.00000007/GB-s. Instance types: lite 256 MiB, basic 1 GiB, standard-1 4 GiB, standard-2 6 GiB, standard-3 8 GiB, standard-4 12 GiB.