Deploy on Cloudflare
What you need: a Cloudflare account (the free plan is enough), Node 22 with pnpm (corepack enable), a place for the
media (Google Drive, drive.md, or an S3-compatible bucket such as R2, s3.md), a way to reach each Instagram account (a
Meta app and a token per account, instagram-graph.md, or Composio,
instagram-composio.md), and for Shorts comments a YouTube OAuth client
(youtube.md).
Some of this setup is still manual. After deploying, you connect accounts, storage and devices from the dashboard.
1. Install and log in
pnpm install
cd apps/cloudflare
pnpm exec wrangler login
The commands below run in apps/cloudflare.
2. Create the database
pnpm exec wrangler d1 create bongsu
Put the printed database_id into both apps/cloudflare/wrangler.toml and apps/cloudflare/wrangler.cron.toml. Keep your edited copies
out of any public fork (they hold your ids and URLs).
3. Set the variables
In both wrangler files:
| Variable | Value |
|---|---|
PUBLIC_URL |
the bongsu Worker's URL, e.g. https://bongsu.<your-subdomain>.workers.dev or your own domain |
RP_ID |
that URL's hostname (passkeys are tied to it: changing it later means registering passkeys again) |
VAPID_PUBLIC_KEY |
the public half of a VAPID key pair (e.g. pnpm dlx web-push generate-vapid-keys) |
VAPID_SUBJECT |
mailto: plus your email |
DRY_RUN |
keep "1" until you trust the setup: every step logs and notifies instead of publishing |
4. Accounts come later
Instagram accounts and YouTube channels are connected from the dashboard once it's running (step 8). A deployment that prefers SQL and secrets can still add the rows by hand: see configuration.md.
5. Create the tables
pnpm exec wrangler d1 migrations apply bongsu --remote
6. Set the secrets
See configuration.md for the list. Each one goes to both Workers.
The CLI generates and sets them all, SETUP_TOKEN included. If you'd rather choose SETUP_TOKEN yourself (a long
random string, openssl rand -hex 32), set it in both Workers first, still in apps/cloudflare; the CLI then keeps
it:
pnpm exec wrangler secret put SETUP_TOKEN
pnpm exec wrangler secret put SETUP_TOKEN --config wrangler.cron.toml
Build the repository (that builds the CLI too), then run the CLI from the repository root:
cd ../..
pnpm build # the phone app, the dashboard and the CLI
node packages/cli/dist/cli.mjs deploy-secrets
--config-dir points it elsewhere; the default is apps/cloudflare. You must be logged in to Cloudflare for
wrangler. It sets, in wrangler.toml and wrangler.cron.toml, through wrangler secret put:
SETUP_TOKEN: lets the first person sign up in the dashboard, once. The command prints it (and only when it generated it now, not when it kept one already set): copy it for step 8.ENCRYPTION_KEY: encrypts your OAuth apps' secrets and the accounts' tokens in the database. Keep a copy of it elsewhere: without it the stored tokens can't be read, and--rotatereplaces it.MEDIA_SIGNING_KEY: signs the media links.VAPID_PRIVATE_KEY: the private half of the Web Push key pair.
Names already set in both Workers are kept (kept SETUP_TOKEN in wrangler.toml); a name set in only one stops the
command, since both Workers need the same value (--rotate --only <NAME> fixes it). --rotate replaces them and
--only ENCRYPTION_KEY,SETUP_TOKEN limits the run. The values are never written to disk, and the only one printed
is the SETUP_TOKEN it just generated, once. It also prints the public half of the VAPID pair, which is not secret:
VAPID_PUBLIC_KEY = "B…"
Paste it over REPLACE_WITH_YOUR_VAPID_PUBLIC_KEY in [vars] of both wrangler configs. Rotating
VAPID_PRIVATE_KEY makes existing push subscriptions stop working until the app is opened again.
7. Deploy
The app was built in step 6 (the phone app in packages/web/dist, the dashboard in dashboard/ inside it). Deploy
both Workers from apps/cloudflare:
cd apps/cloudflare
pnpm run deploy # both Workers
After a change to the app, run pnpm build again from the repository root first.
To give the app your own name, colour and icons, set them before pnpm build: configuration.md.
Check https://<PUBLIC_URL>/health: ok: true, all tables present, no missing secrets.
8. Sign up in the dashboard
- Open
https://<PUBLIC_URL>/dashboardon your computer. The first visit asks for theSETUP_TOKENfrom step 6 (printed bydeploy-secrets, or the one you set), your name and email, and creates your passkey (the computer's fingerprint, face or PIN). Save the recovery codes it shows: one of them signs you in if you ever lose every passkey. You're the instance's admin; next the dashboard asks for your first workspace (a name, its time zone, and its first admin: you, preselected). Then Get started opens: a checklist of the steps below, each ticked as it's done (the overview links back to it until all four are). - Time zone and the daily digest's hour, right on Get started (or later in Settings).
- OAuth apps: paste your Meta app (Instagram) and Google app (YouTube). Each card shows the redirect URL to register in the app; the guides walk through making them: instagram-graph.md, youtube.md. Composio users skip this (instagram-composio.md).
- Connections → Connect for each Instagram account and YouTube channel.
- Devices & passkeys → Add a device: scan the QR code with the phone or tablet, create its passkey, add the app to the home screen and turn on notifications in its Settings. That device approves the posts.
- Keys & tokens → Agent keys, if an agent (Claude, a script) will plan posts: create a key with the scopes it needs.
9. Go live
When dry runs look right, set DRY_RUN = "0" in both wrangler files and deploy both Workers again.
Publishing YouTube videos? Watch the first real upload: if YouTube answers it with 400 or 411, or the run hits the CPU
limit, set YOUTUBE_UPLOAD_MODE = "chunk" on the cron Worker
(YouTube: uploads on Cloudflare).
Updating
Pull, apply the migrations, build and deploy both Workers again: Updating.
Backups
pnpm exec wrangler d1 export bongsu --remote --output backup.sql; restoring and the media are in Backups and restore.