Skip to content

Umami: new service - #349

Merged
jackspiering merged 3 commits into
tailscale-dev:mainfrom
noelob:add-service-umami
Oct 9, 2026
Merged

jackspiering merged 3 commits into
tailscale-dev:mainfrom
noelob:add-service-umami

Conversation

@noelob

@noelob noelob commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Umami: new service

Description

Adds Umami, an open-source web analytics platform that respects user privacy. The stack has a private mode (Tailnet only) and a public mode that publishes the tracking script and the data collection endpoint through Funnel.

Related Issues

  • None.

Verification

Tested locally with docker compose in both private and public mode

Checklist

  • I have performed a self-review of my code and followed the templates structure.
  • I have added verification that the stack works as expected.
  • I have updated necessary documentation (e.g. frontpage README.md ).
  • I have selected the correct label(s) for this PR.

Additional Context

  • None.

@jackspiering

Copy link
Copy Markdown
Collaborator

Thanks for adding Umami, @noelob! I tested this branch locally and the core works well:

  • In private mode all three containers become healthy, the dashboard loads over HTTPS through Tailscale Serve, and the default admin / umami login works.
  • In public mode the Serve routes are correct: /script.js and /api/send on 443, a 404 for the dashboard on 443, and the dashboard on 8443. I could not test the Funnel itself from the internet, because my test node has no Funnel permission.

Before we can merge, please bring it in line with the service template and CONTRIBUTING.md:

.env

  1. Set SERVICEPORT=3000. Umami listens on 3000, not 3001.
  2. Do not ship example secrets. Leave DB_PASSWORD= and APP_SECRET= empty, with a comment that they are required. CONTRIBUTING asks for no working passwords.
  3. Keep the template's comment lines for SERVICE, IMAGE_URL, SERVICEPORT, DNS_SERVER, and TS_AUTHKEY.
  4. Leave PUID/PGID commented out like in the template, and remove them from compose.yaml. The image runs as its own nextjs user and ignores them.
  5. Add a final newline. This also applies to compose.yaml and config/serve-public.json.

compose.yaml

  1. Use the template's inline configs: block for Serve instead of separate JSON files. You can keep both modes: define ts-serve-private and ts-serve-public, then select one with source: ts-serve-${SERVE_CONFIG} and target: /config/serve.json. I checked that Compose resolves the variable in source.
  2. Application health check: curl without -f also passes on HTTP errors. Use ["CMD", "curl", "-fsS", "http://127.0.0.1:3000/api/heartbeat"] with the template timings (interval: 1m, timeout: 10s, retries: 3, start_period: 30s). Please run it inside the container once to confirm it works.
  3. Database health check: CONTRIBUTING asks for a TCP check, so use pg_isready -h 127.0.0.1 .... A socket check reports "ready" too early on the first start. Add a start_period and use restart: always like the other containers.
  4. Take the comment lines from the current template: Variables are declared in .env file. and the current TS_ACCEPT_DNS comment.
  5. Optional: name the data folder ./umami-data/db so it is clear that it holds the database.

services/umami/README.md

Follow the template's sections and cover:

  • Setup steps: set TS_AUTHKEY, DB_PASSWORD, and APP_SECRET before the first start.
  • Change the default admin / umami password right after the first login.
  • Public mode prerequisites: Funnel must be allowed for the node in the tailnet policy. Also show how to embed the tracking script (<script defer src="https://umami.<tailnet>.ts.net/script.js" data-website-id="...">).
  • The persistent path (the PostgreSQL data folder).
  • Links to the upstream docs, for example https://umami.is/docs.
  • Remove the trailing spaces on lines 20–21. Markdown lint (MD009) flags them.

Root README.md

Please only add the Umami row. The PR also reformats the table's separator row and the Beszel Agent row, which is unrelated.

PR description

Please keep the template's ## Description heading, and add a few details under Verification (for example container status and what you checked in the browser).

Thanks again, looking forward to the update!

@noelob
noelob force-pushed the add-service-umami branch 2 times, most recently from 12de9c4 to 95aab3d Compare October 9, 2026 00:50
@noelob
noelob force-pushed the add-service-umami branch from 95aab3d to 1a0cbe5 Compare October 9, 2026 00:51
@noelob

noelob commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review @jackspiering , I think I've addressed all the feedback

@jackspiering

Copy link
Copy Markdown
Collaborator

Thanks for the update, @noelob! This is much closer. I tested the new branch again (with APP_SECRET and DB_PASSWORD set): all three containers become healthy, /api/heartbeat and /script.js answer, and the admin / umami login works. pg_isready -h 127.0.0.1 works too.

Fixed since the last round: the port, the inline Serve configs with SERVE_CONFIG, both health checks, the data folder name, the final newlines, and the root README.md (only the Umami row now).

