DocsGet started
Install and deploy
The Deploy to Cloudflare button, or one command that creates the database, the bucket, the secrets and the account, then deploys the Worker.
On this page
Two ways to deploy. The Deploy to Cloudflare button is the recommended one: nothing to install, and updates later come through GitHub with no token. Your terminal with npm run setup is for changing the code, or for deploying without a GitHub or GitLab account. Both end the same way, with a Worker, a D1 database, an R2 bucket and your account (Updating).
Everything here assumes a Cloudflare account with Workers, D1 and R2 available; R2 asks for a payment method on file even on the free tier.
Deploy with one click
- Press the button and sign in to Cloudflare. It copies cogsend/deploy, a prebuilt release, into your GitHub or GitLab account (tick Create private Git repository if you like) and deploys it with Workers Builds.
- The form asks for one secret,
APP_ENCRYPTION_KEY. Generate one at cogsend.com/key and save a copy: you need it again in step 4, and losing it later means reconnecting every account. Leave the other fields at their defaults, unless the form says a name is taken. - Press Create and deploy and wait for the build to finish. If the Worker’s Domains tab then shows its workers.dev URL as off, reload the page: the build turns the URL on as its last step, and the dashboard does not refresh by itself.
- Open the Worker’s URL. The login page asks for the encryption key, an email and a password, and creates your account. Then scan the QR with an authenticator app and save the backup codes.
Only someone who knows the encryption key can create the account, so finding the URL first is not enough to take over a fresh instance.
The account needs a workers.dev subdomain before anything can deploy; a brand-new Cloudflare account is asked to pick one (Custom domains). Updates go through that repository’s Update CogSend action, which Settings → Instance links to; keep Workers Builds connected (Updating).
One command
You also need Node 22.17+, 24 or 26+ — the versions the app declares, which npm
enforces before it runs anything — and about five minutes, most of it
wrangler login and the first deploy. To read the plan before anything changes,
run npm run setup -- --dry-run from the checkout first.
git clone --depth 1 https://github.com/cogsend/cogsend.git cogsend
cd cogsend && npm install && npm run setup
setup runs wrangler login, creates the D1 database and the R2 bucket if they
are missing, generates APP_ENCRYPTION_KEY (written to .dev.vars and to the
Worker), applies migrations, creates the account (email plus a password it
generates or you type; only the PBKDF2 hash is stored), deploys and sets
APP_URL. The account exists before the URL answers its first request, so there
is nothing to claim and no window in which someone else could get there first.
A brand-new Cloudflare account has no workers.dev subdomain yet, and the deploy needs one. setup checks before it creates anything and asks for the name, which becomes <worker>.<subdomain>.workers.dev. It is account-wide and can be changed later (Custom domains).
It is safe to re-run: resources that exist, secrets that are already set, and an
account that already exists are all left alone, because rotating
APP_ENCRYPTION_KEY orphans every stored credential and signs every session out.
main is the branch these docs are tested against, and release tags are cut
from it. See Updating for tags and rolling back.
Its flags, for the cases the defaults deliberately avoid:
| Flag | What it does |
|---|---|
--dry-run |
read-only: checks auth, prints the plan |
--yes |
no prompts; the secrets and the password are printed once |
--admin-email, --admin-password |
answer the account questions without a prompt |
--subdomain |
a new account’s workers.dev subdomain, without a prompt |
--name, --db, --bucket |
your own resource names, for a second instance on one account |
--skip-deploy |
everything except the deploy |
--rotate-secrets |
also overwrite APP_ENCRYPTION_KEY |
--reset-login |
also give the account a new password and revoke every session |
--verbose |
print every command and its raw output |
--no-color |
plain text, for logs and bug reports |
A second instance, with names of its own:
npm run setup -- --name my-cogsend --db my-cogsend --bucket my-cogsend-media
Those names live in wrangler.personal.jsonc, so every later command uses them.
Forgot the password later? npm run admin:reset, described in
Configuration.
Setup prints one line per step. The tools underneath are chatty — a first
d1 migrations apply alone is a few hundred lines of box drawing — so their
output is captured and shown only when a step fails. --verbose prints all of
it; colour turns itself off in a pipe or a CI log, and --no-color (or
NO_COLOR=1) does the same by hand.
This path needs no GitHub App and no Workers Builds, and nothing to configure in a browser beyond the wrangler login that setup starts.
After the first deploy
- Open the Worker URL and sign in with the email and password
setupcreated (or, after the button, the account you just created). The first sign-in asks for an authenticator app: scan the QR and save the backup codes it shows. - Connect accounts under Accounts. Mastodon and Bluesky work immediately; LinkedIn, Threads and X need an OAuth app each, with the redirect URI built from your deployed URL. The accounts dialog walks you through it, takes the app’s credentials in a form, and can hand the whole job to a browser agent (OAuth apps).
- Scheduled posts publish themselves through the cron trigger in
wrangler.jsonc. Nothing to set up — unless the account had no trigger slot left, in which case the deploy says so and Settings → Scheduled publishing has the tick URL and a token for an external cron. - Optional:
RESEND_API_KEY+NOTIFY_EMAILfor failure digests, and the instance name under Settings → Instance.
Check it worked
npm run doctor
npm run doctor -- --app-url https://your-worker.workers.dev
Read-only: it verifies your Cloudflare login, that your commands reach the account this checkout deployed to, that the D1 database and R2 bucket exist, that APP_ENCRYPTION_KEY is set, whether migrations are pending, and whether the Worker has a deployment. With --app-url it also asks the running app: that /api/health answers, whether an account exists yet (and, when you are signed in to Cloudflare, whether 2FA is set up, read from D1: the app does not tell anonymous callers), whether the scheduler is actually ticking (and why not, when it is not), and whether a newer release is out (with the update command for your install shape). Every failure prints the exact command that fixes it. It never changes anything.
Something failed? Troubleshooting covers the errors people actually hit.
Updating, and rolling back
Settings → Instance and npm run doctor both tell you when a newer release is out. Install it from Settings → Instance with a Cloudflare API token, or from a checkout with git pull && npm ci && npm run deploy:release; Updating covers both, rolling back, and why the in-app update is safe to run.
Your data is never in the repository: D1, R2, the Worker secrets and the app
settings live in your Cloudflare account, so neither path can touch them. The one
local file that matters is wrangler.personal.jsonc, which replaces the
committed config — a config change upstream therefore does not reach you, and
npm run doctor says so when the two disagree.
Deploying by hand
setup is the supported path. To run the steps yourself you need Node 22.17+, 24 or 26+, a Cloudflare account (npx wrangler login), and R2 enabled. Every
command goes through scripts/wrangler.mjs, which applies your
wrangler.personal.jsonc and WRANGLER_PROFILE; plain npx wrangler … would
use the generic config in the repo, and skip the check that a command reaches
the account you deployed to.
Two commands. The first sets the one required secret; generate it with
openssl rand -hex 32 and keep it out of vars, because a deploy overwrites
those. The second builds and uploads — and the first deploy also creates the D1
database and the R2 bucket.
node scripts/wrangler.mjs secret put APP_ENCRYPTION_KEY
npm run build && npm run deploy
The account comes last: open the Worker’s URL and create it on the login page with the APP_ENCRYPTION_KEY you just set, or run npm run setup once, which finds the database and bucket you just made, uploads the secrets and creates the account from the terminal. Either way, only someone holding the key can create it. npm run deploy:release runs tests, migrations, build and deploy in one
go.
A fresh database needs no migration step — the schema bootstraps itself on the
first request; an older one gets new migrations with npm run db:migrate:remote.
To choose a location, or to reuse a database or bucket you already have, create
them first: node scripts/wrangler.mjs d1 create cogsend prints an id for
wrangler.personal.jsonc, and node scripts/wrangler.mjs r2 bucket create cogsend-media makes the bucket. Otherwise the deploy creates both.
Optional secrets — API_TOKEN for scripts, SCHEDULER_SECRET for an external
pinger, the OAuth client ids, Resend for failure emails, MEDIA_PUBLIC_BASE_URL
for Meta’s crawler — are listed under Configuration.
Upload them in one go with npm run secrets:put, which reads them from
.dev.vars and then reads the Worker’s own secret list back to confirm what
landed. Secrets take effect immediately from the CLI, so a deploy is not what
makes them live; in the dashboard (Workers & Pages → your Worker → Settings →
Variables and Secrets → Add → Secret) press Deploy to apply them.
Push-to-deploy, without Workers Builds
Optional, and for updates only: the account has to exist first (run npm run setup once locally, or claim it on the login page). Like any deploy from a checkout, it is tagged with the release and refuses to replace a newer one (Updating). Copy .github/workflows/deploy.yml.example to
.github/workflows/deploy.yml and add two repository secrets —
CLOUDFLARE_API_TOKEN (Workers Scripts, D1 and R2 edit permissions) and
CLOUDFLARE_ACCOUNT_ID. Pushes to main then build, migrate and deploy from
GitHub’s runners. The workflow skips itself when the secrets are missing, so a
fork that has not set them up stays green.
Starting over
Worker, D1 database and R2 bucket can be deleted from the dashboard; the
wrangler.personal.jsonc and .dev.vars files hold the only local state. A
fresh clone plus npm run setup then rebuilds everything. Take a
backup first if you might want the data.
Where to go next
- Configuration — the secrets your instance reads, and how to name it.
- Writing and publishing — your first post, once an account is connected.
- OAuth apps — connect the platforms that need a developer app of your own.
- Scheduling — how a scheduled post actually gets published, and what to do when a tick stops arriving.
- Cloudflare Access — an extra gate in front of an instance only you use.
- Troubleshooting — the errors people actually hit, and what fixes each.