Umami is privacy-friendly web analytics: which pages people read, where they came from, and which country, browser and device they use. It sets no cookies on your visitors, does not follow them from site to site, and keeps every page view in a Postgres database you own rather than on someone else's servers.
A thin wrapper, not a fork. None of Umami's code is committed here. The version is pinned in
one place, package.json:
"umami": { "version": "3.4.0" }During the build, build-umami.js downloads that release's source from GitHub, builds it the
way Umami's own Docker image is built, and keeps only what the server needs to run. At runtime
start.js checks the database, applies Umami's migrations and starts Umami's server.
| File | What it does |
|---|---|
build-umami.js |
Downloads and builds the pinned release (npm run build) |
start.js |
Setup page, database check and migrations, then Umami's server |
db.js |
Makes velixir's DATABASE_URL work with Umami's database clients |
admin.js |
Replaces the default admin login before anyone can use it |
nixpacks.toml |
Starts the container with node start.js |
Upgrading is a version bump. Umami's dashboard tells you when a new version is out. Change
umami.version, commit and deploy, and the first boot on the new version applies Umami's
database migrations. Read the release notes
first: a new major version can change more than the dashboard.
Deploy it from the gallery. velixir creates a managed Postgres, binds it to the app as
DATABASE_URL, and holds the first deploy until the database is running, so Umami starts
connected and creates its tables on the first boot.
Out of the box, Umami has one user, admin, with the password umami. On a public URL that
would let anyone in, so this template never lets it through: before Umami's server starts for
the first time, the password is replaced with a random one and printed once to the app's logs.
Umami admin login. It is shown once, so save it now:
username: admin
password: xxxxxxxxxxxxxxxxxxxx
Open the app's logs, sign in at https://your-app.velixir.run, and change the password under
Settings, Profile, Change password. The check runs on every boot, so the password umami
never survives a restart.
To choose the password yourself, set UMAMI_ADMIN_PASSWORD (at least 8 characters) on the
app's Environment tab before the first deploy. While that variable is set it wins on every
boot, so a password changed inside Umami is put back on the next restart. Remove the variable
once you would rather manage the password in Umami. It is also how you get back in if the
printed password is lost: set it and redeploy.
After deploying your own copy with velixir deploy:
- Create a managed Postgres from the Databases page, or run
velixir db create. - Bind it to the app as
DATABASE_URL: on the database's page under Bound apps, orvelixir db bind <database-id> --app <app-id>. - Redeploy, with the Redeploy button on the live release in the Deploys tab.
Until a database is bound, the app serves a page that says exactly this, rather than failing.
In Umami, open Settings, Websites, Add website, enter the site's domain, then open its tracking code. It looks like this:
<script defer src="https://your-app.velixir.run/script.js" data-website-id="your-website-id"></script>Put it in the <head> of every page. Page views show up on the dashboard within seconds. To
count clicks and sign-ups as well, add data-umami-event="Signup" to a button, or call
umami.track('Signup') from your own code. See Umami's
tracker docs for the rest.
Some ad blockers block analytics scripts by name. Setting TRACKER_SCRIPT_NAME=stats.js on the
Environment tab serves the same script at /stats.js as well.
npm install
npm run build
DATABASE_URL=postgres://user:pass@localhost:5432/umami npm startThen open http://localhost:8080 and sign in with the login printed in the output. The build
downloads Umami and its dependencies and takes a few minutes. It needs Linux or macOS (use WSL
on Windows), Node 22 or newer and about 2 GB of free disk. With no sslmode in the URL the
connection is plain TCP, which is what a local Postgres expects.
It needs 512 MB of memory. In our tests Umami used about 160 MB just after starting, around 360 MB once it was serving a few requests a second, and peaked at 460 MB under a sustained 85 requests a second. At 256 MB it was killed for running out of memory. So it does not run on the Free plan: Hobby (512 MB) suits most sites, Starter (1 GB) a busy one or a dashboard many people use at once.
Visits are counted around the clock. Only the Free plan sleeps when idle, and Umami needs more memory than Free has, so it stays up. That matters for analytics: an app that had to wake for the first visitor after a quiet spell would miss that visit whenever the visitor left before it woke.
Each deploy builds Umami from source. That takes about three minutes in our tests, and the build needs internet access: GitHub, the npm registry and Prisma's download server.
No email. Umami does not send email, so there is no emailed password reset. If the admin
password is lost, use UMAMI_ADMIN_PASSWORD as described above.
Two-factor sign-in needs a key. Umami encrypts two-factor secrets with
TWO_FACTOR_ENCRYPTION_KEY. Set it to 64 hexadecimal characters (openssl rand -hex 32) to
turn the option on, and never change it afterwards: users who set up two-factor sign-in could
not sign in without it.
Rotating the database password signs everyone out once. Umami signs logins with
APP_SECRET. When you do not set one, this template derives it from DATABASE_URL, so it is
stable across restarts and replicas with nothing to configure, and changes when the database
password does. Set APP_SECRET yourself to pin it.
Visitor locations come from GeoLite2. Countries and cities are looked up in MaxMind's free GeoLite2 database, which the build downloads the same way Umami's own build does. It is as current as your last deploy.
Everything lives in Postgres. Umami keeps nothing on the container's disk, so redeploys and restarts lose nothing, and your analytics are backed up with the managed database.
Health checks. If you turn them on for the app, use /healthz. It answers 200 while Umami
is running and while the setup page is showing, and 503 while Umami starts or waits for its
database.
Umami is MIT licensed, by Umami Software, Inc. This wrapper is MIT too. The GeoLite2 database is MaxMind's, under its own licence.