Install
Two ways to run it: a container, or one file. Either way you get the same server — and either way it asks Let's Encrypt for its own certificate, so there is no certbot, no renewal cron and no reverse proxy to set up before the first page view.
1. Docker — the default
One command, one volume, one container. The image carries the executable, the dashboard frontend and this documentation — the same bundle the release archive ships.
docker run -d --name sumimi \
--restart unless-stopped \
-p 3000:3000 -p 443:443 \
-v sumimi-data:/data \
-e SUMIMI_SSL_DOMAIN=stats.example.com \
-e SUMIMI_SSL_EMAIL=you@example.com \
ghcr.io/sumimi-dev/sumimi:latest
What each part is for:
| The part | What it does |
|---|---|
--restart unless-stopped |
Docker brings the container back when it dies. This is the same job the supervisor does outside Docker — the server is restarted, not left dead. |
-v sumimi-data:/data |
The instance directory: the events, the accounts, the certificates. This is the one thing to back up, and the one thing to keep when you move the server. |
-p 3000:3000 |
The dashboard and the API. Put a reverse proxy in front of it if you want the public port to be 443 only. |
-p 443:443 |
Needed for the certificate: Let's Encrypt's TLS-ALPN-01 challenge has to reach the process. Leave it out only if you terminate TLS somewhere else. |
SUMIMI_SSL_DOMAIN |
The domain the certificate is issued for. It has to point at this host. Leave it out and the server speaks plain HTTP. |
Or with compose, which is the same thing in a file:
docker compose up -d
The container runs as an unprivileged user (uid 10001) and reads its own
memory limit rather than the host's, so a container limited to 512 MB sizes itself
for 512 MB. The dashboard frontend is served by the container itself
(SUMIMI_APP_URL=/app), so the whole thing works with no outbound request
at all. And the allocator is already set
(MALLOC_MMAP_THRESHOLD_=65536), which is worth
12% to 21% of the peak — more than any other setting in this file.
The image is not published yet
The workflow that publishes it is in the repository
(.github/workflows/publish-container.yml) and runs on every release tag.
Until the first tag has gone through, build it yourself — it takes a while the first
time, because DuckDB is compiled from source:
docker build -t sumimi:local .
docker run -d --name sumimi -p 3000:3000 -v sumimi-data:/data sumimi:local
2. The binary — no container runtime
If you would rather not run a container, the whole product is one executable. DuckDB is
linked statically: its engine is compiled into the binary, so there is
no libduckdb.so to ship, no LD_LIBRARY_PATH to set and no
loader configuration to do.
Get the release archive
| File | What it is |
|---|---|
sumimi | The one executable: the analytics server (collection API, statistics API, authentication, the dashboard shell and the tracker) and the analysis CLI. A self-contained executable — DuckDB is compiled into it. sumimi serve … runs the server, every other subcommand is the analyser. |
sumimi-serversumimi-client | Symlinks to sumimi, for people who prefer the explicit name. The program decides its mode by the name it was invoked under, so sumimi-server serve and sumimi-client summary both work. There is nothing extra to download. |
js/ | The dashboard frontend files (sumimi-app.js, sumimi.css) and a copy of the tracker. The frontend is served from a CDN by default, and from here when you set SUMIMI_APP_URL=/app; the tracker itself is compiled into the server binary and always served by the backend. |
docs/ | This documentation site, served by the server at /docs. |
Linux (x86-64)
curl -fsSL https://github.com/sumimi-dev/sumimi/releases/latest/download/sumimi-linux-amd64.tar.gz | tar xz
ls -l
You should now see sumimi, the sumimi-server and sumimi-client symlinks, js/ and docs/.
macOS (Apple silicon)
curl -fsSL https://github.com/sumimi-dev/sumimi/releases/latest/download/sumimi-macos-arm64.tar.gz | tar xz
The same layout, and the same rule: the executables are self-contained on macOS too, so there is nothing to copy next to them.
Windows
curl -fsSL https://github.com/sumimi-dev/sumimi/releases/latest/download/sumimi-windows-amd64.zip -o sumimi.zip
Expand-Archive sumimi.zip
Build from source
If you would rather compile it — to match an unusual architecture, for example — you need a Rust toolchain. The first build compiles DuckDB from source and takes a while; afterwards it is quick.
git clone https://github.com/sumimi-dev/sumimi.git
cd sumimi
cargo build --release
# the executable lands in target/release/sumimi
Install the executable
# the one executable: the server and the analysis CLI
sudo install -m 0755 sumimi /usr/local/bin/
# optional: the explicit-name symlinks, if you prefer them
sudo ln -sf /usr/local/bin/sumimi /usr/local/bin/sumimi-server
sudo ln -sf /usr/local/bin/sumimi /usr/local/bin/sumimi-client
# optional: the js/ and docs/ directories, for the dashboard frontend
# served by the backend itself and for the docs site at /docs
sudo install -d -m 0755 /opt/sumimi
sudo cp -r js docs /opt/sumimi/
The js/ and docs/ directories are optional. You need
js/ only if you serve the dashboard frontend from the backend yourself
(SUMIMI_APP_URL=/app); by default it comes from a CDN. docs/ is
this documentation site, served at /docs. The tracker at
/sumimi.js is compiled into the server binary, so it is available either way.
Want a smaller binary instead?
There is an optional dynamic build: make build-small links the prebuilt
DuckDB library instead of compiling it in, which gives you a noticeably smaller
executable — at the price of one extra file to ship. In that case
libduckdb.so (or libduckdb.dylib on macOS) has to sit next to
the binary, and the loader has to be told where to find it. The default release is
static, so this only matters if you build from source.
3. Run it — as a service, without root
sumimi service start runs the server under a supervisor: a
second process that owns the server and restarts it when it dies. Two processes, because
a process that the kernel kills — the OOM killer, a segfault — cannot restart itself, and
the whole point of the promise is that a traffic spike does not take the box with it.
There is nothing to install for it. No systemd unit, no init script, no root: the supervisor is the same executable, started in its own process group, and everything it owns lives inside the instance directory — so it runs as the user who installed it.
# start it, with a domain: the certificate is issued for it
sumimi service start --dir ~/sumimi \
--domain stats.example.com --email you@example.com
# what is it doing?
sumimi service status --dir ~/sumimi
# stop it
sumimi service stop --dir ~/sumimi
The four commands are the whole interface:
| Command | What it does |
|---|---|
sumimi service start | Starts the supervisor, which starts the server. It refuses to start twice. |
sumimi service status | Whether it is running, for how long, how many times it has been restarted, where the log is. |
sumimi service stop | Asks the server to shut down gracefully — the writer flushes, the instance store is checkpointed — and waits for it. |
sumimi service restart | Stops it and starts it again, with the same arguments. |
Three files live inside the instance directory:
~/sumimi/
├── data.duckdb the events
├── instance.sqlite the accounts, the log, the imports, the cache
├── acme/ the TLS certificate cache
├── jwt-secret the signing secret
└── service/
├── status.json the state of the service, rewritten every heartbeat
├── log the server's output
└── stop the stop request
The server's output goes to service/log, so tail -f on it is
the log. If the server keeps dying, the supervisor says so in the status
(restarts) and gives up after five failures in a row rather than restarting
a broken thing forever.
Prefer systemd, or want to see the server in the foreground?
Both are fine. sumimi serve --dir ~/sumimi --domain stats.example.com
runs the server in the foreground, and the
self-hosting guide has a systemd unit for the people who
want their server managed by the system.
4. The certificate
This is the part that usually takes the longest on a cheap VPS, so Sumimi does it
itself. With a domain, the server asks Let's Encrypt for a certificate
over ACME and renews it in the background — no certbot, no renewal cron job, no
acme.sh, no reverse proxy whose only job is TLS.
# in the foreground
sumimi serve --dir ~/sumimi --ssl stats.example.com --ssl-email you@example.com
# or as a service
sumimi service start --dir ~/sumimi \
--domain stats.example.com --email you@example.com
Two things have to be true: the domain has to point at the host, and port 443 has to reach the Sumimi process — the challenge is TLS-ALPN-01, so the handshake itself has to arrive. Behind a reverse proxy that terminates TLS, use the proxy's own ACME support instead; the self-hosting guide has both.
The certificate cache lives in acme/ inside the instance directory — back it
up with the rest, or the server has to ask for a new one.
5. Create the admin account
The server has one user: the administrator. Create it before the first run so the password is one you chose.
sumimi admin --dir ~/sumimi \
--username admin --password 'a-long-passphrase'
If you leave --password out, a strong password is generated and printed
once:
$ sumimi admin --dir ~/sumimi --username admin
admin user 'admin' ready
password: 8f3c1b9d0e7a4f26b5c8d1e3a9f7b2c4
(store it now — it is not shown again)
If you skip this
The first serve on an empty database creates the admin user anyway, using
SUMIMI_ADMIN_USER (default admin) and
SUMIMI_ADMIN_PASSWORD. If the password is unset, one is generated and
printed to the log exactly once. It is never stored in plaintext.
6. The flags
The flags you will use most:
| Flag | Environment variable | Default | Meaning |
|---|---|---|---|
--dir <path> | SUMIMI_DIR | sumimi | The instance directory: the events, the accounts, the log, the imports and the cache all live inside it. |
--db <path> | SUMIMI_DB | <dir>/data.duckdb | Path of the data file — for keeping the events on a bigger disk than the rest. |
--backend <name> | SUMIMI_BACKEND | duckdb | The data engine: duckdb or sqlite. Both store the same tables and answer the same reports; DuckDB is smaller and faster on the heavy aggregations and it costs more memory (its ingestion is the peak, and a long aggregate grows with the range), SQLite is an ordinary file that many readers can open at once and it stays small. |
--host <addr> | SUMIMI_HOST | 0.0.0.0 | Address to bind to. |
--port <port> | SUMIMI_PORT | 3000 | Port to listen on. |
--jwt-secret <secret> | SUMIMI_JWT_SECRET | generated | Secret used to sign tokens. A random one is generated and stored in the instance directory on first run if unset. |
--retention-days <n> | SUMIMI_RETENTION_DAYS | 0 | Default retention window in days. 0 means keep forever. |
--country-header <name> | SUMIMI_COUNTRY_HEADER | none | Header carrying the country code, e.g. CF-IPCountry. |
--js-dir <path> | SUMIMI_JS_DIR | js | Directory holding the dashboard frontend files. Only used when SUMIMI_APP_URL=/app makes the backend serve the frontend itself. |
--docs-dir <path> | SUMIMI_DOCS_DIR | docs | Directory holding the documentation site served at /docs. |
--ssl <domain> | SUMIMI_SSL_DOMAIN | none | Serve HTTPS with a certificate obtained and renewed automatically over ACME. --ssl-domain is accepted as an alias. |
--ssl-email <address> | SUMIMI_SSL_EMAIL | none | Contact address registered with the ACME provider. Let's Encrypt sends expiry notices there. |
--ssl-cache-dir <path> | SUMIMI_SSL_CACHE_DIR | <instance directory>/acme | Directory holding the ACME account and the certificates. Back it up. |
--ssl-port <port> | SUMIMI_SSL_PORT | 443 | Port used for HTTPS when --ssl is set. |
Two more settings have no flag — they are environment-only because they are per-site policy rather than per-server:
SUMIMI_KEEP_QUERY— whether to store the query string of the collected URLs. It is on by default, because that is what Umami stores and the marketing parameters (UTM, ad click ids) live in it — they are parsed out of it either way. Set it to0to keep the parameters but drop the raw query string, which is where session tokens and other personal data tend to be.SUMIMI_STORE_TITLES— whether to store the page title. It is on by default (Umami stores it); set it to0to save the space — the title is roughly half of the stored bytes and almost nobody looks at it.SUMIMI_STORE_FULL_REFERRER— whether to store the referring URL in full. It is on by default; set it to0to keep the host alone, which is what the referrer breakdown groups by anyway.SUMIMI_APP_URL— base URL of the dashboard frontend (sumimi-app.jsandsumimi.css). It defaults tohttps://cdn.jsdelivr.net/gh/sumimi-dev/sumimi@main/js, so the frontend is served from a CDN out of the box and the backend only ships a tiny HTML shell. Set it to/appto serve the frontend from the backend's ownjs/directory instead — that is what you want on an air-gapped machine, and what the container image does. The tracker (sumimi.js) is served by the backend itself and is unaffected by this setting.
A few more environment variables tune the process rather than the data:
SUMIMI_DUCKDB_MEMORY_LIMIT(defaultmin(256MB, RAM / 4)) — DuckDB's memory limit, in bytes. A 512 MB box gets 128 MB, a 1 GB box gets 256 MB, and nothing gets more than 256 MB. It is a ceiling, not an allocation: the whole process stays under 256 MB on the smallest supported machine, and under 512 MB on a 1 GB one — both are asserted on every CI run.SUMIMI_MAX_QUEUE(default100000) — how many events may be buffered in memory before the oldest are dropped.SUMIMI_FLUSH_INTERVAL_SECS(default2) — how often the writer commits a batch to disk.-
The settings were called
SUMAMI_*until v0.2.0. The old names still work — they are in people's.envfiles and systemd units — but the new names win when both are set. Rename them the next time you touch the file. SUMIMI_UPTIME_INTERVAL_SECS(default300) — how often the uptime of the websites is checked.0turns the checker off.SUMIMI_UPTIME_TIMEOUT_SECS(default10) — how long a website has to answer before it counts as down.SUMIMI_CACHE_STALE_SECS(default30) — how old a cached report may be. The writer commits every couple of seconds, so a cache that only accepted the current version of the data would never hit on a busy site; with the window, the numbers on the screen are at most that old and the query runs once instead of a hundred times.0means "only while the data has not changed".SUMIMI_JWT_SECRET_FILE— path of a file holding the token secret, if you would rather keep it out of the environment.MALLOC_MMAP_THRESHOLD_=65536— not a Sumimi setting, and worth more than all the others together: it tells glibc to give the pages of a large freed block back to the operating system instead of keeping them, which is −12% to −21% of the peak for about 5% of the throughput. The container image sets it; on a bare-metal box, set it too. It does nothing on macOS.
The admin subcommand has its own flags — --username
(SUMIMI_ADMIN_USER, default admin),
--password (SUMIMI_ADMIN_PASSWORD) and --db
(SUMIMI_DB). A third subcommand, config, prints the resolved
configuration and exits.
7. Create a website
Log in and create the website you want to measure. The website id is what the tracker needs.
# log in
curl -s -X POST http://127.0.0.1:3000/api/login \
-H 'content-type: application/json' \
-d '{"username":"admin","password":"a-long-passphrase"}'
# {"token":"eyJ...","user":{"id":"...","username":"admin","role":"admin",...}}
# create a website
curl -s -X POST http://127.0.0.1:3000/api/websites \
-H 'content-type: application/json' \
-H "authorization: Bearer $TOKEN" \
-d '{"name":"My Blog","domain":"blog.example.com"}'
# {"id":"3f2a...","websiteId":"3f2a...","name":"My Blog",...}
The domain field is optional but useful: when set, the tracker can be
configured with the domain instead of the id, and the server can reject hits that arrive
for a website it does not know.
8. Install the tracker
The server serves the tracker itself at /sumimi.js — it is compiled into the
binary, so it is always available whatever you set SUMIMI_APP_URL to. Put
this in the <head> of every page you want to measure:
<script defer
src="https://your-sumimi-host/sumimi.js"
data-website-id="3f2a..."></script>
That is the whole integration. A page view is sent automatically on load. There are more ways to install it — self-hosted, on a CDN, for several sites at once — and they are all in the tracker guide.
9. Look at the numbers
Open http://127.0.0.1:3000/ and log in with the admin account. The dashboard
is a small HTML shell that loads the frontend JavaScript. By default that frontend comes
from a CDN (https://cdn.jsdelivr.net/gh/sumimi-dev/sumimi@main/js), so the
backend ships no frontend code; set SUMIMI_APP_URL=/app to serve it from the
backend's own js/ directory instead. The tracker at
/sumimi.js is always served by the backend itself.
You can also read the same numbers straight from the database file:
sumimi summary --db ~/sumimi/data.duckdb 3f2a...
sumimi pages --db ~/sumimi/data.duckdb 3f2a... --limit 10
The CLI reference lists every subcommand and every flag.
Just want to look at it first?
You do not need a website to see the dashboard working. sumimi seed fills a
database with synthetic demo traffic — invented pages, invented visitors, a plausible
traffic shape — so you can evaluate the whole thing without pointing a real site at it:
# the server must be stopped while the database is written
sumimi seed --db ./demo.duckdb --days 30
# then start it on the demo database
sumimi serve --db ./demo.duckdb --port 3000
Open http://127.0.0.1:3000/ and sign in with the admin account (the first
start prints its password once). The seeded website is called "Example Blog". The
CLI reference describes the flags.
Next steps
- Self-host on a cheap VPS — the service, TLS, the firewall and backups.
- Tracker options — custom events, self-hosting, CDN, multiple sites.
- Privacy and data protection — what is stored, what is refused, how to erase it.
- FAQ — common problems and how to solve them.
Sumimi is an independent open-source project, an open-source alternative to Umami that implements the Umami v2 HTTP API. Apache-2.0 licensed.