Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ Every stack starts from the same [standard setup](documentation/standard-setup.m
| 🛰️ **Beszel Agent** | The Beszel Agent collects stats and reports them back to the Hub. | [Details](services/beszel-agent) |
| 🔎 **Portracker** | A simple, self-hosted port monitoring and tracking tool for auditing open ports. | [Details](services/portracker) |
| 🚀 **Speedtest Tracker** | A self-hosted tool to monitor and log internet speed tests with detailed visualizations. | [Details](services/speedtest-tracker) |
| 📊 **Umami** | An open-source web analytics platform that respects user privacy. | [Details](services/umami) |
| 📊 **Uptime Kuma** | A self-hosted monitoring tool like "Uptime Robot". | [Details](services/uptime-kuma) |

### 🏠 Smart Home
Expand Down
43 changes: 43 additions & 0 deletions services/umami/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#version=1.1
#URL=https://github.com/tailscale-dev/ScaleTail
#COMPOSE_PROJECT_NAME= # Optional: only use when running multiple deployments on the same infrastructure.

# Service Configuration
# Service name (e.g., adguard). Used as hostname in Tailscale and for container naming (app-${SERVICE}).
SERVICE=umami
# Docker image URL from container registry (e.g., adguard/adguard-home).
IMAGE_URL=ghcr.io/umami-software/umami:latest

# Network Configuration
# Port to expose to local network. Uncomment the "ports:" section in compose.yaml to enable.
SERVICEPORT=3000
DNS_SERVER=9.9.9.9 # Preferred DNS server for Tailscale. Uncomment the "dns:" section in compose.yaml to enable.

# Tailscale Configuration
# Auth key from https://tailscale.com/admin/authkeys. See: https://tailscale.com/kb/1085/auth-keys#generate-an-auth-key for instructions.
TS_AUTHKEY=

# Optional Service variables
# PUID=1000

# Time Zone setting for containers
TZ=Europe/Amsterdam # See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

# Any Container environment variables are declared below. See https://docs.docker.com/compose/how-tos/environment-variables/

# Required: a random value. Generate with: openssl rand -hex 32
APP_SECRET=

# Database Configuration
DB_IMAGE_URL=postgres:15-alpine
DB_NAME=umami
DB_USER=umami
# Required: password for the Umami PostgreSQL user. Use letters and digits only, because it is part of DATABASE_URL.
# PostgreSQL applies it only when the database is first created.
DB_PASSWORD=

# The Serve configuration to use.
# 'private' keeps all access to Umami within your Tailnet.
# 'public' exposes the tracking script and the data collection endpoint through Funnel,
# so that websites with visitors outside your Tailnet can report analytics.
SERVE_CONFIG=private
47 changes: 47 additions & 0 deletions services/umami/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Umami

[Umami](https://umami.is/) is a web analytics platform that respects the privacy of your visitors. It uses no cookies and collects no personal data.

This stack runs Umami with a Tailscale sidecar, as described in [the standard setup](../../documentation/standard-setup.md).

## At a glance

| Item | Value |
| ------------- | ------------------------------------------ |
| Web interface | `https://umami.<tailnet>.ts.net` |
| Service port | `3000` |
| Images | `ghcr.io/umami-software/umami:latest` |
| | `postgres:15-alpine` |
| Data | `./umami-data/db` (PostgreSQL database) |

## Before you start

Set these values in `.env`. Compose stops with an error if one of them is empty.

- **`APP_SECRET`.** A random value. Generate one with `openssl rand -hex 32`.
- **`DB_PASSWORD`.** The password of the database. Use letters and digits only, because the value is part of the database address. PostgreSQL applies it only when it first creates the database.

## Deviations from the standard setup

- **Extra container.** The stack runs `database` (PostgreSQL). It uses the default Compose network, and Umami reaches it by its service name through Docker's DNS. Keep `TS_ACCEPT_DNS` disabled, because MagicDNS cannot resolve this name.
- **Two Serve configurations.** `compose.yaml` defines `ts-serve-private` and `ts-serve-public`. `SERVE_CONFIG` in `.env` selects one of them. The default is `private`, which matches the standard setup.
- **Funnel in public mode.** With `SERVE_CONFIG=public`, Tailscale Funnel publishes `/script.js` and `/api/send` on port 443 to the public internet. The web interface moves to `https://umami.<tailnet>.ts.net:8443` and stays Tailnet-only.

## First run

Open the web interface and log in with the username `admin` and the password `umami`. Change this password right after the first login.

Then add a website in Umami and embed its tracking script in your site, as described in the [Umami documentation](https://umami.is/docs/collect-data):

```html
<script defer src="https://umami.<tailnet>.ts.net/script.js" data-website-id="..."></script>
```

## Configuration

- **`SERVE_CONFIG`.** Use `private` when all visitors of your websites are on your Tailnet. Use `public` to collect data from websites with visitors outside your Tailnet. Public mode needs [Funnel](https://tailscale.com/kb/1223/funnel) allowed for the device in your Tailnet policy. Recreate the stack after you change the value.

## Links

- [Umami documentation](https://umami.is/docs)
- [Umami source code](https://github.com/umami-software/umami)
101 changes: 101 additions & 0 deletions services/umami/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
configs:
ts-serve-private:
content: |
{"TCP":{"443":{"HTTPS":true}},
"Web":{"$${TS_CERT_DOMAIN}:443":
{"Handlers":{"/":
{"Proxy":"http://127.0.0.1:3000"}}}},
"AllowFunnel":{"$${TS_CERT_DOMAIN}:443":false}}
ts-serve-public:
content: |
{"TCP": {"443": {"HTTPS": true}, "8443": {"HTTPS": true}},
"Web": {"$${TS_CERT_DOMAIN}:443":
{"Handlers": {"/script.js":
{"Proxy": "http://127.0.0.1:3000/script.js"},
"/api/send":
{"Proxy": "http://127.0.0.1:3000/api/send"}}},
"$${TS_CERT_DOMAIN}:8443": {
"Handlers": {"/":
{"Proxy": "http://127.0.0.1:3000/"}}}},
"AllowFunnel": {"$${TS_CERT_DOMAIN}:443": true, "$${TS_CERT_DOMAIN}:8443": false}}

services:
# Make sure you have updated/checked the .env file with the correct variables.
# Every variable used in this file must be defined there.
# Tailscale Sidecar Configuration
tailscale:
image: tailscale/tailscale:latest # Image to be used
container_name: tailscale-${SERVICE} # Name for local container management
hostname: ${SERVICE} # Name used within your Tailscale environment
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_STATE_DIR=/var/lib/tailscale
- TS_SERVE_CONFIG=/config/serve.json # Tailscale Serve configuration to expose the web interface on your local Tailnet - remove this line if not required
- TS_USERSPACE=false
- TS_ENABLE_HEALTH_CHECK=true # Enable healthcheck endpoint: "/healthz"
- TS_LOCAL_ADDR_PORT=127.0.0.1:41234 # The <addr>:<port> for the healthz endpoint
#- TS_ACCEPT_DNS=true # Uncomment only if the service must resolve MagicDNS names - this replaces Docker DNS, so Compose service names no longer resolve
- TS_AUTH_ONCE=true
configs:
- source: ts-serve-${SERVE_CONFIG}
target: /config/serve.json
volumes:
- ./config:/config # Config folder used to store Tailscale files - you may need to change the path
- ./ts/state:/var/lib/tailscale # Tailscale requirement - you may need to change the path
devices:
- /dev/net/tun:/dev/net/tun # Network configuration for Tailscale to work
cap_add:
- net_admin # Tailscale requirement
#ports:
# - 0.0.0.0:${SERVICEPORT}:${SERVICEPORT} # Binding the service port to the local network - may be removed if only exposure to your Tailnet is required
# If any DNS issues arise, use your preferred DNS provider by uncommenting the config below
#dns:
# - ${DNS_SERVER}
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:41234/healthz"] # Check Tailscale has a Tailnet IP and is operational
interval: 1m # How often to perform the check
timeout: 10s # Time to wait for the check to succeed
retries: 3 # Number of retries before marking as unhealthy
start_period: 10s # Time to wait before starting health checks
restart: always

# Umami
application:
image: ${IMAGE_URL} # Image to be used
network_mode: service:tailscale # Sidecar configuration to route the service through Tailscale
container_name: app-${SERVICE} # Name for local container management
environment: # Variables are declared in .env file.
- DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD:?Set DB_PASSWORD in .env}@database:5432/${DB_NAME}
- APP_SECRET=${APP_SECRET:?Set APP_SECRET in .env}
depends_on:
tailscale:
condition: service_healthy
database:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:3000/api/heartbeat"] # Check if the service is responding
interval: 1m # How often to perform the check
timeout: 10s # Time to wait for the check to succeed
retries: 3 # Number of retries before marking as unhealthy
start_period: 30s # Time to wait before starting health checks
restart: always

# PostgreSQL database
database:
image: ${DB_IMAGE_URL}
container_name: db-${SERVICE}
environment: # Variables are declared in .env file.
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD:?Set DB_PASSWORD in .env}
# Point PGDATA to a subfolder inside the volume mount
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- ./${SERVICE}-data/db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] # Check if PostgreSQL accepts connections
interval: 5s # How often to perform the check
timeout: 3s # Time to wait for the check to succeed
retries: 10 # Number of retries before marking as unhealthy
start_period: 10s # Time to wait before starting health checks
restart: always
Loading