Operate a self-hosted instance
The host operator owns infrastructure, runtime secrets, database/storage connectivity, mail delivery and background workers. Workspace Owners manage their content within those operator boundaries.
How to use it
- Follow the repository deployment guide for the supported Docker/Compose setup and required production configuration.
- For Railway preparation, follow docs/operations/railway.md: explicitly review the private-network proxy trust boundary, choose path or virtual-host S3 addressing and configure SMTP or Resend HTTPS email. Local compatibility checks do not establish hosted acceptance.
- Select installation-wide storage with CMS_STORAGE_PROVIDER=s3, railway or r2. Follow docs/operations/storage-providers.md for server-only credentials and defaults. Changing a preset does not copy existing media; verify a controlled transfer before switching an established installation.
- Use public-domain TLS, scoped secrets, private PostgreSQL and configured object storage. Keep demo seeds and development services out of staging/production.
- Run migrations through the release procedure; verify readiness, mail, storage, worker outcomes and access before admitting users.
- Back up PostgreSQL and original media together and rehearse restoration off the serving instance.
Good practice
- Use the acceptance checklist for each actual environment and distinguish local tests from external SMTP/S3/TLS/restore proof.
Avoid
- Preparing a release package is not deploying it. Deployment work is currently paused; these guides do not change that decision.
Understand Staff Admin and Database Studio
Staff Admin is a separate staff-only application, not a tenant Owner permission. It provides operational workspace/account/support/usage/monitoring views and a read-only Database Studio inventory.
How to use it
- An authorized operator configures the separate staff app and its restricted source roles.
- Use the operational views for authorized support and safe metadata inspection; select a table in Database Studio to inspect bounded rows.
- Handle pilot applications only through authorized staff review. The public pilot form stores applications and contact permission; it does not send outreach or create an account automatically.
Good practice
- Keep staff access separate, audit operational work and protect application/export data.
Avoid
- Do not expose staff ports publicly by default or expect Database Studio to reveal token hashes, encrypted credentials or private report snapshots.
Use CLI, CI and reviewed schema migrations
Delivery commands inspect schemas and generate types without writes. Explicit schema migrate commands use a separate management API for reviewed compatible additions; they do not publish content.
How to use it
- Follow docs/cli.md to install/build the private @lemonadecms/client package from packages/cms-client and configure its server-held credential.
- Select workspace and environment explicitly, run the supported connection/schema/diff/codegen commands and inspect nonzero exit outcomes in CI.
- Keep generated schema/types under review and use fixture-safe output when sharing logs.
- Use types generate --language typescript, csharp or python with an explicit output file; TypeScript remains the default. See docs/type-generation.md for nullable transport models and docs/client-compatibility.md for framework/language coverage. A generated model is not a native network SDK.
- For schema writes, ask your trusted host operator to issue an expiring management credential for an existing full-schema reader with the required model actions. Keep its private output file outside source control. Existing public/secret delivery keys cannot authorize writes.
- Use an explicit environment UUID. Run schema migrate status to obtain its schema_hash and latest migration ID. Author an ordered JSON migration with format_version, id, previous, from and operations; see docs/schema-migrations.md for the exact format.
- For local schema editing, pull a baseline using a delivery credential, copy and edit that JSON, and capture fresh management status using the separate management credential. Use schema migrate create --baseline current.json --file desired.json --status status.json --id YYYYMMDDHHMMSS_description --out migration.json. It writes a local additive file only; unsupported edits are rejected. Planning and explicit apply remain mandatory.
- Run schema migrate plan --file migration.json --out review.json. Read affected models/fields, existing-entry counts and compatibility notes. Planning simulates the real validators and quotas, then rolls everything back.
- Apply only the reviewed file with schema migrate apply --file migration.json --plan review.json --confirm CHECKSUM. Plans last fifteen minutes and bind to one credential, environment and schema revision. After drift or expiry, create and review a fresh plan.
- Inspect status after an interrupted request before retrying the identical migration. An applied ID with the same checksum returns its original receipt; changed checksums or missing predecessors are rejected. Review every environment independently. Older history is available using --before NEXT_CURSOR.
Good practice
- Use the minimum credential scope and pass secrets through your CI's protected secret mechanism. Keep migration files in source control; keep private plans and credentials out. Revoke credentials when no longer needed.
Avoid
- Do not assume a plan reserves capacity or grants publishing approval. Version-one schema migrations permit new types and optional fields only: no edits/removals, defaults, SEO roles, component definitions or remote bindings. Version-two draft fills use their separate explicit workflow. Do not edit a migration that has already applied; use a new ordered addition or a coordinated verified restore. Deployment remains an independent operator action.
Review missing-draft backfills and recover from a checkpoint
Fill an absent or null draft value in an optional field proven by an earlier add_fields migration. Existing authored values and every published snapshot stay intact.
How to use it
- Ask the trusted host operator for an expiring draft.fill_missing credential with explicit models and languages using --locales. Existing schema-only and delivery credentials do not gain this access. The actor still needs current draft read/update permissions.
- Read docs/draft-backfills.md. Author a version-two file with one fill_missing_drafts operation, model key, field, locale, introduced_by migration ID and a valid scalar value. Use entry_ids for an explicit batch; at most 100 entries and 8 MiB of existing snapshots fit one file.
- Run schema migrate plan, inspect affected IDs/versions and matched/skipped counts, then review the literal value in the source file. Missing nondefault-language drafts are skipped; fallback is never copied. Empty text, false and zero count as authored and remain unchanged.
- Apply only the exact reviewed checksum through schema migrate apply. A fifteen-minute plan binds the schema, field provenance and selected entry state. New edits or relevant membership changes require fresh review. All writes commit together or none do.
- Inspect the result in the editor and history. Every changed entry has a Checkpointed revision followed by a normal Saved revision. Existing history and publication remain unchanged; saved webhooks use their usual transactional outbox.
- To recover, compare the migration checkpoint with the current draft and explicitly restore that language through normal history/restore permissions. This restores the whole language draft, so review later edits carefully. An interrupted pre-commit application rolls back; after a timeout inspect status before retrying the identical file.
Good practice
- Use small explicit batches, review each language/environment independently and keep private plans/credentials out of source control. Literal migration values should be safe shared starter content.
Avoid
- Do not use backfills to overwrite authored values, create translations, fill old/manual fields, coerce types or publish content. Replaying an applied migration after restoring a checkpoint returns its old receipt; it does not refill the field. Arbitrary scripts, destructive changes and host recovery remain separate workflows.
Troubleshoot without changing the wrong boundary
A visible symptom can originate in the studio session, saved content, delivery credential, website renderer or external service.
How to use it
- For missing content, check workspace/environment, language, filters, publication state and credential model/media scope.
- For blocked writes, inspect validation, stale revisions, effective roles and current plan capacity.
- For scheduled actions, webhooks or integrations, inspect recorded outcomes and worker/provider connectivity before retrying.
- Long-poll responses containing message arrays and transport status are Phoenix LiveView transport envelopes, not the content API payload. Report persistent fallback/performance issues separately.
Good practice
- Share sanitized error codes and reproduction steps; preserve unsaved work before reloading.
Avoid
- Do not paste private tokens, transport credentials, application data or full production database exports into issue reports.