A few points are still open. Some are left from the last review, and some are new because the template changed after your first push (#351, #358, #363, #367). Sorry for the moving target.

.env

  1. Empty values with a comment on the same line do not stay empty. Compose reads DB_PASSWORD= # REQUIRED as the password # REQUIRED. docker compose config shows:

    POSTGRES_PASSWORD: "# REQUIRED"
    APP_SECRET: "# REQUIRED - Generate a salt with, e.g. `openssl rand -base64 32`"
    TS_AUTHKEY: "# Auth key from https://tailscale.com/admin/authkeys. ..."
    

    Put the comment on its own line above the value, like the current template .env does:

    # Required. Generate with: openssl rand -base64 32
    APP_SECRET=
    # Required. The password of the PostgreSQL database.
    DB_PASSWORD=
  2. Please do the same for SERVICE, IMAGE_URL, SERVICEPORT, and TS_AUTHKEY. The template now has these comments on their own lines.

  3. Optional: upstream's own Compose file now uses ghcr.io/umami-software/umami:latest. Both tags point to the same image today (v3.4.0), so latest is the safer choice.

compose.yaml

  1. Make Compose stop when a secret is missing, as Docmost and Formbricks do since Arcane, ConvertX, Formbricks, Hemmelig, Karakeep: require your own secrets #367. Use ${APP_SECRET:?Set APP_SECRET in .env} and ${DB_PASSWORD:?Set DB_PASSWORD in .env}, for the password in both DATABASE_URL and POSTGRES_PASSWORD.
  2. Remove PUID=${PUID} and PGID=${PGID} (still open from the last round). They are not set in .env, so Compose prints a warning for each, and the container runs as uid=1001(nextjs) anyway.
  3. Remove TZ=${TZ} from the application. The image has no time zone data, so date in the container still prints UTC. Keep the TZ line in .env.
  4. Take these comment lines from the current template:
    • # Every variable used in this file must be defined there. instead of # All the ${ xx } need to be defined there.
    • #- TS_ACCEPT_DNS=true # Uncomment only if the service must resolve MagicDNS names - this replaces Docker DNS, so Compose service names no longer resolve
    • environment: # Variables are declared in .env file. (the application line still says "Varibles are delared").
  5. Comments no longer use a ${SERVICE} placeholder. Write "the service" instead:
    • # Binding the service port to the local network - may be removed if only exposure to your Tailnet is required
    • # Sidecar configuration to route the service through Tailscale
    • # Check if the service is responding
  6. Use restart: always for the database (still open), and add the comment # Check if PostgreSQL accepts connections to its health check, like the other stacks.
  7. Remove the trailing spaces on lines 12, 23, and 52. git diff --check reports them.
  8. Optional: upstream's Compose file no longer sets DATABASE_TYPE. Umami v3 only supports PostgreSQL, so you can probably drop it.

services/umami/README.md

All service READMEs moved to one fixed layout in #363. Please rewrite the file with the headings of the template README, in the same order and without other headings. Formbricks is a good example of a stack with a database. Your current text has almost everything; it mostly needs to move:

  • Title and introduction. # Umami, one or two sentences with the upstream link, then the standard-setup sentence from the template.
  • At a glance. Web interface https://umami.<tailnet>.ts.net, service port 3000, both images, and the data path ./umami-data/db (PostgreSQL database).
  • Before you start. Set APP_SECRET and DB_PASSWORD in .env. For public mode, Funnel must be allowed for the node in the tailnet policy. The auth key, /dev/net/tun, and HTTPS certificate points can go, because the Quick Start and the standard setup cover them.
  • Deviations from the standard setup. The extra database container (reached by its service name through Docker's DNS, so keep TS_ACCEPT_DNS disabled), the two Serve configurations selected by SERVE_CONFIG, and that public mode enables Funnel on 443 for /script.js and /api/send and moves the dashboard to 8443.
  • First run. Log in with admin / umami and change the password right away. Then add a website and embed the tracking script.
  • Configuration (optional section, between "First run" and "Links"). A good place for the SERVE_CONFIG explanation.
  • Links. Rename "References" to "Links".

In the HTML example, remove the empty first line and close the tag with </script>.

Pull request

  • Title: Umami: new service.
  • Keep the template's ## Description heading with a short description under it, and add a few details under Verification (for example the container status and what you checked in the browser).

Thanks again, this is nearly there!

Move the .env comments to their own lines. Compose read an empty value
with a comment on the same line as the comment text, so the database
password, APP_SECRET, and TS_AUTHKEY were never empty.

Make Compose stop when APP_SECRET or DB_PASSWORD is missing. Remove
PUID, PGID, TZ, and DATABASE_TYPE from the application, because the
image does not use them. Use the image tag that upstream uses.

Rewrite the README with the standard headings.
@jackspiering jackspiering changed the title add service umami Umami: new service Oct 9, 2026
@jackspiering

Copy link
Copy Markdown
Collaborator

@noelob, to save you another round I pushed the changes from my comment above as one commit to your branch (a0229e4). I also set the PR title and added the ## Description heading. Please pull before you make further changes, and tell me if you disagree with anything.

What the commit does:

  • .env: comments are on their own lines, so TS_AUTHKEY, APP_SECRET, and DB_PASSWORD are really empty. The image tag is latest, as in upstream's Compose file.
  • compose.yaml: Compose stops with an error when APP_SECRET or DB_PASSWORD is missing. PUID, PGID, TZ, and DATABASE_TYPE are gone from the application. The comments match the current template, the database uses restart: always, and the trailing spaces are removed. Your Serve configurations are unchanged.
  • README.md: rewritten with the standard headings. The content is yours, moved to the matching sections.

Tested on a Tailnet node after the change:

  • All three containers become healthy on a fresh database, without DATABASE_TYPE.
  • Private mode: the login page loads over HTTPS through Tailscale Serve, and the admin / umami login works.
  • Public mode: /script.js answers on 443, the dashboard gives 404 on 443 and loads on 8443, and tailscale serve status shows Funnel on for 443 only. I did not test access from the public internet.
  • docker compose config --quiet, rumdl, and git diff --check pass.

@jackspiering jackspiering self-assigned this Oct 9, 2026
@jackspiering
jackspiering self-requested a review October 9, 2026 08:59
@jackspiering
jackspiering merged commit ecb8474 into tailscale-dev:main Oct 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants