From 9b737cd2aa3b3f1588d50fe24c811df2593ec6a6 Mon Sep 17 00:00:00 2001 From: Theo Gregory Date: Wed, 15 Apr 2026 18:10:41 +0200 Subject: [PATCH 1/4] Add Umami web analytics example Deploys Umami v3.0.3 as a Next.js standalone app on a FROM scratch image, backed by the existing postgres example. Includes a workaround for the missing pgcrypto extension (not needed on PG16+). Co-Authored-By: Claude Opus 4.6 (1M context) --- umami/.dockerignore | 1 + umami/Dockerfile | 54 +++++++++++++ umami/Kraftfile | 7 ++ umami/README.md | 185 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 247 insertions(+) create mode 100644 umami/.dockerignore create mode 100644 umami/Dockerfile create mode 100644 umami/Kraftfile create mode 100644 umami/README.md diff --git a/umami/.dockerignore b/umami/.dockerignore new file mode 100644 index 00000000..9ac9448d --- /dev/null +++ b/umami/.dockerignore @@ -0,0 +1 @@ +.unikraft diff --git a/umami/Dockerfile b/umami/Dockerfile new file mode 100644 index 00000000..ce015b7f --- /dev/null +++ b/umami/Dockerfile @@ -0,0 +1,54 @@ +FROM node:22-alpine AS build + +RUN apk add --no-cache libc6-compat git binutils + +WORKDIR /app + +# Clone pinned Umami release +ARG UMAMI_VERSION=v3.0.3 +RUN git clone --depth 1 --branch ${UMAMI_VERSION} https://github.com/umami-software/umami.git . + +# Install pnpm and dependencies +RUN npm install -g pnpm && pnpm install --frozen-lockfile + +# Copy Docker-specific middleware (disables auth middleware for container use) +RUN cp docker/middleware.ts src/middleware.ts + +# The Unikraft Cloud postgres example does not include the pgcrypto extension. +# PG16+ has gen_random_uuid() built-in, so the extension is not needed. +RUN sed -i 's/CREATE EXTENSION IF NOT EXISTS "pgcrypto";/-- pgcrypto: not needed on PG16+/' \ + prisma/migrations/01_init/migration.sql + +# Dummy DATABASE_URL required for prisma generate during build +ENV DATABASE_URL="postgresql://user:pass@localhost:5432/umami" +ENV NEXT_TELEMETRY_DISABLED=1 + +# Build: prisma client + tracker + geo database + Next.js app +RUN pnpm build-docker + +# Strip node binary (~20 MB savings) and remove GeoIP data (~54 MB savings) +RUN strip /usr/local/bin/node && rm -rf /app/.next/standalone/geo + +FROM scratch + +# Node binary (stripped) +COPY --from=build /usr/local/bin/node /usr/bin/node + +# System libraries +COPY --from=build /lib/ld-musl-x86_64.so.1 /lib/ld-musl-x86_64.so.1 +COPY --from=build /usr/lib/libgcc_s.so.1 /usr/lib/libgcc_s.so.1 +COPY --from=build /usr/lib/libstdc++.so.6 /usr/lib/libstdc++.so.6 + +# SSL certificates (needed for TLS database connections) +COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt + +# Distribution configuration +COPY --from=build /etc/os-release /etc/os-release + +# Next.js standalone output +COPY --from=build /app/.next/standalone /app/ +COPY --from=build /app/.next/static /app/.next/static +COPY --from=build /app/public /app/public + +ENV HOSTNAME=0.0.0.0 +ENV PORT=3000 diff --git a/umami/Kraftfile b/umami/Kraftfile new file mode 100644 index 00000000..f08151b3 --- /dev/null +++ b/umami/Kraftfile @@ -0,0 +1,7 @@ +spec: v0.6 + +runtime: base-compat:latest + +rootfs: ./Dockerfile + +cmd: ["/usr/bin/node", "/app/server.js"] diff --git a/umami/README.md b/umami/README.md new file mode 100644 index 00000000..59a5c057 --- /dev/null +++ b/umami/README.md @@ -0,0 +1,185 @@ +# Umami Web Analytics + +This guide shows you how to deploy [Umami](https://umami.is), an open-source, privacy-focused web analytics platform. + +Umami requires PostgreSQL. +This example deploys the [postgres](../postgres/) example as the database backend and runs Umami as a Next.js standalone application. + +To run it, follow these steps: + +1. Install the CLI and a container runtime engine, for example [Docker](https://docs.docker.com/engine/install/). + Use the [unikraft CLI](https://unikraft.com/docs/cli/unikraft) or the legacy [kraft CLI](https://unikraft.org/docs/cli/install). + +2. Clone the [`examples` repository](https://github.com/unikraft-cloud/examples) and `cd` into the `examples/umami/` directory: + +```bash +git clone https://github.com/unikraft-cloud/examples +cd examples/umami/ +``` + +Make sure to log into Unikraft Cloud and pick a [metro](https://unikraft.com/docs/platform/metros) close to you. +This guide uses `fra` (Frankfurt, 🇩🇪): + +```bash title="unikraft" +unikraft login +``` + +or + +```bash title="kraft" +# Set Unikraft Cloud access token +export UKC_TOKEN=token +# Set metro to Frankfurt, DE +export UKC_METRO=fra +``` + +## Step 1: Deploy PostgreSQL + +First, deploy a PostgreSQL instance using the [postgres](../postgres/) example: + +```bash title="unikraft" +cd ../postgres/ +unikraft build . --output /postgres:latest +unikraft run --metro=fra -p 5432:5432/tls -m 1G -e POSTGRES_PASSWORD= -e POSTGRES_DB=umami --scale-to-zero=off /postgres:latest +``` + +or + +```bash title="kraft" +cd ../postgres/ +kraft cloud deploy -p 5432:5432/tls -M 1G -e POSTGRES_PASSWORD= -e POSTGRES_DB=umami --scale-to-zero off . +``` + +Note the service FQDN from the output (e.g., `young-thunder-fbafrsxj.fra.unikraft.app`). + +## Step 2: Run Database Migrations + +Umami requires database tables to be created before first use. +Run migrations from your local machine using the Prisma CLI: + +```bash +# Clone Umami locally (only needed for the Prisma schema and migrations) +git clone --depth 1 --branch v3.0.3 https://github.com/umami-software/umami.git /tmp/umami-migrate +cd /tmp/umami-migrate + +# Install dependencies +npm install -g pnpm && pnpm install --frozen-lockfile + +# Patch pgcrypto (not available in the Unikraft postgres image, not needed on PG16+) +sed -i 's/CREATE EXTENSION IF NOT EXISTS "pgcrypto";/-- pgcrypto: not needed on PG16+/' \ + prisma/migrations/01_init/migration.sql + +# Run migrations +DATABASE_URL="postgresql://postgres:@:5432/umami?sslmode=require" \ + npx prisma migrate deploy +``` + +> **Note:** Use `?sslmode=require` when connecting over the public FQDN. + +## Step 3: Deploy Umami + +Return to the `umami/` example directory and deploy: + +```bash title="unikraft" +cd examples/umami/ +unikraft build . --output /umami:latest +unikraft run --metro=fra \ + -p 443:3000/tls+http \ + -m 1G \ + -e DATABASE_URL="postgresql://postgres:@:5432/umami?sslmode=require" \ + -e APP_SECRET="$(openssl rand -hex 32)" \ + -e DISABLE_TELEMETRY=1 \ + /umami:latest +``` + +or + +```bash title="kraft" +kraft cloud deploy \ + -p 443:3000 \ + -M 1024 \ + -e DATABASE_URL="postgresql://postgres:@:5432/umami?sslmode=require" \ + -e APP_SECRET="$(openssl rand -hex 32)" \ + -e DISABLE_TELEMETRY=1 \ + . +``` + +The output shows the instance address and other details: + +```ansi +[●] Deployed successfully! + │ + ├────────── name: umami-f7k2x + ├────────── uuid: c742ae33-d502-481c-b691-7732f00abf0c + ├───────── state: starting + ├──────── domain: https://blue-bush-59v2y072.fra.unikraft.app + ├───────── image: umami@sha256:d7f3221eee2ecbd8d90fcebe97eb7dc7f5989ee95c4cedd4a9fe728a709890b0 + ├──────── memory: 1024 MiB + ├─────── service: blue-bush-59v2y072 + ├── private fqdn: umami-f7k2x.internal + ├──── private ip: 10.0.8.193 + └────────── args: /usr/bin/node /app/server.js +``` + +Open the URL in a browser and log in with the default credentials: + +| Username | Password | +|----------|----------| +| `admin` | `umami` | + +**Change the default password immediately after first login.** + +## Environment Variables + +| Variable | Required | Description | +|----------|----------|-------------| +| `DATABASE_URL` | Yes | PostgreSQL connection string | +| `APP_SECRET` | Recommended | Secret for session encryption. Auto-generated if not set, but won't persist across restarts. | +| `DISABLE_TELEMETRY` | No | Set to `1` to disable Umami's telemetry | +| `COLLECT_API_ENDPOINT` | No | Custom tracker endpoint path (replaces `/api/send`) | + +## Upgrading + +To upgrade to a new Umami release: + +1. Update `UMAMI_VERSION` in the `Dockerfile` (e.g., `v3.0.3` to `v3.1.0`) +2. Run database migrations again (Step 2) with the new version +3. Redeploy (Step 3) + +Migrations are idempotent; already-applied migrations are skipped. + +## Notes + +- **No GeoIP location data**: The GeoLite2 database is removed from the image to reduce size (~54 MB). Umami will still track visits but won't resolve visitor locations. To re-enable, remove the `rm -rf /app/.next/standalone/geo` line from the `Dockerfile`. +- **Image size**: The `FROM scratch` image is ~320 MB. Umami requires at least 1024 MiB of memory to unpack the initramfs and run. +- **pgcrypto**: The Unikraft Cloud postgres example does not include the `pgcrypto` extension. This is not a problem because PostgreSQL 16+ provides `gen_random_uuid()` as a built-in function. The migration patch in Step 2 and in the `Dockerfile` comments out the `CREATE EXTENSION` statement. + +## Cleanup + +```bash title="unikraft" +unikraft instances delete +unikraft instances delete +``` + +or + +```bash title="kraft" +kraft cloud instance remove +kraft cloud instance remove +``` + +## Learn more + +Use the `--help` option for detailed information on using Unikraft Cloud: + +```bash title="unikraft" +unikraft --help +``` + +or + +```bash title="kraft" +kraft cloud --help +``` + +Or visit the [CLI Reference](https://unikraft.com/docs/cli/unikraft) or the [legacy CLI Reference](https://unikraft.com/docs/cli/kraft/overview). From 3a0069fa384413b3b8c82f12f3beac285e2a33ea Mon Sep 17 00:00:00 2001 From: Theo Gregory Date: Wed, 15 Apr 2026 18:16:07 +0200 Subject: [PATCH 2/4] Fix navigation paths and sed portability note in umami README - cd back to ../umami/ after postgres deploy (not examples/umami/) - Note macOS sed -i '' syntax difference for pgcrypto patch Co-Authored-By: Claude Opus 4.6 (1M context) --- umami/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/umami/README.md b/umami/README.md index 59a5c057..ed50707a 100644 --- a/umami/README.md +++ b/umami/README.md @@ -66,6 +66,7 @@ cd /tmp/umami-migrate npm install -g pnpm && pnpm install --frozen-lockfile # Patch pgcrypto (not available in the Unikraft postgres image, not needed on PG16+) +# On macOS, use: sed -i '' 's/...' instead sed -i 's/CREATE EXTENSION IF NOT EXISTS "pgcrypto";/-- pgcrypto: not needed on PG16+/' \ prisma/migrations/01_init/migration.sql @@ -81,7 +82,7 @@ DATABASE_URL="postgresql://postgres:@:5432/umami?sslmod Return to the `umami/` example directory and deploy: ```bash title="unikraft" -cd examples/umami/ +cd ../umami/ unikraft build . --output /umami:latest unikraft run --metro=fra \ -p 443:3000/tls+http \ @@ -95,6 +96,7 @@ unikraft run --metro=fra \ or ```bash title="kraft" +cd ../umami/ kraft cloud deploy \ -p 443:3000 \ -M 1024 \ From 91031b936139b9c790321025183681e06c77981b Mon Sep 17 00:00:00 2001 From: Theo Gregory Date: Wed, 15 Apr 2026 18:20:57 +0200 Subject: [PATCH 3/4] Keep GeoIP data in umami image by default Location tracking is one of Umami's key features. Stripping it saved 54 MB but isn't worth the loss in functionality. Users who need a smaller image can add the rm line back. Co-Authored-By: Claude Opus 4.6 (1M context) --- umami/Dockerfile | 4 ++-- umami/README.md | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/umami/Dockerfile b/umami/Dockerfile index ce015b7f..a7930c66 100644 --- a/umami/Dockerfile +++ b/umami/Dockerfile @@ -26,8 +26,8 @@ ENV NEXT_TELEMETRY_DISABLED=1 # Build: prisma client + tracker + geo database + Next.js app RUN pnpm build-docker -# Strip node binary (~20 MB savings) and remove GeoIP data (~54 MB savings) -RUN strip /usr/local/bin/node && rm -rf /app/.next/standalone/geo +# Strip node binary (~20 MB savings) +RUN strip /usr/local/bin/node FROM scratch diff --git a/umami/README.md b/umami/README.md index ed50707a..b447d3e1 100644 --- a/umami/README.md +++ b/umami/README.md @@ -152,8 +152,7 @@ Migrations are idempotent; already-applied migrations are skipped. ## Notes -- **No GeoIP location data**: The GeoLite2 database is removed from the image to reduce size (~54 MB). Umami will still track visits but won't resolve visitor locations. To re-enable, remove the `rm -rf /app/.next/standalone/geo` line from the `Dockerfile`. -- **Image size**: The `FROM scratch` image is ~320 MB. Umami requires at least 1024 MiB of memory to unpack the initramfs and run. +- **Image size**: The `FROM scratch` image is ~370 MB. Umami requires at least 1024 MiB of memory to unpack the initramfs and run. To reduce the image by ~54 MB, add `rm -rf /app/.next/standalone/geo` to the `Dockerfile` build stage (this disables visitor location tracking). - **pgcrypto**: The Unikraft Cloud postgres example does not include the `pgcrypto` extension. This is not a problem because PostgreSQL 16+ provides `gen_random_uuid()` as a built-in function. The migration patch in Step 2 and in the `Dockerfile` comments out the `CREATE EXTENSION` statement. ## Cleanup From d92baa0867aa350799f2ad24f694a065777b5c01 Mon Sep 17 00:00:00 2001 From: Theo Gregory Date: Wed, 15 Apr 2026 18:24:48 +0200 Subject: [PATCH 4/4] Bump minimum memory to 1536 MiB for GeoIP-enabled image The 386 MB image (with GeoIP data) OOMs at 1024 MiB during initramfs unpacking. 1536 MiB works reliably. Document the 1024 MiB option for users who strip GeoIP. Co-Authored-By: Claude Opus 4.6 (1M context) --- umami/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/umami/README.md b/umami/README.md index b447d3e1..810fc74f 100644 --- a/umami/README.md +++ b/umami/README.md @@ -40,7 +40,7 @@ First, deploy a PostgreSQL instance using the [postgres](../postgres/) example: ```bash title="unikraft" cd ../postgres/ unikraft build . --output /postgres:latest -unikraft run --metro=fra -p 5432:5432/tls -m 1G -e POSTGRES_PASSWORD= -e POSTGRES_DB=umami --scale-to-zero=off /postgres:latest +unikraft run --metro=fra -p 5432:5432/tls -m 1536M -e POSTGRES_PASSWORD= -e POSTGRES_DB=umami --scale-to-zero=off /postgres:latest ``` or @@ -86,7 +86,7 @@ cd ../umami/ unikraft build . --output /umami:latest unikraft run --metro=fra \ -p 443:3000/tls+http \ - -m 1G \ + -m 1536M \ -e DATABASE_URL="postgresql://postgres:@:5432/umami?sslmode=require" \ -e APP_SECRET="$(openssl rand -hex 32)" \ -e DISABLE_TELEMETRY=1 \ @@ -99,7 +99,7 @@ or cd ../umami/ kraft cloud deploy \ -p 443:3000 \ - -M 1024 \ + -M 1536 \ -e DATABASE_URL="postgresql://postgres:@:5432/umami?sslmode=require" \ -e APP_SECRET="$(openssl rand -hex 32)" \ -e DISABLE_TELEMETRY=1 \ @@ -116,7 +116,7 @@ The output shows the instance address and other details: ├───────── state: starting ├──────── domain: https://blue-bush-59v2y072.fra.unikraft.app ├───────── image: umami@sha256:d7f3221eee2ecbd8d90fcebe97eb7dc7f5989ee95c4cedd4a9fe728a709890b0 - ├──────── memory: 1024 MiB + ├──────── memory: 1536 MiB ├─────── service: blue-bush-59v2y072 ├── private fqdn: umami-f7k2x.internal ├──── private ip: 10.0.8.193 @@ -152,7 +152,7 @@ Migrations are idempotent; already-applied migrations are skipped. ## Notes -- **Image size**: The `FROM scratch` image is ~370 MB. Umami requires at least 1024 MiB of memory to unpack the initramfs and run. To reduce the image by ~54 MB, add `rm -rf /app/.next/standalone/geo` to the `Dockerfile` build stage (this disables visitor location tracking). +- **Image size**: The `FROM scratch` image is ~386 MB (including GeoIP data for visitor location tracking). Umami requires at least 1536 MiB of memory to unpack the initramfs and run. To reduce the image by ~54 MB (and lower the minimum memory to 1024 MiB), add `rm -rf /app/.next/standalone/geo` to the `Dockerfile` build stage. This disables visitor location tracking. - **pgcrypto**: The Unikraft Cloud postgres example does not include the `pgcrypto` extension. This is not a problem because PostgreSQL 16+ provides `gen_random_uuid()` as a built-in function. The migration patch in Step 2 and in the `Dockerfile` comments out the `CREATE EXTENSION` statement. ## Cleanup