Plausible self-hosted with Docker and Nomad

Plausible self-hosted with Docker and Nomad

Guide on how to host Plausible with Docker on Hashi Nomad & Consul infrastructure.

Roman Zipp, April 11th, 2023

This guide will show you how to host Plausible on Nomad & Consul. Please see the Plausible self-hosting Docs for more information.

Requirements

Prequisites

  1. Note that this templates uses the Nomad Server client.disable_file_sandbox option se we can mount the environment config file as env values

  2. Create a folder on your host machine. In the example we will use /mnt/plausible

  3. Update the values in plausible-conf.env

Folder tree

mnt/
├─ plausible/
│  ├─ db-data/
│  ├─ event-data/
│  ├─ plausible-conf.env

plausible-conf.env

BASE_URL=...
SECRET_KEY_BASE=...

Nomad Job Template

job "plausible" {
  datacenters = ["dc1"]
  type        = "service"

  group "database" {
    count = 1

    network {
      mode = "bridge"

      port "db" {
        to = 5432
      }
    }

    service {
      name = "plausible-database"
      port = 5432

      connect {
        sidecar_service {}
      }
    }

    task "postgres" {
      driver = "docker"

      config {
        image = "postgres:14-alpine"
        ports = ["db"]

        mount {
          type     = "bind"
          target   = "/var/lib/postgresql/data"
          source   = "/mnt/plausible/db-data/"
          readonly = false
          bind_options {
            propagation = "rshared"
          }
        }
      }

      env {
        POSTGRES_PASSWORD = "postgres"
      }
    }
  }

  group "events" {
    count = 1

    network {
      mode = "bridge"

      port "events" {
        to = 8123
      }
    }

    service {
      name = "plausible-events"
      port = 8123

      connect {
        sidecar_service {}
      }
    }

    task "clickhouse" {
      driver = "docker"

      config {
        image = "clickhouse/clickhouse-server:22.6-alpine"
        ports = ["events"]

        mount {
          type     = "bind"
          target   = "/var/lib/clickhouse"
          source   = "/mnt/plausible/event-data/"
          readonly = false
          bind_options {
            propagation = "rshared"
          }
        }

        mount {
          type     = "bind"
          target   = "/etc/clickhouse-server/config.d/logging.xml"
          source   = "local/clickhouse-config.xml"
          readonly = true
          bind_options {
            propagation = "rshared"
          }
        }

        mount {
          type     = "bind"
          target   = "/etc/clickhouse-server/users.d/logging.xml"
          source   = "local/clickhouse-user-config.xml"
          readonly = true
          bind_options {
            propagation = "rshared"
          }
        }

        ulimit {
          nofile = "262144:262144"
        }
      }

      template {
        data        = <<EOF
        <clickhouse>
            <logger>
                <level>warning</level>
                <console>true</console>
            </logger>

            <!-- Stop all the unnecessary logging -->
            <query_thread_log remove="remove"/>
            <query_log remove="remove"/>
            <text_log remove="remove"/>
            <trace_log remove="remove"/>
            <metric_log remove="remove"/>
            <asynchronous_metric_log remove="remove"/>
            <session_log remove="remove"/>
            <part_log remove="remove"/>
        </clickhouse>
        EOF
        destination = "local/clickhouse-config.xml"
      }

      template {
        data        = <<EOF
        <clickhouse>
            <profiles>
                <default>
                    <log_queries>0</log_queries>
                    <log_query_threads>0</log_query_threads>
                </default>
            </profiles>
        </clickhouse>
        EOF
        destination = "local/clickhouse-user-config.xml"
      }

      resources {
        cpu    = 100
        memory = 512
      }
    }
  }
  
  group "plausible" {
    count = 1

    network {
      mode = "bridge"

      port "http" {
        to = 8000
      }
    }

    service {
      name = "plausible-web"
      port = "http"

      connect {
        sidecar_service {
          proxy {
            upstreams {
              destination_name = "plausible-database"
              local_bind_port  = 5432
            }
            upstreams {
              destination_name = "plausible-events"
              local_bind_port  = 8123
            }
          }
        }
      }
    }

    task "plausible" {
      driver = "docker"

      config {
        image   = "plausible/analytics:latest"
        ports   = ["http"]
        command = "sh"
        args    = ["/start.sh"]

        mount {
          type     = "bind"
          target   = "/start.sh"
          source   = "local/start.sh"
          readonly = true
          bind_options {
            propagation = "rshared"
          }
        }
      }

      template {
        data        = <<EOF
        #!/bin/sh
        sleep 10
        sh /entrypoint.sh db createdb
        sh /entrypoint.sh db migrate
        sh /entrypoint.sh run
        EOF
        destination = "local/start.sh"
      }

      template {
        source      = "/mnt/plausible/plausible-conf.env"
        destination = "local/plausible-conf.env"
        env         = true
      }

      env {
        DATABASE_URL            = "postgres://postgres:postgres@${NOMAD_UPSTREAM_ADDR_plausible_database}/plausible_db"
        CLICKHOUSE_DATABASE_URL = "http://${NOMAD_UPSTREAM_ADDR_plausible_events}/plausible_events_db"
      }
    }
  }
}