Self-Hosted Analytics with Umami on Fly.io

Jul 23, 2026

5 min read

title image

Google Analytics? No thanks. I wanted an analytics tool that:

After some research, I landed on Umami and self-host it on Fly.io.

Why Not Google Analytics?

  1. Privacy: GA tracks more than you need and shares data with Google
  2. Cookie banners: GA sets cookies, you need consent
  3. Complexity: GA4 is bloated for simple metrics
  4. GDPR: Legally questionable in the EU

Why Not Plausible, Fathom & Co?

Plausible and Fathom are good privacy-first alternatives, but:

As an indie dev with multiple projects, that adds up. I wanted a solution that costs nothing.

Umami: The Open-Source Alternative

Umami is:

What Umami Tracks

What Umami Does NOT Track

Deployment on Fly.io

Fly.io is perfect for self-hosting: generous free tier, easy deployment, and locations worldwide.

1. Install Fly CLI

# macOS
brew install flyctl
 
# Or directly
curl -L https://fly.io/install.sh | sh

2. Create Fly App

fly launch --image ghcr.io/umami-software/umami:postgresql-latest

Fly asks for app name and region. I use analytics as the name and fra (Frankfurt) as the region.

3. PostgreSQL Database

Umami needs a PostgreSQL database. My first instinct was Fly's managed Postgres — one command, zero thinking:

fly postgres create --name analytics-db
fly postgres attach analytics-db --app analytics

It worked, but the bill didn't: $38/month for a database this small. I migrated to a plain, unmanaged Postgres app instead:

# Create a minimal Postgres app (not Fly's managed offering)
fly apps create analytics-db
fly volumes create analytics_db_data --app analytics-db --region fra --size 1
 
# Deploy postgres:16-alpine with a small config (256MB, shared-cpu-1x)
fly deploy --config fly.postgres.toml --app analytics-db
 
fly secrets set DATABASE_URL="postgresql://umami:<password>@analytics-db.internal:5432/umami" \
  --app analytics

Same database, same reliability, ~$2/month instead of $38. Connect over .internal, not .flycast — the latter needs an extra [[services]] block in the config for no benefit here.

4. Set App Secret

Umami needs an APP_SECRET for session encryption:

fly secrets set APP_SECRET="$(openssl rand -hex 32)"

5. Deploy

fly deploy

After a few minutes, Umami runs at https://analytics.fly.dev.

6. Custom Domain (Optional)

I use analytics.herrlichdigital.de:

fly certs create analytics.herrlichdigital.de

Then set DNS CNAME to analytics.fly.dev.

First Login

Umami automatically creates an admin user:

Change it immediately after first login!

Add a Website

  1. Settings → Websites → Add website
  2. Enter name and domain
  3. Copy website ID

Integration

One script tag per project:

<script
  defer
  src="https://analytics.herrlichdigital.de/script.js"
  data-website-id="736b294f-2b1e-4f76-9fba-c898539330f5"
/>

React/Remix Integration

In your root.tsx:

export default function Root() {
  return (
    <html>
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
        <script
          defer
          src="https://analytics.herrlichdigital.de/script.js"
          data-website-id="736b294f-2b1e-4f76-9fba-c898539330f5"
        />
      </head>
      <body>
        <Outlet />
        <Scripts />
      </body>
    </html>
  );
}

Track Only in Production

{
  process.env.NODE_ENV === "production" && (
    <script
      defer
      src="https://analytics.herrlichdigital.de/script.js"
      data-website-id="736b294f-2b1e-4f76-9fba-c898539330f5"
    />
  );
}

Custom Events

Umami supports custom events for button clicks, form submissions, etc:

// Global function from Umami
declare function umami(eventName: string): void;
 
// Usage
<button onClick={() => umami("signup-clicked")}>
  Sign Up
</button>

Cost

| Service | Cost/Month | | ------------------------------------ | ---------- | | Fly.io App (shared-cpu-1x, 1GB) | ~$5 | | Fly.io Postgres (self-hosted, 256MB) | ~$2 | | Total | ~$7 |

Not free, but a fraction of what a managed database would cost — my first Postgres setup (Fly's managed offering) alone was $38/month for the database. Self-hosting Postgres too, instead of just the app, is what actually makes this cheap.

For comparison:

Dashboard

Umami's dashboard is clean and shows exactly what you need:

No 47 reports that nobody needs. No ML-powered insights. Just numbers.

Conclusion

Self-hosting Umami was one of the best decisions for my projects:

  1. Privacy: No data with third parties
  2. Cost: ~$7/month instead of $9-14/month, and no per-visitor pricing to worry about as it grows
  3. Simplicity: Shows what matters, nothing more
  4. GDPR: No cookie banner, no legal stress

The only downside: You're responsible for uptime. But Fly.io is stable and Umami has minimal requirements.

I track Sharry, herrlichdigital.de, and all future projects with it. One Umami instance for everything.

Questions? Find me on X @pr0gstar.