Docs Start here
Getting started
Four steps to a running install, the snippet, and your first event.
Four steps. About five minutes, most of it waiting for Docker.
1. Install
You need Docker and Docker Compose v2.21.0 or newer. Nothing else.
That version is not arbitrary. install.sh asks Compose for a container’s
status with a Go template (docker compose ps caddy --format '{{.Status}}'),
and --format only learned to accept a template in v2.21.0 — before that it
took table or json and answered anything else with
format value "…" could not be parsed. Everything else these scripts use is
older: up --wait since v2.1.1, ps --status since the first v2 release.
If you are on something older, the install still completes — that call is on an
error path and falls back to saying nothing rather than failing. You would only
notice by getting a less specific message when a container fails to start.
Check yours with docker compose version.
git clone https://github.com/lyraflow/lyraflow.git
cd lyraflow
./install.sh
That generates passwords into .env, starts three containers, and waits until
the app answers on port 3000.
That is a local install: plain HTTP on port 3000, which is all the examples below need. Running this on a server with a domain name? Pass it to the installer and Lyraflow serves HTTPS itself:
./install.sh analytics.example.com
See Serving over HTTPS for what that changes, and for the one case — a domain proxied through Cloudflare — where it needs a hand.
Then create a project:
docker compose exec lyraflow node packages/cli/dist/index.js create-project "My App"
It prints two keys, and the difference between them matters:
Write key wk_… | Public. It can only write events. Ship it in your page source — that is what it is for. Leaked? Rotate it — see POST /v1/project/rotate-write-key. |
Server key sk_… | Secret, shown once. Reads people, merges them, deletes and exports them. Write it down; only its hash is stored, so nothing can recover it for you. |
Run create-project again for each additional website you want to track
separately — one install holds as many projects as you like, and the rest of
this document shows one only because a project is the unit each example
operates on. If you are tracking two sites, read
Tracking more than one site before
instrumenting either: whether they are one project or two decides whether a
person using both is one person or two, and that cannot be changed later
without re-ingesting.
Keep both to hand:
export LYRAFLOW_WRITE_KEY=wk_...
export LYRAFLOW_SERVER_KEY=sk_...
2. Put the snippet on your website
Ask Lyraflow for the snippet rather than writing it yourself — it fills in your host and write key, and escapes them correctly:
docker compose exec \
-e LYRAFLOW_HOST=http://localhost:3000 \
-e LYRAFLOW_SERVER_KEY=$LYRAFLOW_SERVER_KEY \
lyraflow node packages/cli/dist/index.js snippet
Paste what it prints into your site’s <head>. It loads a ~5 KB script, starts
recording page views immediately, and queues events in localStorage if your
server is unreachable, so a deploy or a blip loses nothing.
When someone signs in, tell Lyraflow who they are — this is what ties their anonymous browsing to their account:
lyraflow.identify('user-42', { plan: 'pro' })
Details, consent handling and single-page-app routing: Sending events from a browser.
3. Send events from your backend
Anything your server knows and the browser does not — payments, cancellations, webhooks — goes over the same ingest API with the same write key:
curl -i http://localhost:3000/v1/track \
-H 'content-type: application/json' \
-H "x-lyraflow-write-key: $LYRAFLOW_WRITE_KEY" \
-A 'MyApp/1.0 (+https://example.com)' \
-d '{
"message_id": "0b2f6a1e-9c4d-4a1f-8f3b-2f1c7d5e6a90",
"user_id": "user-42",
"event": "subscription_started",
"properties": { "plan": "pro", "seats": 3 }
}'
You get 202 Accepted with {"status":"accepted"}.
Set a real User-Agent, as above. Lyraflow discards events that look
automated so bots do not inflate your person counts — and curl’s default, or no
header at all, counts as automated. Without one this request still answers
202 and the event is silently dropped. Avoid bot, crawler, curl/,
python-requests and similar (full list: packages/core/src/enrich/bots.ts).
4. See your data
The CLI wraps the read endpoints. It is already built inside the running container, so give yourself a shorthand:
lyraflow() {
docker compose exec \
-e LYRAFLOW_HOST=http://localhost:3000 \
-e LYRAFLOW_SERVER_KEY="$LYRAFLOW_SERVER_KEY" \
lyraflow node packages/cli/dist/index.js "$@"
}
Then:
lyraflow stats --since 24h --by-event # how many of each event, per hour
lyraflow events --since 1h # the raw feed, newest first
lyraflow events --follow # watch them arrive live
lyraflow persons get user-42 # one person's stitched profile
Every command takes --json for scripts and agents; the table output is for
humans and is not a stable interface. Full reference:
packages/cli/README.md.
Or watch the same feed in a browser: sign in at http://localhost:3000 — see
Web UI.
Nothing to look at yet? Demo data fills a project with synthetic history so the screens have something to show.
That is the whole loop — instrument, send, read. Everything below is detail on each part.
This page is the Getting started section of the product
README at v0.15.0. It is generated from that file rather than
written here, so a correction belongs upstream.