Run Plausible With Docker Swarm & Traefik

Posted on: Wednesday, 20 October 2021

I can't praise Plausible enough. It's a truly great product. While the self-hosted installation docs are useful if you're using Docker Compose, they don't go into any details about how to run it with Docker Swarm, hence the reason for this blog post.

Firstly it's good to be aware the following points:

  • At the time of writing, Plausible (well, mainly Clickhouse) does not support the arm64 architecture, so if you're trying to run Plausible on a Raspberry Pi or a fancy M1 Mac, you're going to have a bad time. (But you can always build the images yourself.)
  • At the time of writing, we have to use a non-stable version of Plausible to utilise a recent change to read config from file (to support Docker Secrets).
    • (Note that I fixed a bug relating to reading config from file.)

Now to set things up.

First let's define our stack definition:

version: '3.8'

services:
  plausible_db:
    image: postgres:13.3-alpine
    volumes:
      - plausible-db-data:/var/lib/postgresql/data
    networks:
      - plausible
    healthcheck:
      test: ['CMD', 'pg_isready']
    environment:
      - POSTGRES_DB=plausible
      - POSTGRES_USER_FILE=/run/secrets/plausible-postgres-user
      - POSTGRES_PASSWORD_FILE=/run/secrets/plausible-postgres-password
    secrets:
      - plausible-postgres-password
      - plausible-postgres-user
    deploy:
      mode: replicated
      replicas: 1
      labels:
        - 'traefik.enable=false'

  plausible_events_db:
    image: yandex/clickhouse-server:21.3.2.5
    volumes:
      - plausible-event-data:/var/lib/clickhouse
      - plausible-etc-clickhouse-server:/etc/clickhouse-server
    networks:
      - plausible
    deploy:
      mode: replicated
      replicas: 1
      labels:
        - 'traefik.enable=false'

  plausible:
    image: plausible/analytics:master@sha256:adfc5f693e8d70d48b04fc17b009b2871b9ee23d9a349e606eab708e964d2a60
    command: sh -c "/entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh db init-admin && /entrypoint.sh run"
    networks:
      - plausible
      - traefik-public
    environment:
      - CONFIG_DIR=/run/secrets
      - SMTP_HOST_PORT=587
    healthcheck:
      test:
        ['CMD', 'wget', '-q', '--tries=1', '--spider', 'http://localhost:8000']
    secrets:
      - source: plausible-admin-user-email
        target: /run/secrets/ADMIN_USER_EMAIL
      - source: plausible-admin-user-name
        target: /run/secrets/ADMIN_USER_NAME
      - source: plausible-admin-user-password
        target: /run/secrets/ADMIN_USER_PWD
      - source: plausible-base-url
        target: /run/secrets/BASE_URL
      - source: plausible-secret-key-base
        target: /run/secrets/SECRET_KEY_BASE
      - source: plausible-database-url
        target: /run/secrets/DATABASE_URL
      - source: plausible-mailer-email
        target: /run/secrets/MAILER_EMAIL
      - source: plausible-smtp-host-addr
        target: /run/secrets/SMTP_HOST_ADDR
      - source: plausible-smtp-user-name
        target: /run/secrets/SMTP_USER_NAME
      - source: plausible-smtp-user-pwd
        target: /run/secrets/SMTP_USER_PWD
      - source: plausible-google-client-id
        target: /run/secrets/GOOGLE_CLIENT_ID
      - source: plausible-google-client-secret
        target: /run/secrets/GOOGLE_CLIENT_SECRET
    deploy:
      mode: replicated
      replicas: 1
      labels:
        - 'traefik.enable=true'
        - 'traefik.docker.lbswarm=true'
        - 'traefik.http.routers.plausible.entrypoints=web'
        - 'traefik.http.routers.plausible.rule=Host(`plausible.example.com`)'
        - 'traefik.http.services.plausible-service.loadbalancer.server.port=8000'
        - 'traefik.http.middlewares.plausible-redirectscheme.redirectscheme.permanent=true'
        - 'traefik.http.middlewares.plausible-redirectscheme.redirectscheme.scheme=https'
        - 'traefik.http.routers.plausible.middlewares=plausible-redirectscheme'
        - 'traefik.http.routers.plausible-secure.entrypoints=websecure'
        - 'traefik.http.routers.plausible-secure.rule=Host(`plausible.example.com`)'
        - 'traefik.http.routers.plausible-secure.tls.certresolver=letsencrypt'

networks:
  traefik-public:
    external: true
  plausible:
    driver: overlay
    attachable: true

secrets:
  plausible-admin-user-email:
    external: true
  plausible-admin-user-name:
    external: true
  plausible-admin-user-password:
    external: true
  plausible-base-url:
    external: true
  plausible-secret-key-base:
    external: true
  plausible-postgres-user:
    external: true
  plausible-postgres-password:
    external: true
  plausible-database-url:
    external: true
  plausible-geoupdate-account-id:
    external: true
  plausible-geoupdate-license-key:
    external: true
  plausible-mailer-email:
    external: true
  plausible-smtp-host-addr:
    external: true
  plausible-smtp-user-name:
    external: true
  plausible-smtp-user-pwd:
    external: true
  plausible-google-client-id:
    external: true
  plausible-google-client-secret:
    external: true

volumes:
  plausible-db-data:
  plausible-event-data:
  plausible-etc-clickhouse-server:

If you've worked with Docker Swarm & Traefik before, the above stack definition should be familiar to you, but here's some things to keep in mind:

  • You need to manually create the Docker Secrets (I use Portainer to create secrets using a GUI).
  • I've pinned Plausible to version plausible/analytics:master@sha256:adfc5f693e8d70d48b04fc17b009b2871b9ee23d9a349e606eab708e964d2a60 (which is the change that contains the fix to read config from file).
  • Plausible reads config from disk that corresponds to the config name (eg GOOGLE_CLIENT_SECRET), but I don't want to name my secrets like this (as secrets are global), which is why we're mounting the secrets under different names.
  • We're using Traefik for network routing and SSL termination.

(If you're looking for a handy script to setup Portainer, Traefik & Docker swarm on a small VPS, check out docker box.)

Hopefully you found this article useful. Any questions? Leave a comment below!


Comments

(No comments)

Add a new comment