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 partWhat 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

FileWhat it is
sumimiThe 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-server
sumimi-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:

CommandWhat it does
sumimi service startStarts the supervisor, which starts the server. It refuses to start twice.
sumimi service statusWhether it is running, for how long, how many times it has been restarted, where the log is.
sumimi service stopAsks the server to shut down gracefully — the writer flushes, the instance store is checkpointed — and waits for it.
sumimi service restartStops 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:

FlagEnvironment variableDefaultMeaning
--dir <path>SUMIMI_DIRsumimiThe instance directory: the events, the accounts, the log, the imports and the cache all live inside it.
--db <path>SUMIMI_DB<dir>/data.duckdbPath of the data file — for keeping the events on a bigger disk than the rest.
--backend <name>SUMIMI_BACKENDduckdbThe 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_HOST0.0.0.0Address to bind to.
--port <port>SUMIMI_PORT3000Port to listen on.
--jwt-secret <secret>SUMIMI_JWT_SECRETgeneratedSecret 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_DAYS0Default retention window in days. 0 means keep forever.
--country-header <name>SUMIMI_COUNTRY_HEADERnoneHeader carrying the country code, e.g. CF-IPCountry.
--js-dir <path>SUMIMI_JS_DIRjsDirectory holding the dashboard frontend files. Only used when SUMIMI_APP_URL=/app makes the backend serve the frontend itself.
--docs-dir <path>SUMIMI_DOCS_DIRdocsDirectory holding the documentation site served at /docs.
--ssl <domain>SUMIMI_SSL_DOMAINnoneServe HTTPS with a certificate obtained and renewed automatically over ACME. --ssl-domain is accepted as an alias.
--ssl-email <address>SUMIMI_SSL_EMAILnoneContact address registered with the ACME provider. Let's Encrypt sends expiry notices there.
--ssl-cache-dir <path>SUMIMI_SSL_CACHE_DIR<instance directory>/acmeDirectory holding the ACME account and the certificates. Back it up.
--ssl-port <port>SUMIMI_SSL_PORT443Port 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 to 0 to 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 to 0 to 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 to 0 to keep the host alone, which is what the referrer breakdown groups by anyway.
  • SUMIMI_APP_URL — base URL of the dashboard frontend (sumimi-app.js and sumimi.css). It defaults to https://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 /app to serve the frontend from the backend's own js/ 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 (default min(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 (default 100000) — how many events may be buffered in memory before the oldest are dropped.
  • SUMIMI_FLUSH_INTERVAL_SECS (default 2) — 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 .env files 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 (default 300) — how often the uptime of the websites is checked. 0 turns the checker off.
  • SUMIMI_UPTIME_TIMEOUT_SECS (default 10) — how long a website has to answer before it counts as down.
  • SUMIMI_CACHE_STALE_SECS (default 30) — 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. 0 means "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

Sumimi is an independent open-source project, an open-source alternative to Umami that implements the Umami v2 HTTP API. Apache-2.0 licensed.