I have a lot of little sites. Probably too many. Includes various personal sites, sites for friends and family, and random projects I'm doing. They're scattered across the internet: Some are hosted here on my swarm, some on netlify, some on surge.sh.. and the list goes on. Google analytics is "fine". It works, and provides the basic features I need (along with a bajillion I usually don't). Frankly, I'll probably keep using google-analytics for my more professional things, but I needed something super simple that was easy to spin up and down for each little site to give me a glimpse into if anyone was looking at them. There's also the whole google privacy concern.
I looked at goaccess a bit, but now that I have things on these CDN-focused services (eg netlify), I really needed something that allowed js-snippet style analytics. Yes, I know there are tricks to getting goaccess to work with pixels, but it's not really what it was meant for, and isn't able to slice and dice quite in an analytics-focused way (eg, break down by virtual host).
So, the search began...
Goals
- Lightweight and fast. I don't have that much traffic, and don't want a giant service running
- Open source / self-hostable
- Respects privacy. Doesn't collect overly obtrusive details on users
- Dockerized / Swarmable
Resources
Let's be frank, this was about 80% googling, but I did find two lists that were super helpful.
Florens Verschelde has a good article on his own experiences: https://fvsch.com/small-analytics
A complete and unopinionated list of stats software: https://github.com/0xnr/awesome-analytics
Options I Evaluated
NOTE: Many of the options below contain example swarm-compatible compose files. Many of these were used for testing purposes, but are not fully built out (Meaning they often don't have volumes or any sort of persistence). Feel free to use them as a baseline, but please see their website setup docs.
Plausible
Site: https://github.com/plausible/analytics
Stars: 8.1k
Language: Elixir
DB: Postgres and clickhouse
Thoughts:
- Overall, it seems to use 3-5 containers to run, and so is a little too heavy weight.
- After spending much more time to setup that I wanted, I moved on to the next...
Fathom
Site: https://github.com/usefathom/fathom
Stars: 6.9k
Language: GO
DB: Sqlite or Postgres
Memory: ~20MB
Thoughts:
- Nice and light, sqllite DB by default or can upgrade.
- Very basic metrics (Visitors/uniques over time).
- Can't see other info like geography/language.
- No commit since Feb 2020, and even longer since a commit with substance
Docker Compose
version: "3.5"
# Note, if this is a new image, must run fathom
# as an instance and run ./fathom user add ...
# see their docs
services:
fathom:
image: usefathom/fathom:latest
networks:
- traefik-net
volumes:
- fathom:/var/fathom
environment:
FATHOM_DATABASE_NAME: /var/fathom/fathom.db
deploy:
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik-net"
- "traefik.http.routers.fathom.rule=Host(`fathom.${HOST}`)"
- "traefik.http.services.fathom.loadbalancer.server.port=8080"
- "traefik.http.routers.fathom.entrypoints=websecure"
resources:
limits: { cpus: '0.2', memory: '64M' }
reservations: { cpus: '0.05', memory: '64M' }
volumes:
fathom: {}
networks:
traefik-net:
external: true
name: traefik-net
Umami
Github: https://github.com/mikecao/umami
Site: https://umami.is/
DB: Postgres or MySQL
Lang: Nodejs 10.3+
Memory: 100-150 MB + DB (20-80MB)
Thoughts:
- A little bit heavier on the memory
- Has all the features I'm looking for
- I like the style of the UI, easy to integrate with
- Active developers
Docker Compose
version: "3.5"
services:
umami:
image: ghcr.io/mikecao/umami:postgresql-latest
networks:
- default
- traefik-net
depends_on:
- db
environment:
DATABASE_URL: postgresql://umami:replace-me@db:5432/umami
DATABASE_TYPE: postgresql
HASH_SALT: replace-me
deploy:
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik-net"
- "traefik.http.routers.umami.rule=Host(`stats.${HOST}`)"
- "traefik.http.services.umami.loadbalancer.server.port=3000"
- "traefik.http.routers.umami.entrypoints=websecure"
resources:
limits: { cpus: '0.2', memory: '128M' }
reservations: { cpus: '0.05', memory: '128M' }
db:
image: postgres:12-alpine
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: replace-me
networks:
- default
configs:
- source: umami-schema
target: /docker-entrypoint-initdb.d/schema.postgresql.sql
volumes:
- umami-db:/var/lib/postgresql/data
deploy:
resources:
limits: { cpus: '0.2', memory: '64M' }
reservations: { cpus: '0.05', memory: '64M' }
configs:
umami-schema:
name: umami-schema-${NOW}
file: config/umami.schema.sql
volumes:
umami-db: {}
networks:
traefik-net:
external: true
name: traefik-net
Shynet
Github: https://github.com/milesmcc/shynet
Stars: 1.4k
DB: Postgres
Language: Python
Memory: ~60-80 MB + DB
Thoughts:
- Mid-memory usage
- UI is okay, but not my favorite
- Development on it is semi-active
Docker Compose
version: '3.5'
services:
shynet:
image: milesmcc/shynet:latest
restart: unless-stopped
env_file:
- shynet.env # Config from their site
environment:
- DB_HOST=db
networks:
- traefik-net
- default
depends_on:
- db
deploy:
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik-net"
- "traefik.http.routers.shynet.rule=Host(`stats.${HOST}`)"
- "traefik.http.services.shynet.loadbalancer.server.port=8080"
- "traefik.http.routers.shynet.entrypoints=websecure"
db:
image: postgres:alpine
restart: always
environment:
- "POSTGRES_USER=shynet"
- "POSTGRES_PASSWORD=shynet"
- "POSTGRES_DB=shynet"
#volumes:
# - shynet_db:/var/lib/postgresql/data
networks:
- default
#volumes:
# shynet_db:
networks:
traefik-net:
external: true
name: traefik-net
Ackee
Github: https://github.com/electerious/Ackee
Site: https://ackee.electerious.com/
Stars: 2.6k
Language: Nodejs
DB: Mongo
Memory: 125-175MB (Ackee) + 60-80 MB (Mongo)
Thoughts:
- Has the features I like
- UI looks nice
- Heaviest of the group on memory usage
Docker Compose
version: "3.5"
services:
ackee:
image: electerious/ackee
environment:
- WAIT_HOSTS=mongo:27017
- ACKEE_MONGODB=mongodb://mongo:27017/ackee
- ACKEE_USERNAME=username
- ACKEE_PASSWORD=password
depends_on:
- mongo
networks:
- default
- traefik-net
deploy:
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik-net"
- "traefik.http.routers.shynet.rule=Host(`stats.${HOST}`)"
- "traefik.http.services.shynet.loadbalancer.server.port=3000"
- "traefik.http.routers.shynet.entrypoints=websecure"
mongo:
image: mongo
#volumes:
# - ./data:/data/db
networks:
traefik-net:
external: true
name: traefik-net
Goatcounter
Github: https://github.com/zgoat/goatcounter
Site: https://www.goatcounter.com/
Stars: 2.1k
Language: Go
Memory: ~25 MB
Thoughts:
- No official docker container (And an okay community contributed one)
- Doesn't handle multiple domains well (At best, it's a hack that puts the domain in the paths)
Docker Compose
version: '3.5'
services:
goatcounter:
image: baethon/goatcounter:1.4 # 2.0 (latest) is broken right now
environment:
GOATCOUNTER_DOMAIN: stats.s0.zdyn.net
GOATCOUNTER_EMAIL: test@example.com
GOATCOUNTER_PASSWORD: testtest
# Would need volume for: sqlite:///goatcounter/db/goatconter.sqlite3
networks:
- traefik-net
deploy:
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik-net"
- "traefik.http.routers.shynet.rule=Host(`stats.${HOST}`)"
- "traefik.http.services.shynet.loadbalancer.server.port=8080"
- "traefik.http.routers.shynet.entrypoints=websecure"
networks:
traefik-net:
external: true
name: traefik-net
What I Picked
Initially I picked Fathom, because it was super light weight (Only used 20 MB in testing), supported SQLite, which is great in low-volume situations, and was super easy to spin up and use in docker. That said, it missed some really key features such as device, country, and browser. It's also woefully out of date and shows no signs of being updated ever again.
Right now, I'm using Umami, and I'm pretty happy with it. It's a little hefty compared to fathom on the resource front (~150 MB total, compared to fathom's 20MB of memory), but at the end of the day that's not that excessive, and it has all the features I was looking for. I'll keep testing both of them out a bit, and update if I have any further thoughts.