Skip to content
carameldocs · 0.16.0
Browse documentation
Reference / Local environment

A local home for every app.

Latte runs PostgreSQL, DNS, and HTTPS on your Mac, and Frappé builds, serves, and checks each application at its own named origin.

ContractValue
Release0.15.0
NeedsAn Apple Silicon Mac with Apple’s Command Line Tools, and a checkout of the release
You will havePostgreSQL, DNS and HTTPS from Latte, and each application at https://NAME.caramel
Proved byfrappe doctor

Learning it for the first time? Your first application installs the release and sets up local HTTPS in order. This page lists every command, file and setting of the local environment.

Limits at a glance

LimitValue
Log size before Latte’s sweep rolls it (the file moves to .previous)1 MiB, checked once a second
Development sessions per project1
frappe logs lines200
Wait for related saves before a build50 ms
First crystalline buildup to 20 minutes
Latte’s DNS port; Caddy’s ports behind the relay15353; 18080 and 18443
Toolchain root directory mode0700
.env mode0600

Latte runs three services, not your application

Latte is a per-user service supervisor for macOS. It runs one shared PostgreSQL 18 cluster on a private Unix socket, CoreDNS, and a Caddy HTTPS proxy, and keeps a registry of your sites. Applications run under frappe dev in your terminal, not under Latte. Latte also runs a trace collector; the Crema reference describes it.

ConditionOutcome
A Frappé command needs the services and Latte is not runningFrappé runs latte daemon --detach
The daemonoutlives the terminal
Stopping the daemonleaves the services running; the next daemon adopts them
latte daemon without --detachruns Latte in the current terminal
A log past 1 MiB at Latte’s once-a-second sweepmoves to a .previous file, so a log can pass 1 MiB between sweeps
The menu bar appdoes not start Latte
Terminal
frappe services          # postgres, dns and proxy with their states
frappe services start    # starts Latte when needed, then its services
frappe services stop     # stops the services; Latte keeps running
latte stop               # stops Latte; the services keep running
latte service install    # start Latte at every login
latte service uninstall
latte version

Expect: frappe services, start and stop print one line each for postgres, dns and proxy with its state, then collector running (127.0.0.1:4318); start starts Latte first when needed, and after stop the services read stopped while Latte keeps running. latte stop prints Latte stopped; its services were left as they were (frappe services stop stops them)., or Latte is not running. latte service install prints Latte now starts when you log in (~/Library/LaunchAgents/dev.caramel.latte.plist), and is running. latte service uninstall prints Latte no longer starts at login; the Latte it started has stopped. and that the services keep running. latte version prints Latte 0.16.0 (control API 1, 2).

The login item is opt-in. latte service install writes ~/Library/LaunchAgents/dev.caramel.latte.plist, takes over from a running daemon, and starts Latte now.

Latte’s logs live in ~/Library/Application Support/Caramel/logs/: latte.log for the daemon, and postgres.log, dns.log, and proxy.log for the services.

The menu bar app shows the services and sites, opens a site, its folder, or its logs, and starts or stops the services. Build it from a Caramel checkout with scripts/build-latte-menu, which writes bin/Latte.app.

Setup runs once per machine

Caramel 0.16.0 runs on Apple Silicon with Apple’s Command Line Tools, and installs from source. From a checkout of the release, these steps install the pinned toolchain, put frappe and latte in ~/.local/bin, and connect .caramel names to Latte. Add ~/.local/bin to your shell’s PATH before the fourth step: from there on the commands run the installed launchers. Each step is safe to run again; prepare refuses an existing bundle folder, so the rm -rf clears it first.

Terminal · inside the caramel checkout
scripts/install-toolchain
scripts/build-release
bin/frappe installations register
frappe services start
rm -rf /private/tmp/caramel-integration-bundle && scripts/install-local-integration prepare /private/tmp/caramel-integration-bundle && sudo scripts/install-local-integration apply /private/tmp/caramel-integration-bundle
latte trust install

Expect: install-toolchain installs the pinned Crystal and PostgreSQL tools into a private 0700 root and writes its path to .caramel-toolchain. build-release installs the locked dependencies and builds bin/frappe, bin/latte and the linter. installations register prints Registered Caramel 0.16.0: PATH and writes the launchers to ~/.local/bin. frappe services start prints the three services and the collector with their states. prepare prints a JSON review with bundle, installs, runs_as and sha256; apply asks for your password and prints Installed Caramel resolver and unprivileged standard-port relay. latte trust install prints Latte certificate trust installed.

ConditionOutcome
preparebuilds the integration bundle as you; as root it refuses with Prepare the installation as the ordinary installing user
prepare with an existing bundle folderfails with File exists
applyinstalls it as an administrator; refuses unless run through sudo
apply while another release’s integration is installedrefuses with A different Caramel integration is installed; uninstall it explicitly first; frappe doctor then prints the prepare, uninstall and apply sequence that replaces it
scripts/install-local-integration statusprints a JSON object with current, stale or absent for relay, plist and resolver, without sudo
The resolver /etc/resolver/caramelsends .caramel lookups to Latte’s DNS on 127.0.0.1 port 15353
The launchd relay /Library/PrivilegedHelperTools/dev.caramel.ports, loaded by /Library/LaunchDaemons/dev.caramel.ports.plistforwards ports 80 and 443 to Caddy on 18080 and 18443
The receipt /Library/Application Support/Caramel/local-integration.jsonrecords what apply installed, and uninstall removes only files that still match it
latte trust installadds Latte’s certificate authority, named Caramel Local Authority in Keychain Access, to the keychain security default-keychain -d user names
latte trust install after the authority was rotatedrefuses with Remove the previously installed Latte certificate before rotating its authority
sudo scripts/install-local-integration uninstall and latte trust removeundo these steps; uninstall leaves certificate trust alone

Every site gets its own origin

A site’s origin is https://NAME.caramel.

ConditionOutcome
frappe new NAMEregisters the application with Latte as a site, writes its private .env, and applies its migrations
frappe setup in a cloned projectinstalls the locked dependencies and takes the same steps, keeping existing files
A site’s state in frappe sitesis running, building, build-error, stopped, unavailable or unknown; (terminal) after it marks a site that a frappe dev session serves
frappe sites remove NAMEunregisters the site and keeps its folder, databases, credentials, logs, and backups; frappe setup registers it again
frappe sites remove NAME for a name Latte does not knowexits 1 with No registered site is named NAME
domain_suffix in config/environment.ymlcaramel, test, or localhost
A .localhost nameresolves to loopback without a resolver entry, but frappe dev, frappe open, and frappe doctor still need the port relay and latte trust install, because they require the origin to reach loopback with trusted HTTPS on the standard port
Switching the suffixchange it, delete .env, and run frappe setup, which registers a new site with its own databases
Terminal
frappe sites
frappe sites remove demo
frappe open

