# Deploy Umami with the analytics-umami template

Source: https://docs.quake.ai/resources/deployments/deploy-analytics-umami-template
Markdown: https://docs.quake.ai/resources/deployments/deploy-analytics-umami-template.md

---

# Deploy Umami with the analytics-umami template

Stand up [Umami](https://umami.is), an open-source web and product analytics tool, on a single Quake AI instance using the [validated OpenTofu template](/docs/platform/validation#how-infrastructure-templates-are-checked) `analytics-umami`. You apply the template, reach the dashboard over the floating IP, change the default admin password, add a website, serve the host over HTTPS, wire a web app to send page views and events, and confirm the data arrives. An optional section adds error telemetry with GlitchTip and points you at the monitoring stack for infrastructure metrics.

Umami keeps your analytics data on infrastructure you own. You run it yourself; this is a self-hosted tool you operate, not a managed service.

<Figure size="md" caption="What you'll build: a Umami host on a single instance, reached over HTTPS through a Caddy reverse proxy, collecting page views and events from a web app you wire to it">

```d2
direction: right

dev: You {shape: person}
visitor: Site visitor {shape: person}
app: Your web app
fip: Floating IP
instance: Ubuntu instance {
  caddy: Caddy\nreverse proxy
  umami: Umami\ndashboard + tracking API
  db: PostgreSQL
  caddy -> umami: proxies 443 to 3000
  umami -> db: stores events
}

dev -> fip: HTTPS dashboard
visitor -> app: loads tracking script
app -> fip: page views + events
fip -> instance.caddy
```

</Figure>

<PricingCompanion
  components={[
    { kind: "template", slug: "analytics-umami", required: true },
  ]}
/>

## Prerequisites

You need:

- OpenTofu 1.6.0 or later (or Terraform 1.6.0 or later) installed locally.
- Your OpenStack credentials sourced into the shell (`source openrc.sh`). See [the OpenStack CLI guide](/docs/tools/openstack-cli).
- An SSH keypair that already exists in your project. Record its name for the `key_name` variable.
- A copy of the `analytics-umami` template directory from [the template reference page](/resources/iac-templates/analytics-umami).
- A domain you can point at the instance. Umami serves a tracking script that your sites load over HTTPS, so a domain with TLS is part of the normal setup, not an afterthought.
- A web app to instrument. The [Next.js app deployment](/resources/deployments/deploy-nextjs-app-template) gives you one if you do not already have a site running.
- Your workstation's public IP address. Find it with `curl -sS https://api.ipify.org`.

## Step 1: Set the variables and apply the template

The dashboard listens on port 3000 over plain HTTP. The template's security group restricts port 3000 to `dashboard_allowed_cidr`, which defaults to the private network only. To reach the dashboard from your workstation for first-boot setup, set `dashboard_allowed_cidr` to your own address.

Copy the template's example variables file and open it:

```bash
cp terraform.tfvars.example terraform.tfvars
```

Set `key_name` to the SSH keypair already in your project, and `dashboard_allowed_cidr` to your workstation's public IP with a `/32` suffix. Leave `db_mode` at its default `bundled` to run PostgreSQL on the same instance:

```hcl
key_name               = "YOUR_KEY_NAME"
dashboard_allowed_cidr = "YOUR_IP/32"
```

Initialize the working directory, preview the plan, and apply:

```bash
tofu init
tofu plan
tofu apply
```

OpenTofu provisions a private network, a router, a security group, a block volume mounted at `/var/lib/docker`, an instance, and a floating IP. On first boot, cloud-init mounts the data volume, installs Docker Engine, generates the app secret and database password into `/opt/umami/.env`, and starts Umami and PostgreSQL on port 3000.

When the apply finishes, read the outputs and record `floating_ip` and `dashboard_url`:

```bash
tofu output
```

## Step 2: Change the default admin password

Umami creates a default admin account (username `admin`, password `umami`) on first start. Change the password before you do anything else.

cloud-init takes a few minutes after the instance reaches `ACTIVE`. Open `dashboard_url` (for example `http://YOUR_FLOATING_IP:3000`). If the page does not load yet, watch the containers start over SSH:

```bash
ssh ubuntu@YOUR_FLOATING_IP "sudo docker compose -f /opt/umami/docker-compose.yml ps"
```

Sign in with `admin` / `umami`, then go to **Settings** > **Profile** and set a strong password.



The `admin` / `umami` default is documented and well known. Until you change it, anyone who can reach the dashboard can sign in. Keep the dashboard restricted to your IP or tunnel (this step) and change the password before you expose the host.



## Step 3: Add a website

1. Go to **Settings** > **Websites** > **Add website**.
2. Set a **Name** and the **Domain** of the site you will instrument (for example `app.example.com`).
3. Select **Save**, then open the website's **Edit** > **Tracking code**. Copy the script tag; it looks like:

```html
<script defer src="https://analytics.example.com/script.js" data-website-id="YOUR_WEBSITE_ID"></script>
```

The `src` host is your Umami host and the `data-website-id` identifies this site. You wire both into your app in step 5.

## Step 4: Serve the host over HTTPS with Caddy

Your sites load the tracking script and post events to this host, so it needs a stable HTTPS address. The template leaves ports 80 and 443 open for a reverse proxy. [Caddy](https://caddyserver.com) obtains and renews a TLS certificate automatically once a domain resolves to the instance.

1. Create a DNS **A record** for your analytics host (for example `analytics.example.com`) pointing at `YOUR_FLOATING_IP`. Follow [How to point a domain at a Quake AI resource](/docs/network/how-to/point-domain-to-quake-ai). Wait until it resolves:

```bash
dig +short analytics.example.com
```

2. SSH to the instance and create `/opt/umami/Caddyfile`:

```text
analytics.example.com {
  reverse_proxy 127.0.0.1:3000
}
```

3. Add Caddy to `/opt/umami/docker-compose.yml`:

```yaml
services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    network_mode: host
    volumes:
      - /opt/umami/Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
volumes:
  caddy_data:
```

4. Apply the changes:

```bash
cd /opt/umami
sudo docker compose up -d
```

Open `https://analytics.example.com` and confirm the padlock. For background, see [How to issue and auto-renew a TLS certificate with Let's Encrypt](/docs/network/how-to/lets-encrypt-certificate). Once HTTPS works, set `dashboard_allowed_cidr` back to the private network in `terraform.tfvars` and run `tofu apply`; the tracking script still loads over 443.

## Step 5: Wire a web app to send events

Add the tracking script to the app you want to measure. In a Next.js app, add it to the root layout so it loads on every page:

```tsx
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <script
          defer
          src="https://analytics.example.com/script.js"
          data-website-id="YOUR_WEBSITE_ID"
        />
      </body>
    </html>
  );
}
```

Deploy the change, then visit the site. To send a custom event from a click handler, call the tracker Umami exposes on the page:

```ts
window.umami?.track("signup", { plan: "starter" });
```

## Step 6: Verify the data arrives

1. Open your instrumented site in a browser and click around a few pages.
2. In the Umami dashboard, open the website you added. Within a minute, **Views** and **Visitors** increment and the page list shows the paths you visited.
3. If a custom event fired, it appears under the website's **Events** tab.

If no data arrives, confirm the script `src` resolves over HTTPS, the `data-website-id` matches the website in Umami, and no content blocker is dropping the request.

## Add error telemetry with GlitchTip

Page views answer "who visited"; error telemetry answers "what broke." [GlitchTip](https://glitchtip.com) is a self-hosted, Sentry-SDK-compatible error tracker. Apply the dedicated [glitchtip template](/resources/iac-templates/glitchtip) on its own instance (it needs PostgreSQL and Redis, similar to the heavier stacks; see the [GlitchTip deployment walkthrough](/resources/deployments/deploy-glitchtip-template)) and point a Sentry SDK at its DSN:

```ts

Sentry.init({
  dsn: "https://YOUR_KEY@glitchtip.example.com/1",
  tracesSampleRate: 0.1,
});
```

GlitchTip speaks the Sentry protocol, so the official Sentry SDKs report to it unchanged. This keeps error telemetry on infrastructure you own, the same way Umami keeps analytics on your own host.

For infrastructure metrics (CPU, memory, disk, and service health on your VMs), deploy the [monitoring stack](/resources/iac-templates/monitoring-stack) rather than duplicating that role here. Umami covers product analytics, GlitchTip covers errors, and the monitoring stack covers the machines.

## What you built

- **Applied the `analytics-umami` template** to provision a network, security group, data volume, instance, and floating IP, with Umami and a bundled PostgreSQL started by cloud-init
- **Changed the default admin password** and added a website
- **Served the host over HTTPS** through a Caddy reverse proxy so sites can load the tracking script
- **Wired a web app** to send page views and a custom event, and confirmed the data in the dashboard
- **Pointed at GlitchTip and the monitoring stack** for error telemetry and infrastructure metrics

## Scope of this deployment

This template runs a single-VM Umami host, not a managed analytics cloud. The instance is CPU-only and runs in one region. You operate the instance, Docker, Umami, the database, and the data volume yourself: back them up, patch them, and watch resource use as event volume grows. For higher volume, switch to external mode against a larger PostgreSQL and size the host up. GlitchTip and the monitoring stack run as their own deployments.

## Next steps

- [Umami analytics template](/resources/iac-templates/analytics-umami): the template reference, parameters, and resource map
- [Deploy a Next.js app](/resources/deployments/deploy-nextjs-app-template): the app to instrument if you do not have one
- [Deploy Uptime Kuma](/resources/deployments/deploy-uptime-kuma-template): add uptime monitoring and a public status page
- [Monitoring stack](/resources/iac-templates/monitoring-stack): Prometheus and Grafana for infrastructure metrics
- [Security hardening checklist](/docs/security/hardening-checklist): tighten SSH access and exposure before you serve real traffic

## Clean up

When you no longer need the deployment, destroy everything the template created:

```bash
tofu destroy
```

Then remove the DNS A record you created in step 4 and the tracking script from your app. Because Umami and its database live on the instance and its attached volume, `tofu destroy` removes the analytics data along with the infrastructure. Export any reports you want to keep before you destroy.
