Self-hosting Laberty
This guide runs your own copy of Laberty on your own Cloudflare account, with your own keys. It is written for the Founder license, and it assumes you are comfortable with a terminal, Git and Cloudflare.
The Founder license includes no support. This guide and the code are what you get. The license itself is in FOUNDER-LICENSE.md: your copy is for your own household or your own organization, not for resale or for running as a service for other people.
What you need
- A Cloudflare account. Laberty is one Cloudflare Worker, one D1 database and a daily Cron Trigger. The Workers Free plan is enough to start; Workers Paid removes the daily request and CPU limits.
- A domain on Cloudflare for the app, for example
labels.example.com. Passkeys are tied to the domain, so pick one you will keep. - Node.js 22 or newer, and Git.
- An email provider for sign-in links. The code sends through Resend. Laberty has no passwords: people sign in with an emailed link or a passkey, so email is required.
- Optional: an Anthropic and/or OpenAI API key for the assistant and food lookups, and a UPCdatabase.org key for barcode lookups.
1. Get the code
Accept the GitHub invitation for the private repository, then:
git clone https://github.com/<owner>/<repository>.git laberty
cd laberty
npm ci
Each release is one commit, tagged with its version. git tag lists them.
2. Sign wrangler in to your Cloudflare account
Either run npx wrangler login, or create an API token (Cloudflare dashboard → My Profile → API Tokens) and set it as CLOUDFLARE_API_TOKEN in your shell. A token needs:
- Account → Workers Scripts → Edit
- Account → D1 → Edit
- Zone → Workers Routes → Edit, and Zone → DNS → Edit, for the zone your domain is in (custom domains)
3. Create the database
npx wrangler d1 create laberty
It prints a database_id. Keep it for the next step.
If you name the database something other than laberty, change database_name in wrangler.toml, and use your name in place of laberty in every command below and in the db:* scripts in package.json.
4. Edit wrangler.toml
Open wrangler.toml and set:
name: the Worker's name, for examplelaberty.routes: your domain, for example{ pattern = "labels.example.com", custom_domain = true }. Cloudflare creates the DNS record for a custom domain on deploy.database_idunder[[d1_databases]]: the id from step 3. Keepbinding = "DB".[ai]binding = "AI": leave it. It is Workers AI, used as a fallback assistant.[triggers]crons: the daily sweep (occasions, subscription sync, clean-up). Once a day is right; pick the hour.
Non-secret settings go in a [vars] section:
[vars]
MAIL_FROM_SIGNIN = "Laberty <signin@labels.example.com>"
MAIL_FROM_LABELS = "Laberty <labels@labels.example.com>"
MAIL_FROM_ALERTS = "Laberty alerts <alerts@labels.example.com>"
MAIL_FROM_SUPPORT = "Laberty support <support@labels.example.com>"
MAIL_FROM_ACCOUNT = "Laberty <hello@labels.example.com>"
APP_URL = "https://labels.example.com"
5. Create the tables
On a new database, apply schema.sql. It is the complete, current schema: every migration up to this release is already in it.
npx wrangler d1 execute laberty --remote --file=./schema.sql
Do not run the files in migrations/ on a new database. They exist to upgrade a database that was created from an older release (see Updating to a new release), and several of them add columns that schema.sql already has, so they would fail.
6. Email: verify your sending domain
- In Resend, add your domain and create the DNS records it asks for (SPF and DKIM). Wait until it shows as verified.
- Create an API key with sending access.
- Set the
MAIL_FROM_*variables (step 4) to addresses on that domain. The built-in defaults uselaberty.app, which you cannot send from, so mail would be rejected without them.
7. Set the secrets
Secrets are set with wrangler secret put, which prompts for the value so it never lands in your shell history:
npx wrangler secret put SESSION_SECRET
| Name | Needed? | What it is for |
|---|---|---|
SESSION_SECRET | Required | Signs sign-in cookies. Any long random string, for example from openssl rand -hex 32. |
RESEND_API_KEY | Required | Sends sign-in links and alerts. Without it nobody can request a sign-in link. |
ALERT_EMAIL | Recommended | Where errors and failed daily sweeps are emailed, as well as to domain admins. Comma-separated. |
SUPPORT_EMAIL | Optional | Where the Help Center contact form is delivered. |
ANTHROPIC_API_KEY | Optional | Claude as the assistant provider. |
OPENAI_API_KEY | Optional | OpenAI as a provider, the second opinion in food lookups, and the assistant's hold-to-talk transcription. |
ELEVENLABS_API_KEY | Optional | The assistant speaking its replies out loud. The key needs the text_to_speech permission; without it (or without the key) voice is simply off and the assistant still shows every reply as text. |
UPCDATABASE_API_KEY | Optional | Barcode lookups against UPCdatabase.org. |
SKILL_API_TOKEN | Optional | A deployment-wide API bearer token for your own scripts. |
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET | Usually not | Billing. See Stripe. |
Leave GITHUB_LICENSE_TOKEN and FOUNDER_REPO unset. They are for selling licenses, which your copy does not do.
Every optional feature hides itself when its secret is missing, so you can add them later.
8. Deploy
npm run deploy
That compiles the Help Center articles (npm run build:help) and runs wrangler deploy. Open your domain: logged out, you see the marketing page.
9. Make yourself the admin
schema.sql creates one workspace, number 1, the primary workspace, on the founder plan (every feature on, no limits). It has no people yet. Add yourself as its admin and as the domain admin, with your email address:
npx wrangler d1 execute laberty --remote --command "INSERT INTO users (workspace_id, is_domain_admin, username, password_hash, display_name, role, email) VALUES (1, 1, 'admin', 'not-used', 'Your Name', 'admin', 'you@example.com')"
npx wrangler d1 execute laberty --remote --command "UPDATE workspaces SET name = 'Our Labels' WHERE id = 1"
password_hash is never used: there is no password sign-in, the column just cannot be empty.
Now go to /login, enter that email, and use the link. Once in, open Security and add a passkey, so you are not dependent on email.
Then:
- Admin is your workspace: printers and label stock, households, people, branding, AI keys.
- Domain console (
/domain) is the level above: create more workspaces (for example one per family member's household, or per department), set their plans and limits, and read the logs.
If email ever breaks and you have no passkey, node scripts/signin-link.mjs <email> mints a sign-in link straight into the database. It needs the same Cloudflare access as a deploy; set LABERTY_URL to your own address first.
Stripe (usually not needed)
Laberty's billing (plans, trials, AI action packs) exists for laberty.app. Under the Founder License you may not sell Laberty or run it as a service for others, so for your own household or organization you normally leave STRIPE_SECRET_KEY unset. The billing pages then stay hidden, and you set each workspace's plan in the domain console.
If you want to try the checkout flows anyway, use a Stripe test-mode key; docs/STRIPE.md describes the webhook endpoint and the events it expects.
The print bridge
Phones and tablets print to Brother QL printers with no setup. For everything else (Zebra, receipt printers, printers on your network), run the print bridge, a small Docker container, on a computer near the printers. Its instructions are in agent/README.md, and the Help Center in your deployment has step-by-step articles.
Updating to a new release
Your repository access lasts for your update period (12 months from purchase). During it:
- Keep your own changes on a branch of their own, so a release merges cleanly.
- Fetch the release:
git fetch --tags, then merge the tag you want, for examplegit merge v2026.10.01. - Apply the new migrations, in order. Look at
migrations/for files numbered higher than the newest one you already applied (or, if you started fromschema.sql, higher than the newest one that existed in the release you installed). Numbers are not always contiguous. Run each one:npx wrangler d1 execute laberty --remote --file=./migrations/016-founder-licenses.sqlEach file says at the top whether it can safely be run twice. Apply migrations before deploying, because the new code reads the new columns.
npm testto check nothing of yours broke, thennpm run deploy.
When your update period ends, repository access ends. Everything you already pulled stays yours to run and modify under the license.
Local development
npm run db:init:local # a local D1 from schema.sql
npm run dev # wrangler dev on http://localhost:8787
npm test # the full test suite, against in-memory SQLite and fakes
Local secrets go in a .dev.vars file (git-ignored), one NAME=value per line.