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.

For a private repository, the credential must remain valid for scheduled and manual syncs. A short-lived GitHub App installation token is appropriate for a single automation run, but not as the durable credential saved for a connection. Use a repository-scoped, read-only credential and rotate it through the portal or platform secret store.

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

The protected production baseline now includes a 42-story narrated library and a mixed book deployment with narrated and text-only chapters. That proves both content shapes and the zero-loss update/sync checks, but it is not a load test. The limits below are contract ceilings or platform limits, not measured capacity claims.

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 — protected reference deployments

Verified 2026-08-18. These are content-safety baselines, not public promises about traffic capacity.

Deployment Library shape Narration Publishing/connection mode
StoryLark demo 2 standalone works / 2 chapters Narrated, multiple voices Demo pipeline
Gunner the Lab 42 standalone stories / 42 chapters 42 narrated Pipeline-published; no saved Admin repo connection
Holdfast Press 2 books / 3 chapters 2 narrated, 1 text-only Private-repo GitHub Actions publishes through the content API; no saved Admin repo connection

All three manifests and their content objects were checked as part of the release-1 deployment audit. Updates were required to preserve brand, theme, presentation, content manifests, chapter objects, narration, timings, and voice metadata. A repo adoption or publisher change is accepted only after a backup, manifest/content parity checks, and a second no-op sync with zero writes.

The absence of repository details under Admin → Connections for Gunner and Holdfast is therefore expected from their current topology: neither deployment was connected through StoryLark's saved repo-connection flow. An external workflow reading a repo and calling the content API is a valid publisher, but it does not create connection state that StoryLark can display or operate.


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