Stand up what pithy add storage only wired: the per-environment R2 bucket, the storage-r2-credentials secret, and the deployed sweep worker that hosts the daily orphan reconciliation.
This is the provisioning half of the storage capability — provisioning Storage walks the same run with the account steps around it, and letting users upload files is what you build on it.
Synopsis
pithy storage provision [--api-token <token>] [--r2-access-key-id <id>] [--r2-secret-access-key <key>] [--json]
pithy storage deprovision [--storage] [--r2-access-key-id <id>] [--r2-secret-access-key <key>] [--json]Both subcommands reach a Cloudflare account. There is no local mode and no --env flag: provisioning spans every managed environment in one run.
Flags
provision
| Flag | Default | Purpose |
|---|---|---|
--api-token <token> | CLOUDFLARE_API_TOKEN | The token carried beside the R2 key pair, so the object store can prove bucket access. The default is a broad token; supply an R2-scoped one for production |
--r2-access-key-id <id> | R2_CREDENTIALS | R2 S3 access key id the Worker presigns uploads and downloads with |
--r2-secret-access-key <key> | R2_CREDENTIALS | The secret half of the pair. Passing one of the two without the other is refused |
--json | false | Machine-readable output |
deprovision
| Flag | Default | Purpose |
|---|---|---|
--storage | false | Irreversible. Also delete the R2 buckets and every file in them |
--r2-access-key-id <id> | R2_CREDENTIALS | Required with --storage: a bucket must be emptied over the S3 protocol before R2 will delete it |
--r2-secret-access-key <key> | R2_CREDENTIALS | The secret half of the pair |
--json | false | Machine-readable output |
What it does
The R2 key pair is supplied, not minted. Cloudflare exposes no API for creating one, so it comes from the flags or from R2_CREDENTIALS in the account config and is written into the secret as given. You make it under R2 → Manage API tokens.
provision runs in phases across every managed environment rather than one environment end to end, so a failure creating production’s bucket stops the run before staging’s worker is deployed against a half-provisioned account:
- Preflight. Verify the account can host a Workflow at all — most importantly a registered
workers.devsubdomain. - Buckets. Create or reuse each environment’s R2 bucket.
- Credentials. Write each environment’s
storage-r2-credentialssecret: the account id, the S3 key pair, the scoped token, and the bucket name the object store presigns with. Before the worker, because a worker that boots without its credentials fails on its first multipart abort. - Workers. Deploy the prebuilt sweep worker per environment, wired to the provisioned bucket.
- Bindings. Write the sweep Workflow binding into the project’s
wrangler.jsonc, per environment.pithy add storagecannot: wrangler requires both anameand aclass_nameon everyworkflowsentry, and the deployed name is per project and environment. An entry short of either field fails the whole config, soaddemits none and this run completes it.
Each environment’s deploy needs two things resolved first, and each missing one is refused rather than deployed around: the app DB id from that environment’s stanza, and the environment’s secrets database, which pithy secrets provision creates.
deprovision removes the sweep workers. The buckets and their files stay unless --storage is passed. With --storage, the key pair is resolved before the first worker comes down: discovering it missing at the bucket step would leave the workers gone and the buckets standing.
--json
storage provision
| Key | Type | Meaning |
|---|---|---|
command | "storage provision" | The subcommand that produced this line |
environments | array | One entry per environment provisioned, in managed-environment order |
environments[].env | string | The environment this entry describes |
environments[].bucketName | string | The R2 bucket objects live in for this environment |
storage deprovision
| Key | Type | Meaning |
|---|---|---|
command | "storage deprovision" | The subcommand that produced this line |
storageDeleted | boolean | Whether --storage was passed, and therefore whether the buckets and their files were deleted |
Errors
The capability is not configured. No Worker’s pithy.config.ts composes storage.
The capability will not load. Distinct from the above, and classified rather than assumed. The package missing answers not installed; the package present with one of its own imports unresolved answers could not be loaded and tells you to install the project’s dependencies — pithy add cannot fix that one. A package that resolves and throws answers installed and will not load.
Credentials are missing.
No Secrets Store id. The sweep worker decrypts its credentials from it. Both subcommands resolve credentials through the same reader, so deprovision is refused without it too, though it writes no secret.
Half an R2 key pair, or none at all.
The R2 access-key pair is incomplete.
Pass both --r2-access-key-id and --r2-secret-access-key.A malformed R2_CREDENTIALS is its own refusal — not valid JSON, or not a valid access-key/secret-key pair — rather than a SigV4 fault later.
The project has no name. Every resource name this run creates, and deprovision’s ability to find them again, derives from it.
The environment is not in wrangler.jsonc, or its DB binding has no id.
The secrets database does not exist.
The staging secrets database (acme-staging-secrets) does not exist.
Run `pithy secrets provision` first — the sweep worker reads its credentials from it.Examples
$ pithy storage provision
staging: bucket acme-staging-storage ready, sweep worker deployed.
prod: bucket acme-prod-storage ready, sweep worker deployed.
Done.Take the sweep workers down and leave the files alone:
$ pithy storage deprovision
Sweep workers removed.
Done.Take everything down, including the bytes:
pithy storage deprovision --storage --r2-access-key-id "$R2_KEY" --r2-secret-access-key "$R2_SECRET" --json