StoryLark
← All guides

StoryLark deployment & credential requirements v1

Status: normative. Deliverable D4 of the content standards plan.

This document answers one question: what must a deployment provide for StoryLark's content features to work? It is written for the person deciding whether StoryLark fits their infrastructure, before they commit to trying it.

Companion documents: the StoryLark Content Format (what content must look like) and the content API (how it arrives).


1. The shape of the requirement

StoryLark runs on a customer's own infrastructure. It needs five capabilities, and it does not care which vendor supplies them:

Capability Why Cloudflare Azure
A JavaScript runtime for the API Serves the app and every content route Workers App Service (Node)
A SQL database Accounts, sessions, progress, connections D1 PostgreSQL Flexible Server
Object storage with public read Chapter JSON, audio, images R2 Blob container
A scheduled trigger Update checks and content sync Cron trigger Timer
A secret store the runtime can read Repo tokens, API keys wrangler secret App settings

Two platforms are supported out of the box. A third is a driver, not a port — the database and storage seams already exist, and the Postgres and S3-API drivers between them cover most of what AWS would need.

1.1 One capability that is easy to overlook

The runtime must be able to make outbound HTTPS requests.

This is what makes repo sync possible at all. A Worker cannot shell out to git, so StoryLark fetches a provider's archive over HTTPS and unpacks it in process. Any runtime that can fetch a URL can sync a repository; any runtime that cannot, cannot — regardless of what else it offers.


2. What the operator must supply

2.1 Always

Value Notes
Brand id Which brand's presentation this deployment serves
App origin Where the app is served from
Content origin Where content is served from — a plain URL, deliberately not a binding
Mail sender Used for password reset and account mail
Admin credential Bootstraps the first admin account

These live in a gitignored env file per platform, which is the installer's contract. The installer has a verify mode that sanity-checks the values before creating anything.

2.2 Only for repo mode

Value Notes
Repository URL HTTPS
Branch Defaults to the repository's default branch
Path Where content lives inside the repo; the root is fine
Access token Only for a private repository
Webhook secret Only if push-triggered sync is wanted

A public repository needs no credential at all, which is worth stating plainly: the cheapest way to try repo mode is a public repo and no secrets.

2.3 Only for API mode

A scoped content-API token, issued from the portal. Nothing else.


3. Credential handling — the rules

These are requirements, not recommendations.

  1. A repository token never goes into deployment.json or any committed file. This is already a hard error rather than a warning, and it stays one. It has two legitimate homes: a platform secret (CONTENT_SYNC_TOKEN), which is preferred and always wins when present, or the deployment's own database when an operator types one into the portal — because a default deployment cannot write its own platform secrets without holding a deploy credential it is better off not holding.
  2. SSH is not supported, and the UI says so. SSH means a keypair the deployment must hold and a host-key trust decision nobody wants to make in a web form. HTTPS with a scoped token is the supported path, and declining SSH explicitly is better than appearing to offer it and failing obscurely.
  3. Scope the token to reading one repository. StoryLark never writes to a connected repository, so a token that can is a liability with no upside.
  4. A secret written to a platform store may not be readable back. Cloudflare refuses to return a secret's value by design. Any flow that assumes it can re-read what it wrote is wrong on at least one supported platform, so the portal shows whether a credential is present, never what it is.
  5. Rotation is the operator's, and it must not need us. Replacing a token is writing a new secret and saving the connection. No support ticket, no redeploy.

4. Scheduling

Every deployment already runs a scheduled job for the engine update check. Content sync is a second job on that same schedule — no new infrastructure, nothing extra for the operator to provision or understand.

The default is daily. Three tiers cover every real need:

Tier Latency Requires
Webhook Seconds A webhook secret, and a provider that sends them
Scheduled Up to a day Nothing — it is on by default
Sync now Immediate Nothing — a button in the portal

Hourly polling was considered and rejected: it would change the schedule on every deployment and multiply API-rate spend by twenty-four, to serve a case webhooks already serve better.


5. Network

Direction Requirement
Inbound to the app Public HTTPS. Readers use it.
Inbound to the content origin Public HTTPS, read-only.
Inbound webhook route Public, if push-triggered sync is used. Signature-verified, and a forged signature is rejected.
Outbound from the runtime HTTPS to the repository provider. Without this, repo mode cannot work (§1.1).

A deployment behind a network that blocks outbound HTTPS can still use the portal and the content API. It cannot use repo mode, and this is the one capability whose absence removes a whole feature rather than degrading it.


6. What StoryLark does not require

Worth stating, because these are the assumptions that usually make a self-hosted tool unusable:


7. Sizing

Four chapters across the two reference deployments today, so nothing here is load-bearing yet and these are engineering estimates rather than measurements. They are stated as ceilings the contract enforces, which is the honest version.

Dimension Limit
Chapter source 2 MB, enforced by the content gate
Ids 64 characters
Storage Chapter JSON is small; narration audio dominates by roughly two orders of magnitude
Database Accounts, sessions, progress, connections. Small.

The planning number that matters: audio is the storage cost. A text-only library is negligible on any platform's free tier.


8. Verifying a deployment before trusting it

A deployment satisfies this document when all of the following are true. Each is observable, deliberately — an operator should be able to check without asking us:

  1. The app answers on its origin, and its health endpoint reports the expected brand.
  2. Content is served from the content origin and is publicly readable.
  3. An admin can sign in and reach the portal.
  4. A chapter published through the portal appears in the library.
  5. The scheduled job has run at least once, visibly.
  6. For repo mode: a Sync now on a connected repository completes and reports what it ingested, what it skipped, and why.
  7. For repo mode with webhooks: a correctly-signed delivery syncs, and a forged one is rejected. Test the rejection — a webhook route that accepts anything is worse than no webhook route.

Appendix — status of the two reference deployments

Measured 2026-08-17. Recorded because a reference deployment that quietly fails a requirement makes this document a work of fiction.

Requirement Cloudflare demo Azure dev
Runtime ✅ health 200
Database ✅ D1 ✅ Postgres
Public content ✅ R2 via content.storylark.dev ✅ blob container
Scheduled trigger
Secret store
Narration ✅ two voices none — the path has never run here

Two open items on Azure, neither blocking this document: narration has never run end to end there, and the storage account holds two containers with identical contents, one presumably orphaned. Both are recorded in the design's §6.7.


Found a gap? StoryLark is open source — improve these docs on GitHub.