Expect: frappe sites prints one line per site: its name, state, origin and folder. frappe sites remove demo prints Removed demo (https://demo.caramel) from Latte., that its folder, databases, credentials, logs and backups were kept, and the frappe setup command that registers it again. frappe open opens the origin in your browser.

Source changes rebuild the application

frappe dev builds the application, serves it at its origin, and opens the browser when the first build is ready. It watches src, config, db, app, public, shard.yml, shard.lock, and .env with kqueue. Ctrl-C stops the project.

Terminal
frappe dev
frappe dev --no-open
frappe dev --branch experiment

Expect: frappe dev builds the application, serves it at its origin and opens the browser when the first build is ready; --no-open leaves the browser alone. The terminal prints Type check passed in N ms or Type check failed in N ms when the compiler finishes its semantic stages, then Building NAME…. --branch experiment runs against the database branch that frappe db branch create experiment made.

ConditionOutcome
A source changewaits 50 ms for related saves, then runs one compiler build with -D caramel_development
A type-check failureprints Type check failed in N ms and skips code generation
A newer changestops an obsolete build
Asset changes alonerefresh the page without a rebuild
The application is building or has failedanswers 503 at its origin with a diagnostics page showing the compiler’s output, with secrets and database URLs redacted; the page reloads when the next build is ready
Pending migrationsfrappe dev says so once and serves as soon as frappe migrate applies them
A changed .envneeds a restart
A second frappe dev for the same projectrefuses
Terminal
frappe logs
frappe logs compiler --follow

Expect: frappe logs prints the last 200 lines of the application log through tail; compiler --follow keeps printing. A missing log exits 1.

frappe logs access prints the proxy’s access log. These logs live in ~/Library/Application Support/Caramel/logs/sites/.

Every site gets two databases

Latte gives each site a development database and a separate spec database, each with a runtime URL and a migration URL. Setup writes them to .env with APP_ORIGIN and a fresh APP_SECRET.

FileHolds
.envPrivate: the origin, the secret, and the development and spec database URLs
.env.testCommitted test-only settings for every spec worker
config/environment.ymlSite name, PostgreSQL major, extensions, and domain suffix
db/seeds.crSeed data for frappe seed

Specs never touch development data. frappe corretto migrates the spec database, has Latte clone one for each worker, and gives each worker the values in .env.test. The keys that file cannot set are in the Corretto reference →

ConditionOutcome
.envmode 0600, ignored by Git
A DATABASE_URL or MIGRATION_DATABASE_URL in .env that differs from Latte’srefuses with KEY differs from this project’s Latte credentials; command refused
An APP_ORIGIN in .env that differs from the project’srefuses with APP_ORIGIN differs from this project; run frappe setup
frappe seedruns App.seed from db/seeds.cr against the development database, and never resets it
frappe db restore FILEsaves the current database as a pre-restore backup first, then restores FILE; refuses while a development session runs
frappe db branch create NAME that is not a lowercase letter followed by up to 30 lowercase letters, digits or underscoresexits 1 with the usage message
Terminal
frappe seed
frappe db dump
frappe db restore backup.dump
frappe db branch create experiment

Expect: seed runs App.seed against the development database. db dump saves a backup under ~/Library/Application Support/Caramel/backups/SITE_ID/. db restore saves the current database as a pre-restore backup, then restores the file. db branch create clones the development database into branch experiment and prints its connection URL.

A branch is a copy-on-write clone of the development database.

Branch, diff, and migrate with SugarORM →

Projects run the release their lock pins

An application’s shard.lock pins its Caramel release, and Frappé hands each project command to that release’s Frappé.

ConditionOutcome
A missing releaseis offered for install on a terminal; agents and pipes get the install command
frappe sites and other commands that need no projectrun in the Frappé you called
The launchers in ~/.local/bin, on-demand Latte, and the login itemrun the newest installed release
A resolver or port relay that differs from the newest releasefails frappe doctor’s Port relay check, which prints the prepare, uninstall and apply commands that replace them
installations install on an installed releasebuilds what is missing, such as its linter
Terminal
frappe installations
frappe installations install 0.16.0
frappe installations register
frappe installations remove 0.16.0

Expect: frappe installations prints one line per release, its version then its root, with (this installation) after the one you called; with none it says so and names the two ways to install one. install clones the release’s tag into ~/Library/Application Support/Caramel/releases/VERSION, reuses your toolchain when the release pins the same one, builds it and registers it. register prints Registered Caramel 0.16.0: PATH and needs a built bin/frappe and bin/latte; without them it exits 1 naming the build script. remove forgets a release.

See every command’s syntax →

Each installation pins two language servers

Each installation pins two language servers that use its private toolchain and the Crystal compiler the release pins, never a global or Homebrew Crystal: crystalline 0.20.0 for definitions, hover, limited completion, diagnostics on save, formatting, document symbols, and signature help, and ameba-ls 0.2.0 for lint diagnostics with Fix and Ignore actions. liger is disabled. The Current release page states the Crystal pin.

Terminal
frappe lsp install

Expect: The first run needs network access, git and Apple’s Command Line Tools, and prints Building crystalline 0.20.0 with Crystal VERSION (this can take up to 20 minutes)… while it builds crystalline from source. Later runs verify the installed tools.

ConditionOutcome
frappe lsp crystalline or frappe lsp ameba-lschecks the pinned release, then runs a server on stdio for the current project; any editor can run them from the project folder
Zed with no matching frappe on its login-shell PATHdoes not start the servers (see troubleshooting)
FileHolds
.zed/settings.jsonGenerated: runs both servers through frappe in Zed, which must find frappe on its login-shell PATH

Older projects copy .zed/settings.json from templates/application in Caramel’s source. frappe installations register puts frappe in ~/.local/bin, where Zed’s login-shell PATH finds it. A one-off PATH=… zed . starts the servers until Zed refreshes the worktree environment, which drops that PATH. CARAMEL_TOOLCHAIN_ROOT is optional, because the installation’s .caramel-toolchain names the toolchain. Before it starts a server, frappe lsp checks the release pinned in shard.lock, as frappe dev does.

Several installations can sit side by side. Each pins its own tools, and installations that share a toolchain root keep their versions apart; different Crystal pins need separate toolchain roots. A project is served by the installation whose frappe Zed finds, and frappe lsp runs under the release the project pins in shard.lock. crystalline navigates into the project’s Caramel dependency in lib/caramel.

To verify, run tail -f ~/Library/Logs/Zed/Zed.log, which shows the lsp crystalline server starting, or run dev: open language server logs in Zed to see the server’s frappe lsp: startup line with its binary, toolchain root and where the root came from.

Editor messages point to their fix

Message or symptomWhat to do
frappe lsp: no Caramel toolchain is configuredRun scripts/install-toolchain in the Caramel checkout.
frappe lsp: toolchain root … (ownership, symlink or privacy)The root must be an owned, private (0700) directory; fix it or point to the right root.
frappe lsp: <root> is not a completed Caramel toolchainRerun scripts/install-toolchain to finish it.
frappe lsp: <root> does not provide Crystal <version>This installation needs a toolchain root with its Crystal pin.
frappe lsp: <name> <version> is not installed in <root>Run frappe lsp install for this installation.
frappe lsp: <binary> failed verificationRemove the named directory and run frappe lsp install.
frappe lsp: another editor tools installation is runningWait for it to finish.
frappe lsp: checksum mismatch or download failedNothing was installed; retry on a working network.
frappe lsp: … loaded a library outside Caramel or macOSThe binary was not published; report it.
frappe lsp: crystalline build directory preserved for inspectionThe build output above names the failure; remove the directory afterwards.
This project uses Caramel X, not Y; use its matching Caramel installationInstall that release with frappe installations install X, or register its checkout.
Zed failed to spawn command for bin/frappeRun scripts/build-frappe. For frappe: the matching binary is absent from Zed’s login-shell PATH; a one-off PATH=… zed . works until a worktree-environment refresh.
Zed Please install crystalline manually and make sure it is on $PATHThe project settings were not applied, because the worktree is untrusted or .zed/settings.json is missing.
Zed Waiting for worktree … to be trustedTrust the worktree.
After installingRun editor: restart language server.
ameba-ls diagnostics while you editameba-ls intentionally disables Layout/TrailingWhitespace, Layout/TrailingBlankLines and Lint/Formatting while you edit. Try pp! 1 to verify Lint/DebugCalls diagnostics instead.
Syntax diagnostics in templates/**Expected: those files contain generator placeholders.
Memory usecrystalline compiles the project in memory; its use grows with the project and its dependencies.
Format on saveUses crystalline, which formats with the pinned Crystal formatter.

Removing editor tools changes nothing else

The root is the first line of the checkout’s .caramel-toolchain.

ConditionOutcome
rm -rf "ROOT/editor"removes every editor tool in that root
Deleting ROOT/editor/ameba-ls/VERSION or ROOT/editor/crystalline/VERSION-FINGERPRINTremoves one version
A launch after deletingprints frappe lsp: NAME VERSION is not installed in ROOT; nothing else changes

Prove it

Run the doctor

Terminal · inside the application
frappe doctor

Passing: Each of the nine checks prints OK and its label, from Project configuration to Port relay, and the command exits 0. A failed check prints CHECK LABEL: reason instead, and the exit is 1. A stale resolver or relay fails Port relay with the prepare, uninstall and apply commands that replace them.