Article Image
read

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.

Blog Logo

Christopher LaPointe


Published

Image

Chris LaPointe

Another site of Code

Back to Overview