Skip to content
Deployments

Deploy Umami with the analytics-umami template

Deployment

Deploy Umami with the analytics-umami template

Stand up Umami, an open-source web and product analytics tool, on a single Quake AI instance using the validated OpenTofu template 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.

YouSite visitorYour web appFloating IPUbuntu instanceCaddyreverse proxyUmamidashboard + tracking APIPostgreSQL proxies 443 to 3000stores eventsHTTPS dashboardloads tracking scriptpage views + events
Click to zoom
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

Monthly cost estimate

Pricing calculator ↗

Sized as a custom package on shared vCPU.

Starting template$32.00/mo

Monthly total for the required template above. Use the configurator below to add optional pieces and see the total update.

What each resource is for

Umami analytics host

s1a.medium · 4 shared vCPU, 4 GiB RAM, 0.5 Gbps

Runs Umami in Docker (the analytics dashboard and the tracking API your sites send events to). In bundled mode a PostgreSQL container runs alongside it; the database lives on an attached volume.

Umami plus a bundled PostgreSQL runs on 4 vCPU and 4 GiB RAM. In external-database mode the app alone fits a smaller flavor.

$33.00/mo

Compute shown per role at custom-package rates ($29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM). The headline above is the billed total: the cheaper of a named plan and the custom package, plus add-ons.

Included in baseline

s1a.medium

4 shared vCPU, 4 GiB RAM, 0.5 Gbps

$33.00

Compute + RAM rate basis

4 vCPU + 4 GiB RAM at $29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM (regular). Totals apply the flat −$5/mo package promotion.

—

Block storage (50 GiB)

50 GiB at $0.08/GiB/mo

$4.00

Public IP (included)

1 included with the custom package

$0.00

Package promotional discount

Flat −$5.00/mo on the custom package (same promotion as named plans).

$-5.00

Included at no charge

These line items are zero on Quake AI. Many other providers meter them separately.

Data transfer (inbound and outbound)

Unlimited data transfer on every plan; Quake AI does not meter per-GB egress.

AWS, GCP, and Azure meter outbound transfer per GB. DigitalOcean and Hetzner include an allowance on compute plans, then charge overage.

Learn more
$0.00

Private networking

Private networks, subnets, Neutron routers, and security groups are included with the plan.

VPC objects are usually free to create elsewhere, but NAT gateways bill hourly plus per-GB processed. Quake AI uses router SNAT with no separate NAT line item.

$0.00

Control-plane API requests

OpenStack API calls for provisioning and management are included.

Some managed services on other clouds meter API calls or charge for premium control-plane features.

$0.00

Pricing data last validated: . For current rates, check quake.ai/pricing.

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.
  • 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.
  • 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 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.

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 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. Wait until it resolves:
bash
dig +short analytics.example.com
  1. SSH to the instance and create /opt/umami/Caddyfile:
analytics.example.com {
  reverse_proxy 127.0.0.1:3000
}
  1. 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:
  1. 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. 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 is a self-hosted, Sentry-SDK-compatible error tracker. Apply the dedicated glitchtip template on its own instance (it needs PostgreSQL and Redis, similar to the heavier stacks; see the GlitchTip deployment walkthrough) and point a Sentry SDK at its DSN:

ts
import * as Sentry from "@sentry/nextjs";

Sentry.init({
  dsn: "https://[email protected]/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 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#

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.

Before this
Was this page helpful?