From 5eb8929929d82cd974f6705aa1c0f14ed786a514 Mon Sep 17 00:00:00 2001
From: Chandra Tungathurthi <tckb@users.noreply.github.com>
Date: Tue, 26 May 2020 15:09:34 +0200
Subject: [PATCH] Support for docker based self-hosting (#64)

* first commit with test and compile job

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* adding 'prepare' stage

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* updated ci script to include "test" compile phase

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* adding environment variables for connecting to postgresql

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* updated ci config for postgres

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* using non-alpine version of elixir

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* re-using the 'compile' artifacts and added explict env variables for testing

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* removing redundant deps fetching from common code

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* formatting using mix.format -- beware no-code changes!

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* added release config

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* adding consistent env variable for Database

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* more cleaning up of environment variables

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Adding releases config for enabling releases

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* cleaning up env configs

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Cleaned up config and prepared config for releases

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* updated CI script with new config for test

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Added Dockerfile for creating production docker image

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Adding "docker" build job yay!

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* using non-slim version of debian and installing webpack

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Adding overlays for migrations on releases

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* restricting the docker built to master branch only

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* typo fix

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* adding "Hosting.md" to explain hosting instructions

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* removed the default comments

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Added documentation related to env variables

* updated documentation and fixed typo

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* updated documentation

* Bumping up elixir version as `overlays` are only supported in latest version

read release notes: https://github.com/elixir-lang/elixir/releases/tag/v1.10.0

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Adding tarball assembly during release

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* updated HOSTING.md

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Added support for db migration

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* minor corrections

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* initializing admin user

Admin user has been added in the "migration" phase. A default user is automatically created in the process. One can provide the related env variables, else a new one will be automatically created for you.

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Initial base domain update - phase#1

These changes are only meant for correct operating it under self-hosting. There are many other cosmetic changes, that require updates to email, site and other places where the original website and author is used.

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Using dedicated config variable `base_domain` instead

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* adding base_domain to releases config

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* removing the dedicated config "base_domain", relying on endpoint host

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Removed the usage of "Mix" in code!

It is bad practice to use "mix" module inside the code as in actual release this module is unavailable. Replacing this with a config environment variable

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Added support for SMTP via Bamboo Smtp Adapter

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Capturing SMTP errors via Sentry

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Minor updates

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* Adding junit formatter -- useful for generating test reports

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* adding documentation for default user

* Resolve "Gitlab Adoption: Add supported services in "Security & Compliance""

* bumping up the debian version to fix issues

fixing some vulnerabilities identified by the scanning tools

* More updates for self-hosting

Changes in most of the places to suit self-hosting. Although, there are some which have been left-off.

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* quick-dirty-fix!

* bumping up the db connect timeout

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* bumping up the db connect timeout

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* bumping up the db connect timeout

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* bumping up timeout - skipping MRs :-/

* removing restrictions on watching for changes

this stuff isn't working

* Update HOSTING.md

* renamed the module name

* reverting formatting-whitespace changes

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* reverting the name to release

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* adding docker-compose.yml and related instructions

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* using `plausible_url` instead of assuming `https`

this is because, it is much to test in local dev machines and in most cases there's already a layer above which is capable for `https` termination and http -> https upgrade

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* WIP: merging changes from upstream

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* wip: more changes

* Pushing in changes from upstream

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* changes to ci for testing

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* cleaning up and finishing clickhouse integration

Signed-off-by: Chandra Tungathurthi <tckb@tgrthi.me>

* updating readme with hosting details
---
 .gitlab-ci.yml                                | 125 ++++++++++
 .gitlab/build-scripts/docker-entrypoint.sh    |  17 ++
 .gitlab/build-scripts/docker.gitlab.sh        |  37 +++
 Dockerfile                                    |  82 ++++++
 HOSTING.md                                    | 152 ++++++++++++
 README.md                                     |   4 +-
 config/config.exs                             |  96 ++++++-
 config/dev.exs                                |  25 +-
 config/prod.exs                               | 106 +-------
 config/releases.exs                           | 128 ++++++++++
 config/test.exs                               |  27 +-
 docker-compose.yml                            |  79 ++++++
 lib/mix/tasks/hydrate_clickhouse.ex           |   1 +
 lib/mix/tasks/send_check_stats_emails.ex      |   2 +-
 lib/mix/tasks/send_email_reports.ex           |   3 +-
 lib/mix/tasks/send_site_setup_emails.ex       |   6 +-
 lib/mix/tasks/send_trial_notifications.ex     |   6 +-
 lib/plausible/google/api.ex                   |   2 +-
 lib/plausible/mailer.ex                       |  14 ++
 lib/plausible_release.ex                      | 234 ++++++++++++++++++
 .../controllers/auth_controller.ex            |   4 +-
 .../controllers/billing_controller.ex         |   4 +
 .../controllers/page_controller.ex            |   2 +-
 .../controllers/stats_controller.ex           |   7 +-
 lib/plausible_web/email.ex                    |  28 ++-
 lib/plausible_web/endpoint.ex                 |  10 +-
 lib/plausible_web/router.ex                   |   2 +-
 .../email/check_stats_email.html.eex          |   4 +-
 .../email/create_site_email.html.eex          |   4 +-
 .../email/site_setup_help_email.html.eex      |   4 +-
 .../email/site_setup_success_email.html.eex   |   7 +-
 .../email/trial_one_week_reminder.html.eex    |   4 +-
 .../templates/email/trial_over_email.html.eex |   4 +-
 .../email/trial_upgrade_email.html.eex        |   4 +-
 .../templates/email/welcome_email.html.eex    |   4 +-
 .../templates/layout/_footer.html.eex         |   4 +-
 .../templates/layout/_tracking.html.eex       |   4 +-
 .../templates/page/privacy.html.eex           |   2 +-
 .../templates/page/terms.html.eex             |   2 +-
 .../site/custom_domain_dns_setup.html.eex     |   4 +-
 .../templates/site/settings.html.eex          |   6 +-
 lib/plausible_web/views/auth_view.ex          |  12 +
 lib/plausible_web/views/billing_view.ex       |  12 +
 lib/plausible_web/views/email_view.ex         |  12 +
 lib/plausible_web/views/layout_view.ex        |  12 +
 lib/plausible_web/views/page_view.ex          |  12 +
 lib/plausible_web/views/site_view.ex          |  25 +-
 lib/plausible_web/views/stats_view.ex         |  12 +
 mix.exs                                       |  37 ++-
 mix.lock                                      |  33 ++-
 .../20190618165016_add_public_sites.exs       |   3 +-
 rel/env.bat.eex                               |   6 +
 rel/env.sh.eex                                |  18 ++
 rel/overlays/createdb.sh                      |   7 +
 rel/overlays/migrate.sh                       |   6 +
 rel/overlays/rollback.sh                      |   5 +
 rel/overlays/seed.sh                          |   5 +
 rel/vm.args.eex                               |  11 +
 test/test_helper.exs                          |   1 +
 59 files changed, 1246 insertions(+), 243 deletions(-)
 create mode 100644 .gitlab-ci.yml
 create mode 100755 .gitlab/build-scripts/docker-entrypoint.sh
 create mode 100644 .gitlab/build-scripts/docker.gitlab.sh
 create mode 100644 Dockerfile
 create mode 100644 HOSTING.md
 create mode 100644 config/releases.exs
 create mode 100644 docker-compose.yml
 create mode 100644 lib/plausible_release.ex
 create mode 100644 rel/env.bat.eex
 create mode 100644 rel/env.sh.eex
 create mode 100755 rel/overlays/createdb.sh
 create mode 100755 rel/overlays/migrate.sh
 create mode 100755 rel/overlays/rollback.sh
 create mode 100755 rel/overlays/seed.sh
 create mode 100644 rel/vm.args.eex

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 00000000..f7e6a0e8
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,125 @@
+include:
+  - template: Container-Scanning.gitlab-ci.yml
+  - template: License-Scanning.gitlab-ci.yml
+  - template: SAST.gitlab-ci.yml
+
+stages:
+  - prepare
+  - compile
+  - test
+  - build
+  - postbuild
+
+.commons: &elixir-commons
+  image: elixir:1.10.3
+  cache:
+    key: ${CI_COMMIT_REF_SLUG}
+    paths:
+      - $CI_PROJECT_DIR/.mix
+      - $CI_PROJECT_DIR/priv/plts
+      - ~/.hex/
+  before_script:
+    - mkdir -p $CI_PROJECT_DIR/priv/plts/
+    - mix local.hex --force &&  mix local.rebar --force
+    - chmod +x .gitlab/build-scripts/*
+    - source .gitlab/build-scripts/docker.gitlab.sh
+
+deps:
+  <<: *elixir-commons
+  stage: prepare
+  variables:
+    MIX_HOME: $CI_PROJECT_DIR/.mix
+  script:
+    - mix deps.get
+  dependencies: []
+  artifacts:
+    paths:
+      - mix.lock
+      - deps
+
+compile:
+  <<: *elixir-commons
+  stage: compile
+  script:
+    - mix compile
+  dependencies:
+    - deps
+  artifacts:
+    paths:
+      - mix.lock
+      - _build
+      - deps
+
+
+license_scanning:
+  stage: compile
+  dependencies:
+    - deps
+
+sast:
+  stage: compile
+
+test:ex_unit:
+  <<: *elixir-commons
+  services:
+    - postgres
+    - name: yandex/clickhouse-server:20.3.9.70
+      alias: clickhouse
+  stage: test
+  variables:
+    POSTGRES_PASSWORD: postgres
+    POSTGRES_USER: postgres
+    DATABASE_URL: postgres://postgres:postgres@postgres:5432/plausible_test?currentSchema=default
+    CLICKHOUSE_DATABASE_HOST: clickhouse
+    CLICKHOUSE_DATABASE_NAME: plausible_test
+    MIX_HOME: $CI_PROJECT_DIR/.mix
+  before_script:
+    - apt update && apt install -y clickhouse-client
+    - clickhouse-client --host clickhouse --query "CREATE DATABASE IF NOT EXISTS plausible_test"
+  script:
+    - mix test --cover
+  coverage: '/\[TOTAL\]\s+(\d+\.\d+)%/'
+  dependencies:
+    - compile
+  artifacts:
+    reports:
+      junit: plausible-report.xml
+
+build:docker:
+  <<: *elixir-commons
+  image:
+    name: gcr.io/kaniko-project/executor:debug
+    entrypoint: [""]
+  stage: build
+  variables:
+    MIX_ENV: prod
+    MIX_HOME: $CI_PROJECT_DIR/.mix/
+    APP_VERSION: $CI_COMMIT_SHORT_SHA
+  before_script:
+    - chmod +x .gitlab/build-scripts/*
+    - source .gitlab/build-scripts/docker.gitlab.sh
+    - docker_create_config
+  script:
+    - docker_build_image
+  dependencies:
+    - compile
+  only:
+    - master
+
+deploy:plausible:
+  stage: postbuild
+  script:
+    - "curl -X POST -F token=$PLAUSIBLE_DEPLOY_TOKEN -F ref=master -F variables[IMAGE_TAG]=${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA} $PLAUSIBLE_DEPLOY_PROJECT"
+  only:
+    - master
+
+container_scanning:
+  stage: postbuild
+  image: registry.gitlab.com/gitlab-org/security-products/analyzers/klar:$CS_MAJOR_VERSION
+  variables:
+    CS_MAJOR_VERSION: 2
+    KLAR_TRACE: "true"
+    CLAIR_TRACE: "true"
+    CLAIR_OUTPUT: "medium"
+    CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE
+    CI_APPLICATION_TAG: ${CI_COMMIT_REF_SLUG}-$CI_COMMIT_SHORT_SHA  
diff --git a/.gitlab/build-scripts/docker-entrypoint.sh b/.gitlab/build-scripts/docker-entrypoint.sh
new file mode 100755
index 00000000..b44a0135
--- /dev/null
+++ b/.gitlab/build-scripts/docker-entrypoint.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+set -e
+
+chmod a+x /app/*.sh
+
+if [[ "$1" = 'run' ]]; then
+      exec gosu plausibleuser /app/bin/plausible start
+
+elif [[ "$1" = 'db' ]]; then
+      exec gosu plausibleuser /app/"$2".sh
+ else
+      exec "$@"
+
+fi
+
+exec "$@"
+
diff --git a/.gitlab/build-scripts/docker.gitlab.sh b/.gitlab/build-scripts/docker.gitlab.sh
new file mode 100644
index 00000000..4cc663ca
--- /dev/null
+++ b/.gitlab/build-scripts/docker.gitlab.sh
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+
+############################
+function docker_create_config() {
+############################
+  mkdir -p /kaniko/.docker/
+  echo "###############"
+  echo "Logging into GitLab Container Registry with CI credentials for kaniko..."
+  echo "###############"
+  echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
+  echo ""
+
+}
+
+
+############################
+function docker_build_image() {
+############################
+  if [[ -f Dockerfile ]]; then
+    echo "###############"
+    echo "Building Dockerfile-based application..."
+    echo "###############"
+
+    /kaniko/executor \
+      --cache=true \
+      --context ${CI_PROJECT_DIR} \
+      --dockerfile ${CI_PROJECT_DIR}/Dockerfile \
+      --destination ${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}  \
+      --destination ${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG}-latest  \
+      \
+      "$@"
+
+  else
+    echo "No Dockerfile found."
+    return 1
+  fi
+}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..1b63605b
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,82 @@
+# we can not use the pre-built tar because the distribution is
+# platform specific, it makes sense to build it in the docker
+
+#### Builder
+FROM elixir:1.10.3 as buildcontainer
+
+# preparation
+ARG APP_VER=0.0.1
+ENV GOSU_VERSION 1.11
+ENV MIX_ENV=prod
+ENV NODE_ENV=production
+ENV APP_VERSION=$APP_VER
+
+RUN mkdir /app
+WORKDIR /app
+
+# install build dependencies
+RUN apt-get update  && \
+    apt-get install -y git build-essential nodejs yarn python npm --no-install-recommends && \
+    npm install npm@latest -g && \
+    npm install -g webpack
+
+RUN apt-get install -y --no-install-recommends ca-certificates wget \
+    && apt-get install -y --install-recommends gnupg2 dirmngr
+
+# grab gosu for easy step-down from root
+RUN set -x \
+    && dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \
+    && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \
+    && wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc" \
+    && export GNUPGHOME="$(mktemp -d)" \
+    && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 \
+    && gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu \
+    && command -v gpgconf && gpgconf --kill all || : \
+    && rm -r "$GNUPGHOME" /usr/local/bin/gosu.asc \
+    && chmod +x /usr/local/bin/gosu \
+    && gosu --version \
+    && gosu nobody true
+
+COPY config ./config
+COPY assets ./assets
+COPY priv ./priv
+COPY lib ./lib
+COPY mix.exs ./
+COPY mix.lock ./
+RUN mix local.hex --force && \
+        mix local.rebar --force && \
+        mix deps.get --only prod && \
+        mix deps.compile
+
+RUN npm audit fix --prefix ./assets && \
+    npm install --prefix ./assets && \
+    npm run deploy --prefix ./assets && \
+    mix phx.digest priv/static
+
+WORKDIR /app
+COPY rel rel
+RUN mix release plausible
+
+
+# Main Docker Image
+FROM debian:bullseye
+LABEL maintainer="tckb <tckb@tgrthi.me>"
+ENV LANG=C.UTF-8
+
+RUN apt-get update && \
+    apt-get install -y bash openssl --no-install-recommends&& \
+    apt-get clean autoclean && \
+    apt-get autoremove --yes && \
+    rm -rf /var/lib/{apt,dpkg,cache,log}/
+
+COPY .gitlab/build-scripts/docker-entrypoint.sh /entrypoint.sh
+
+RUN chmod a+x /entrypoint.sh && \
+    useradd -d /app -u 1000 -s /bin/bash -m plausibleuser
+
+COPY --from=buildcontainer /usr/local/bin/gosu /usr/local/bin/gosu
+COPY --from=buildcontainer /app/_build/prod/rel/plausible /app
+RUN chown -R plausibleuser:plausibleuser /app
+WORKDIR /app
+ENTRYPOINT ["/entrypoint.sh"]
+CMD ["run"]
diff --git a/HOSTING.md b/HOSTING.md
new file mode 100644
index 00000000..2bafd3f2
--- /dev/null
+++ b/HOSTING.md
@@ -0,0 +1,152 @@
+# Plausible Analytics
+Self-hosting is possible based on the docker images and are automatically pushed into [Gitlab hosted docker](registry.gitlab.com/tckb-public/plausible) registry for all commits on `master` branch.    
+All `master-*` tags are considered to be stable and are persisted. Any other tag in the registry is considered to be for development purposes and/or unstable and are auto-deleted after a week.
+
+
+### Building Docker image
+Besides the GitlabCI, one can build docker image from [Dockerfile](./Dockerfile). 
+
+#### Up and Running
+The repo supplies with a [Docker Compose](./docker-compose.yml) file, this serves as a sample for running Plausible with Docker. 
+In this sample, the db migration is done by default on startup, so you need to clean the data up every time you run:  
+
+First run
+```bash
+$ docker-compose up
+```
+
+subsequent runs--
+```bash
+$ docker-compose down
+$ docker volume rm plausible_db-data -f
+$ docker-compose up
+```
+
+### Non-docker building
+It is possible to create a release artifact by running a release. 
+
+```elixir
+MIX_ENV=prod mix release plausible
+```
+the release will create the pre-packed artifact at `_build/prod/rel/plausible/bin/plausible`, the release will also create a tarball at `_build/prod/` for convenience. 
+
+Note, that you have to feed in the related environment variables (see below `Environment Variables`)
+
+## Database Migration
+On the initial setup, a migration step is necessary to create database and table schemas needed for initial bootup. 
+Normally, this done by mix aliases like `ecto.setup` defined in the `mix.exs`. As this not available in "released" artifact,  [`plausible_migration.ex`](./lib/plausible_migration.ex) facilitates this process. 
+The overlay [scripts](./rel/overlays) take care of these.
+
+After the release, these are available under  `_build/prod/rel/plausible` --
+
+
+```bash
+_build/prod/rel/plausible/createdb.sh
+_build/prod/rel/plausible/migrate.sh
+_build/prod/rel/plausible/rollback.sh
+_build/prod/rel/plausible/seed.sh
+```
+
+the same is available in the docker images as follows --
+
+```bash
+docker run plausible:master-12add db createdb
+docker run plausible:master-12add db migrate
+docker run plausible:master-12add db rollback
+docker run plausible:master-12add db seed
+```
+
+
+## Environment Variables
+Plausible relies on the several services for operating, the expected environment variables are explaiend below.  
+
+### Server
+Following are the variables that can be used to configure the availability of the server.
+
+- HOST (*String*)
+    - The hosting address of the server. For running on local system, this can be set to **localhost**. In production systems, this can be your ingress host.
+- PORT (*Number*)
+    - The port on which the server is available. 
+- SECRET_KEY_BASE (*String*)
+    - An internal secret key used by [Phoenix Framework](https://www.phoenixframework.org/). Follow the [instructions](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Secret.html#content) to generate one.
+- ENVIRONMENT (*String*)
+    - The current running environment. _defaults to **prod**_ 
+- APP_VERSION (*String*)
+    - The version of the app running. _defaults to current docker tag_ 
+    
+### Default User Generation
+For self-hosting, a default user is generated during the [Database Migration](#Database Migration) to access Plausible. To be noted that, a default user is a user whose trial period expires in 100 Years ;). 
+It is *highly* recommended that you configure these parameters.
+
+- ADMIN_USER_NAME
+    - The default ("admin") username. _if not provided, one will be generated for you_    
+- ADMIN_USER_EMAIL
+    - The default ("admin") user email. _if not provided, one will be generated for you_   
+- ADMIN_USER_PWD
+    - The default ("admin") user password. _if not provided, one will be generated for you_           
+
+### Mailer/SMTP Setup
+
+- MAILER_ADAPTER (*String*)
+    - The adapter used for sending out e-mails. Available: `Bamboo.PostmarkAdapter` / `Bamboo.SMTPAdapter`
+- MAILER_EMAIL (*String*)
+    - The email id to use for as _from_ address of all communications from Plausible. 
+
+In case of `Bamboo.SMTPAdapter` you need to supply the following variables: 
+    
+- SMTP_HOST_ADDR (*String*)
+    - The host address of your smtp server.
+- SMTP_HOST_PORT (*Number*)
+    - The port of your smtp server. 
+- SMTP_USER_NAME (*String*)
+    - The username/email for smtp auth.
+- SMTP_USER_PWD (*String*)
+    - The password for smtp auth.
+- SMTP_HOST_SSL_ENABLED (*Boolean String*)
+    - If ssl is enabled for connecting to Smtp, _defaults to `false`_
+- SMTP_RETRIES (*Number*)
+    - Number of retries to make until mailer gives up. _defaults to `2`_
+- SMTP_MX_LOOKUPS_ENABLED (*Boolean String*)
+    - If MX lookups should be done before sending out emails. _defaults to `false`_ 
+
+### Database
+
+Plausible uses postgresql as database for storing all the user-data. Use the following the variables to configure it.
+
+- DATABASE_URL (*String*)
+    - The repo Url as dictated [here](https://hexdocs.pm/ecto/Ecto.Repo.html#module-urls)
+- DATABASE_POOL_SIZE (*Number*)
+    -  A default pool size for connecting to the database, defaults to *10*, a higher number is recommended for a production system.
+- DATABASE_TLS_ENABLED (*Boolean String*)
+    - A flag that says whether to connect to the database via TLS, read [here](https://www.postgresql.org/docs/10/ssl-tcp.html)
+
+For performance reasons, all the analytics events are stored in clickhouse:
+
+- CLICKHOUSE_DATABASE_HOST (*String*)
+- CLICKHOUSE_DATABASE_NAME (*String*)
+- CLICKHOUSE_DATABASE_USER (*String*)
+- CLICKHOUSE_DATABASE_PASSWORD (*String*)
+- CLICKHOUSE_DATABASE_POOLSIZE (*Number*)
+    - A default pool size for connecting to the database, defaults to *10*, a higher number is recommended for a production system.
+
+### External Services
+
+- [Google Client](https://developers.google.com/api-client-library)
+    - GOOGLE_CLIENT_ID
+    - GOOGLE_CLIENT_SECRET
+- [Sentry](https://sentry.io/) 
+    - SENTRY_DSN
+- [Paddle](https://paddle.com/)
+    - PADDLE_VENDOR_AUTH_CODE
+- [PostMark](https://postmarkapp.com/), only in case of `Bamboo.PostmarkAdapter` mail adapter.
+    - POSTMARK_API_KEY
+
+Apart from these, there are also the following integrations 
+
+- [Twitter](https://developer.twitter.com/en/docs)
+    - TWITTER_CONSUMER_KEY
+    - TWITTER_CONSUMER_SECRET
+    - TWITTER_ACCESS_TOKEN
+    - TWITTER_ACCESS_TOKEN_SECRET
+- [Slack](https://api.slack.com/messaging/webhooks) 
+    - SLACK_WEBHOOK
diff --git a/README.md b/README.md
index a8d71988..9d34818c 100644
--- a/README.md
+++ b/README.md
@@ -21,9 +21,7 @@ Interested? [Read more on our website](https://plausible.io)
 
 ### Can Plausible Analytics be self-hosted?
 
-At the moment we don't provide support for easily self-hosting the code. Currently, the purpose of keeping the code open-source is to be transparent with the community about how we collect and process data.
-
-Making Plausible Analytics easy to self-host, providing full documentation and support for the process is something we want to see happening in the future. There is [a GitHub thread](https://github.com/plausible-insights/plausible/issues/26) you can join and engage with to follow our progress in making Plausible Analytics easy to self-host.
+The purpose of keeping the code open-source is to be transparent with the community about how we collect and process however, we do provide an experimental [docker-based self hosting](./HOSTING.md) setup. Please note that this is still in *alpha* stage and care should be taken while using it for production system. 
 
 ### Why is Plausible Analytics not free like Google Analytics?
 
diff --git a/config/config.exs b/config/config.exs
index 72c4ee3a..c09c04a2 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -1,19 +1,34 @@
-# This file is responsible for configuring your application
-# and its dependencies with the aid of the Mix.Config module.
-#
-# This configuration file is loaded before any dependency and
-# is restricted to this project.
-
-# General application configuration
 use Mix.Config
 
 config :plausible,
-  ecto_repos: [Plausible.Repo]
+  admin_user: System.get_env("ADMIN_USER_NAME", "admin"),
+  admin_email: System.get_env("ADMIN_USER_EMAIL", "admin@plausible.local"),
+  mailer_email: System.get_env("MAILER_EMAIL", "hello@plausible.local"),
+  admin_pwd: System.get_env("ADMIN_USER_PWD", "!@d3in"),
+  ecto_repos: [Plausible.Repo],
+  environment: System.get_env(Atom.to_string(Mix.env()), "dev")
+
+config :plausible, :clickhouse,
+       hostname: System.get_env("CLICKHOUSE_DATABASE_HOST", "localhost"),
+       database: System.get_env("CLICKHOUSE_DATABASE_NAME", "plausible_test"),
+       username: System.get_env("CLICKHOUSE_DATABASE_USER"),
+       password: System.get_env("CLICKHOUSE_DATABASE_PASSWORD"),
+       pool_size: 10
 
 # Configures the endpoint
 config :plausible, PlausibleWeb.Endpoint,
-  url: [host: "localhost"],
-  secret_key_base: "/NJrhNtbyCVAsTyvtk1ZYCwfm981Vpo/0XrVwjJvemDaKC/vsvBRevLwsc6u8RCg",
+  url: [
+    host: System.get_env("HOST", "localhost"),
+    port: String.to_integer(System.get_env("PORT", "8000"))
+  ],
+  http: [
+    port: String.to_integer(System.get_env("PORT", "8000"))
+  ],
+  secret_key_base:
+    System.get_env(
+      "SECRET_KEY_BASE",
+      "/NJrhNtbyCVAsTyvtk1ZYCwfm981Vpo/0XrVwjJvemDaKC/vsvBRevLwsc6u8RCg"
+    ),
   render_errors: [
     view: PlausibleWeb.ErrorView,
     accepts: ~w(html json)
@@ -21,11 +36,13 @@ config :plausible, PlausibleWeb.Endpoint,
   pubsub: [name: Plausible.PubSub, adapter: Phoenix.PubSub.PG2]
 
 config :sentry,
-  dsn: "https://0350a42aa6234a2eaf1230866788598e@sentry.io/1382353",
+  dsn: System.get_env("SENTRY_DSN"),
   included_environments: [:prod, :staging],
-  environment_name: String.to_atom(Map.get(System.get_env(), "APP_ENV", "dev")),
+  environment_name: String.to_atom(Map.get(System.get_env(), "MIX_ENV", "dev")),
   enable_source_code_context: true,
-  root_source_code_path: File.cwd!
+  root_source_code_path: File.cwd!(),
+  tags: %{app_version: System.get_env("APP_VERSION", "0.0.1")},
+  context_lines: 5
 
 # Configures Elixir's Logger
 config :logger, :console,
@@ -46,6 +63,7 @@ config :plausible,
   google_api: Plausible.Google.Api
 
 config :plausible,
+  # 30 minutes
   session_timeout: 1000 * 60 * 30, # 30 minutes
   session_length_minutes: 30
 
@@ -53,6 +71,58 @@ config :plausible, :paddle,
   vendor_id: "49430",
   vendor_auth_code: System.get_env("PADDLE_VENDOR_AUTH_CODE")
 
+config :plausible,
+       Plausible.Repo,
+       pool_size: String.to_integer(System.get_env("DATABASE_POOL_SIZE", "10")),
+       timeout: 300_000,
+       connect_timeout: 300_000,
+       handshake_timeout: 300_000,
+       url:
+         System.get_env(
+           "DATABASE_URL",
+           "postgres://postgres:postgres@127.0.0.1:5432/plausible_test?currentSchema=default"
+         ),
+       ssl: false
+
+config :plausible, :google,
+  client_id: System.get_env("GOOGLE_CLIENT_ID"),
+  client_secret: System.get_env("GOOGLE_CLIENT_SECRET")
+
+config :plausible, :slack, webhook: System.get_env("SLACK_WEBHOOK")
+
+mailer_adapter = System.get_env("MAILER_ADAPTER", "Bamboo.PostmarkAdapter")
+
+case mailer_adapter do
+  "Bamboo.PostmarkAdapter" ->
+    config :plausible, Plausible.Mailer,
+      adapter: :"Elixir.#{mailer_adapter}",
+      api_key: System.get_env("POSTMARK_API_KEY")
+
+  "Bamboo.SMTPAdapter" ->
+    config :plausible, Plausible.Mailer,
+      adapter: :"Elixir.#{mailer_adapter}",
+      server: System.fetch_env!("SMTP_HOST_ADDR"),
+      hostname: System.get_env("HOST", "localhost"),
+      port: System.fetch_env!("SMTP_HOST_PORT"),
+      username: System.fetch_env!("SMTP_USER_NAME"),
+      password: System.fetch_env!("SMTP_USER_PWD"),
+      tls: :if_available,
+      allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"],
+      ssl: System.get_env("SMTP_HOST_SSL_ENABLED") || true,
+      retries: System.get_env("SMTP_RETRIES") || 2,
+      no_mx_lookups: System.get_env("SMTP_MX_LOOKUPS_ENABLED") || true,
+      auth: :always
+
+  _ ->
+    raise "Unknown mailer_adapter; expected SMTPAdapter or PostmarkAdapter"
+end
+
+config :plausible, :twitter,
+  consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
+  consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET"),
+  token: System.get_env("TWITTER_ACCESS_TOKEN"),
+  token_secret: System.get_env("TWITTER_ACCESS_TOKEN_SECRET")
+
 # Import environment specific config. This must remain at the bottom
 # of this file so it overrides the configuration defined above.
 import_config "#{Mix.env()}.exs"
diff --git a/config/dev.exs b/config/dev.exs
index f8be6ec5..5da1a099 100644
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -1,13 +1,7 @@
 use Mix.Config
 
-# For development, we disable any cache and enable
-# debugging and code reloading.
-#
-# The watchers configuration can be used to run external
-# watchers to your application. For example, we use it
-# with webpack to recompile .js and .css sources.
 config :plausible, PlausibleWeb.Endpoint,
-  http: [port: 8000],
+  server: true,
   debug_errors: true,
   code_reloader: true,
   check_origin: false,
@@ -35,21 +29,8 @@ config :logger, :console, format: "[$level] $message\n"
 config :phoenix, :stacktrace_depth, 20
 config :phoenix, :plug_init_mode, :runtime
 
-config :plausible, :clickhouse,
-  hostname: "localhost",
-  database: "plausible_dev",
-  pool_size: 10
-
-config :plausible, Plausible.Repo,
-  username: "postgres",
-  password: "postgres",
-  database: "plausible_dev",
-  hostname: "localhost",
-  pool_size: 10
-
-config :plausible, Plausible.Mailer,
-  adapter: Bamboo.LocalAdapter
-
 if File.exists?("config/dev.secret.exs") do
   import_config "dev.secret.exs"
 end
+
+config :logger, level: :debug
diff --git a/config/prod.exs b/config/prod.exs
index 3691c77c..3ccd578d 100644
--- a/config/prod.exs
+++ b/config/prod.exs
@@ -1,105 +1,7 @@
 use Mix.Config
 
-# For production, don't forget to configure the url host
-# to something meaningful, Phoenix uses this information
-# when generating URLs.
-#
-# Note we also include the path to a cache manifest
-# containing the digested version of static files. This
-# manifest is generated by the `mix phx.digest` task,
-# which you should run after static files are built and
-# before starting your production server.
-config :plausible, PlausibleWeb.Endpoint,
-  http: [:inet6, port: System.get_env("PORT") || 4000],
-  url: [host: System.get_env("HOST"), scheme: "https", port: 443],
-  cache_static_manifest: "priv/static/cache_manifest.json"
+# For the actual-production deployments we will use releases,
+# i.e., "releases.exs" is the _actual_ production config
+# see "releases.exs"
 
-# Do not print debug messages in production
-config :logger, level: :info
-
-# ## SSL Support
-#
-# To get SSL working, you will need to add the `https` key
-# to the previous section and set your `:url` port to 443:
-#
-#     config :plausible, PlausibleWeb.Endpoint,
-#       ...
-#       url: [host: "example.com", port: 443],
-#       https: [
-#         :inet6,
-#         port: 443,
-#         cipher_suite: :strong,
-#         keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
-#         certfile: System.get_env("SOME_APP_SSL_CERT_PATH")
-#       ]
-#
-# The `cipher_suite` is set to `:strong` to support only the
-# latest and more secure SSL ciphers. This means old browsers
-# and clients may not be supported. You can set it to
-# `:compatible` for wider support.
-#
-# `:keyfile` and `:certfile` expect an absolute path to the key
-# and cert in disk or a relative path inside priv, for example
-# "priv/ssl/server.key". For all supported SSL configuration
-# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
-#
-# We also recommend setting `force_ssl` in your endpoint, ensuring
-# no data is ever sent via http, always redirecting to https:
-#
-#     config :plausible, PlausibleWeb.Endpoint,
-#       force_ssl: [hsts: true]
-#
-# Check `Plug.SSL` for all available options in `force_ssl`.
-
-# ## Using releases (distillery)
-#
-# If you are doing OTP releases, you need to instruct Phoenix
-# to start the server for all endpoints:
-#
-#     config :phoenix, :serve_endpoints, true
-#
-# Alternatively, you can configure exactly which server to
-# start per endpoint:
-#
-#     config :plausible, PlausibleWeb.Endpoint, server: true
-#
-# Note you can't rely on `System.get_env/1` when using releases.
-# See the releases documentation accordingly.
-
-# Finally import the config/prod.secret.exs which should be versioned
-# separately.
-config :plausible, PlausibleWeb.Endpoint,
-  secret_key_base: System.get_env("SECRET_KEY_BASE")
-
-config :plausible, :clickhouse,
-  hostname: System.get_env("CLICKHOUSE_DATABASE_HOST"),
-  database: System.get_env("CLICKHOUSE_DATABASE_NAME"),
-  username: System.get_env("CLICKHOUSE_DATABASE_USER"),
-  password: System.get_env("CLICKHOUSE_DATABASE_PASSWORD"),
-  pool_size: 30
-
-# Configure your database
-config :plausible, Plausible.Repo,
-  adapter: Ecto.Adapters.Postgres,
-  url: System.get_env("AVIEN_DATABASE_URL"),
-  pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
-  timeout: 10_000,
-  ssl: true
-
-config :plausible, :google,
-  client_id: System.get_env("GOOGLE_CLIENT_ID"),
-  client_secret: System.get_env("GOOGLE_CLIENT_SECRET")
-
-config :plausible, :slack,
- webhook: System.get_env("SLACK_WEBHOOK")
-
-config :plausible, Plausible.Mailer,
-  adapter: Bamboo.PostmarkAdapter,
-  api_key: System.get_env("POSTMARK_API_KEY")
-
-config :plausible, :twitter, [
-   consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
-   consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET"),
-   token: System.get_env("TWITTER_ACCESS_TOKEN"),
-   token_secret: System.get_env("TWITTER_ACCESS_TOKEN_SECRET")
-]
+import_config "releases.exs"
diff --git a/config/releases.exs b/config/releases.exs
new file mode 100644
index 00000000..cae7097e
--- /dev/null
+++ b/config/releases.exs
@@ -0,0 +1,128 @@
+import Config
+
+### Mandatory params Start
+# it is highly recommended to change this parameters in production systems
+# params are made optional to facilitate smooth release
+
+port = System.get_env("PORT") || 8000
+host = System.get_env("HOST", "localhost")
+
+secret_key_base =
+  System.get_env(
+    "SECRET_KEY_BASE",
+    "/NJrhNtbyCVAsTyvtk1ZYCwfm981Vpo/0XrVwjJvemDaKC/vsvBRevLwsc6u8RCg"
+  )
+
+db_pool_size = String.to_integer(System.get_env("DATABASE_POOL_SIZE", "10"))
+
+db_url =
+  System.get_env(
+    "DATABASE_URL",
+    "postgres://postgres:postgres@127.0.0.1:5432/plausible_test?currentSchema=default"
+  )
+
+db_tls_enabled? = String.to_existing_atom(System.get_env("DATABASE_TLS_ENABLED", "false"))
+admin_user = System.get_env("ADMIN_USER_NAME")
+admin_email = System.get_env("ADMIN_USER_EMAIL")
+admin_pwd = System.get_env("ADMIN_USER_PWD")
+env = System.get_env("ENVIRONMENT", "prod")
+mailer_adapter = System.get_env("MAILER_ADAPTER", "Bamboo.PostmarkAdapter")
+mailer_email = System.get_env("MAILER_EMAIL", "hello@plausible.local")
+app_version = System.get_env("APP_VERSION", "0.0.1")
+ck_host = System.get_env("CLICKHOUSE_DATABASE_HOST", "localhost")
+ck_db = System.get_env("CLICKHOUSE_DATABASE_NAME", "plausible_dev")
+ck_db_user = System.get_env("CLICKHOUSE_DATABASE_USER")
+ck_db_pwd = System.get_env("CLICKHOUSE_DATABASE_PASSWORD")
+ck_db_pool = System.get_env("CLICKHOUSE_DATABASE_POOLSIZE") || 10
+### Mandatory params End
+
+sentry_dsn = System.get_env("SENTRY_DSN")
+paddle_auth_code = System.get_env("PADDLE_VENDOR_AUTH_CODE")
+google_cid = System.get_env("GOOGLE_CLIENT_ID")
+google_secret = System.get_env("GOOGLE_CLIENT_SECRET")
+slack_hook_url = System.get_env("SLACK_WEBHOOK")
+twitter_consumer_key = System.get_env("TWITTER_CONSUMER_KEY")
+twitter_consumer_secret = System.get_env("TWITTER_CONSUMER_SECRET")
+twitter_token = System.get_env("TWITTER_ACCESS_TOKEN")
+twitter_token_secret = System.get_env("TWITTER_ACCESS_TOKEN_SECRET")
+postmark_api_key = System.get_env("POSTMARK_API_KEY")
+
+config :plausible,
+  admin_user: admin_user,
+  admin_email: admin_email,
+  admin_pwd: admin_pwd,
+  environment: env,
+  mailer_email: mailer_email
+
+config :plausible, PlausibleWeb.Endpoint,
+  url: [host: host, port: port],
+  http: [
+    port: port
+  ],
+  secret_key_base: secret_key_base,
+  cache_static_manifest: "priv/static/cache_manifest.json",
+  check_origin: false,
+  load_from_system_env: true,
+  server: true,
+  code_reloader: false
+
+config :plausible,
+       Plausible.Repo,
+       pool_size: db_pool_size,
+       url: db_url,
+       adapter: Ecto.Adapters.Postgres,
+       ssl: db_tls_enabled?
+
+config :sentry,
+  dsn: sentry_dsn,
+  environment_name: env,
+  release: app_version,
+  tags: %{app_version: app_version}
+
+config :plausible, :paddle, vendor_auth_code: paddle_auth_code
+
+config :plausible, :google,
+  client_id: google_cid,
+  client_secret: google_secret
+
+config :plausible, :slack, webhook: slack_hook_url
+
+config :plausible, :clickhouse,
+       hostname: ck_host,
+       database: ck_db,
+       username: ck_db_user,
+       password: ck_db_pwd,
+       pool_size: ck_db_pool
+
+case mailer_adapter do
+  "Bamboo.PostmarkAdapter" ->
+    config :plausible, Plausible.Mailer,
+      adapter: :"Elixir.#{mailer_adapter}",
+      api_key: System.get_env("POSTMARK_API_KEY")
+
+  "Bamboo.SMTPAdapter" ->
+    config :plausible, Plausible.Mailer,
+      adapter: :"Elixir.#{mailer_adapter}",
+      server: System.fetch_env!("SMTP_HOST_ADDR"),
+      hostname: System.get_env("HOST", "localhost"),
+      port: System.fetch_env!("SMTP_HOST_PORT"),
+      username: System.fetch_env!("SMTP_USER_NAME"),
+      password: System.fetch_env!("SMTP_USER_PWD"),
+      tls: :if_available,
+      allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"],
+      ssl: System.get_env("SMTP_HOST_SSL_ENABLED") || true,
+      retries: System.get_env("SMTP_RETRIES") || 2,
+      no_mx_lookups: System.get_env("SMTP_MX_LOOKUPS_ENABLED") || true,
+      auth: :always
+
+  _ ->
+    raise "Unknown mailer_adapter; expected SMTPAdapter or PostmarkAdapter"
+end
+
+config :plausible, :twitter,
+  consumer_key: twitter_consumer_key,
+  consumer_secret: twitter_consumer_secret,
+  token: twitter_token,
+  token_secret: twitter_token_secret
+
+config :logger, level: :warn
diff --git a/config/test.exs b/config/test.exs
index 337864fe..eae3c2c1 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -13,24 +13,23 @@ config :logger, level: :warn
 config :bcrypt_elixir, :log_rounds, 4
 
 # Configure your database
-config :plausible, Plausible.Repo,
-  username: "postgres",
-  password: "postgres",
-  database: "plausible_test",
-  hostname: "localhost",
-  pool: Ecto.Adapters.SQL.Sandbox
-
-config :plausible, :clickhouse,
-  hostname: "localhost",
-  database: "plausible_test",
-  pool_size: 10
+config :plausible,
+       Plausible.Repo,
+       pool: Ecto.Adapters.SQL.Sandbox
 
-config :plausible, Plausible.Mailer,
-  adapter: Bamboo.TestAdapter
+config :plausible, Plausible.Mailer, adapter: Bamboo.TestAdapter
 
 config :plausible,
   paddle_api: Plausible.PaddleApi.Mock,
   google_api: Plausible.Google.Api.Mock
 
+
+config :junit_formatter,
+  report_file: "report.xml",
+  report_dir: File.cwd!(),
+  print_report_file: true,
+  prepend_project_name?: true,
+  include_filename?: true
+
 config :plausible,
-  session_timeout: 0
+       session_timeout: 0
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..a27d2dd9
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,79 @@
+# NOTE:
+# This Docker-compose file is created as a sample and should not be used directly for production
+# You can adjust the settings as-per your environment
+version: "3.3"
+services:
+
+# As it says, this service is a fake smtp server which accepts SMTP connections
+# the inbox is available at localhost:8025, in actuality, you need to use a proper smtp server
+  fakesmtp_server:
+    container_name: fakesmtp_server
+    image: mailhog/mailhog:v1.0.0
+    ports:
+    - 1025:1025
+    - 8025:8025
+    healthcheck:
+      test: echo | telnet 127.0.0.1 1025
+
+
+  plausible_db:
+    container_name: plausible_db
+    image: postgres:9.4
+    command: ["postgres", "-c", "log_statement=all", "-c", "log_destination=stderr"]
+    volumes:
+      - db-data:/var/lib/postgresql/data
+    environment:
+      - POSTGRES_PASSWORD=postgres
+      - POSTGRES_DB=plausible_db
+      - POSTGRES_USER=postgres
+    ports:
+      - 5432:5432
+
+  plausible_events_db:
+    container_name: plausible_events_db
+    image: yandex/clickhouse-server
+    ports:
+    - 8123:8123
+
+  plausible:
+    container_name: plausible
+    build:
+      context: .
+      dockerfile: ./Dockerfile
+    command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate &&/entrypoint.sh run"
+    environment:
+      - ENVIRONMENT=production
+      - PORT=8080
+      - SECRET_KEY_BASE=iYb1mP5cnmY+gUxo7C/h6XMigossPhzwd8/ic6LFnQ9Y58Fl1xduSWaPq0fHDdbn
+      - SIGNING_SALT=PL/THF0VMOzuv1bOcldjDzYFBLryvXNs
+      - HOST=localhost
+      - DATABASE_URL=postgres://postgres:postgres@plausible_db:5432/plausible
+      - DATABASE_TLS_ENABLED=false
+      - ADMIN_USER_NAME=admin
+      - ADMIN_USER_EMAIL=admin@plausible.local
+      - ADMIN_USER_PWD=admin@1234!
+      - APP_VERSION=test
+      - MAILER_ADAPTER=Bamboo.SMTPAdapter
+      - SMTP_HOST_ADDR=fakesmtp_server
+      - SMTP_HOST_PORT=1025
+      - SMTP_USER_NAME=fakeuser@plausible.local
+      - SMTP_USER_PWD=password
+      - SMTP_HOST_SSL_ENABLED=false
+      - SMTP_MX_LOOKUPS_ENABLED=false
+      - CLICKHOUSE_DATABASE_HOST=plausible_events_db
+      - CLICKHOUSE_DATABASE_NAME=plausible_events_db
+    depends_on:
+      - plausible_events_db
+      - plausible_db
+      - fakesmtp_server
+    ports:
+      - 80:8080
+    links:
+      - plausible_db
+      - plausible_events_db
+      - fakesmtp_server
+
+
+volumes:
+  db-data:
+    driver: local
\ No newline at end of file
diff --git a/lib/mix/tasks/hydrate_clickhouse.ex b/lib/mix/tasks/hydrate_clickhouse.ex
index a8f9776b..ff687ac1 100644
--- a/lib/mix/tasks/hydrate_clickhouse.ex
+++ b/lib/mix/tasks/hydrate_clickhouse.ex
@@ -20,6 +20,7 @@ defmodule Mix.Tasks.HydrateClickhouse do
     hydrate_events(repo)
   end
 
+
   def create_events() do
     ddl = """
     CREATE TABLE IF NOT EXISTS events (
diff --git a/lib/mix/tasks/send_check_stats_emails.ex b/lib/mix/tasks/send_check_stats_emails.ex
index 23063f8c..8532dd8d 100644
--- a/lib/mix/tasks/send_check_stats_emails.ex
+++ b/lib/mix/tasks/send_check_stats_emails.ex
@@ -39,7 +39,7 @@ defmodule Mix.Tasks.SendCheckStatsEmails do
 
   defp send_check_stats_email(_, user) do
     PlausibleWeb.Email.check_stats_email(user)
-    |> Plausible.Mailer.deliver_now()
+    |> Plausible.Mailer.send_email()
 
     Repo.insert_all("check_stats_emails", [%{
       user_id: user.id,
diff --git a/lib/mix/tasks/send_email_reports.ex b/lib/mix/tasks/send_email_reports.ex
index a11d3100..3c1ab90f 100644
--- a/lib/mix/tasks/send_email_reports.ex
+++ b/lib/mix/tasks/send_email_reports.ex
@@ -91,7 +91,8 @@ defmodule Mix.Tasks.SendEmailReports do
       pages: pages,
       query: query,
       name: name
-    ) |> Plausible.Mailer.deliver_now()
+    )
+    |> Plausible.Mailer.send_email()
   end
 
   defp weekly_report_sent(site, time) do
diff --git a/lib/mix/tasks/send_site_setup_emails.ex b/lib/mix/tasks/send_site_setup_emails.ex
index 6b855c6d..b9720cff 100644
--- a/lib/mix/tasks/send_site_setup_emails.ex
+++ b/lib/mix/tasks/send_site_setup_emails.ex
@@ -80,7 +80,7 @@ defmodule Mix.Tasks.SendSiteSetupEmails do
 
   defp send_create_site_email(_, user) do
     PlausibleWeb.Email.create_site_email(user)
-    |> Plausible.Mailer.deliver_now()
+    |> Plausible.Mailer.send_email()
 
     Repo.insert_all("create_site_emails", [%{
       user_id: user.id,
@@ -94,7 +94,7 @@ defmodule Mix.Tasks.SendSiteSetupEmails do
 
   defp send_setup_success_email(_, user, site) do
     PlausibleWeb.Email.site_setup_success(user, site)
-    |> Plausible.Mailer.deliver_now()
+    |> Plausible.Mailer.send_email()
 
     Repo.insert_all("setup_success_emails", [%{
       site_id: site.id,
@@ -108,7 +108,7 @@ defmodule Mix.Tasks.SendSiteSetupEmails do
 
   defp send_setup_help_email(_, user, site) do
     PlausibleWeb.Email.site_setup_help(user, site)
-    |> Plausible.Mailer.deliver_now()
+    |> Plausible.Mailer.send_email()
 
     Repo.insert_all("setup_help_emails", [%{
       site_id: site.id,
diff --git a/lib/mix/tasks/send_trial_notifications.ex b/lib/mix/tasks/send_trial_notifications.ex
index 4f2bbc28..41b844f2 100644
--- a/lib/mix/tasks/send_trial_notifications.ex
+++ b/lib/mix/tasks/send_trial_notifications.ex
@@ -52,7 +52,7 @@ defmodule Mix.Tasks.SendTrialNotifications do
 
   defp send_one_week_reminder(_, user) do
     PlausibleWeb.Email.trial_one_week_reminder(user)
-    |> Plausible.Mailer.deliver_now()
+    |> Plausible.Mailer.send_email()
   end
 
   defp send_tomorrow_reminder(["--dry-run"], user) do
@@ -63,7 +63,7 @@ defmodule Mix.Tasks.SendTrialNotifications do
     usage = Plausible.Billing.usage(user)
 
     PlausibleWeb.Email.trial_upgrade_email(user, "tomorrow", usage)
-    |> Plausible.Mailer.deliver_now()
+    |> Plausible.Mailer.send_email()
   end
 
   defp send_today_reminder(["--dry-run"], user) do
@@ -74,7 +74,7 @@ defmodule Mix.Tasks.SendTrialNotifications do
     usage = Plausible.Billing.usage(user)
 
     PlausibleWeb.Email.trial_upgrade_email(user, "today", usage)
-    |> Plausible.Mailer.deliver_now()
+    |> Plausible.Mailer.send_email()
   end
 
   defp send_over_reminder(["--dry-run"], user) do
diff --git a/lib/plausible/google/api.ex b/lib/plausible/google/api.ex
index 21a7d1fc..417813ab 100644
--- a/lib/plausible/google/api.ex
+++ b/lib/plausible/google/api.ex
@@ -3,7 +3,7 @@ defmodule Plausible.Google.Api do
   @verified_permission_levels ["siteOwner", "siteFullUser", "siteRestrictedUser"]
 
   def authorize_url(site_id) do
-    if Mix.env() == :test do
+    if Application.get_env(:plausible, :environment) == "test" do
       ""
     else
       "https://accounts.google.com/o/oauth2/v2/auth?client_id=#{client_id()}&redirect_uri=#{redirect_uri()}&prompt=consent&response_type=code&access_type=offline&scope=#{@scope}&state=#{site_id}"
diff --git a/lib/plausible/mailer.ex b/lib/plausible/mailer.ex
index 60b06a7b..220a655f 100644
--- a/lib/plausible/mailer.ex
+++ b/lib/plausible/mailer.ex
@@ -1,3 +1,17 @@
 defmodule Plausible.Mailer do
   use Bamboo.Mailer, otp_app: :plausible
+
+  def send_email(email) do
+    try do
+      Plausible.Mailer.deliver_now(email)
+    rescue
+      error ->
+        Sentry.capture_exception(error,
+          stacktrace: __STACKTRACE__,
+          extra: %{extra: "Error while sending email"}
+        )
+
+        raise error
+    end
+  end
 end
diff --git a/lib/plausible_release.ex b/lib/plausible_release.ex
new file mode 100644
index 00000000..a018c67c
--- /dev/null
+++ b/lib/plausible_release.ex
@@ -0,0 +1,234 @@
+defmodule Plausible.Release do
+  use Plausible.Repo
+  @app :plausible
+  @start_apps [
+    :postgrex,
+    :ecto
+  ]
+  alias Mix.Tasks.HydrateClickhouse, as: Clickhouse
+
+  def init_admin do
+    {admin_email, admin_user, admin_pwd} =
+      validate_admin(
+        {Application.get_env(:plausible, :admin_email),
+         Application.get_env(:plausible, :admin_user),
+         Application.get_env(:plausible, :admin_pwd)}
+      )
+
+    {:ok, admin} = Plausible.Auth.create_user(admin_user, admin_email)
+    # set the password
+    {:ok, admin} = Plausible.Auth.User.set_password(admin, admin_pwd) |> Repo.update()
+    # bump-up the trail period
+    admin
+    |> Ecto.Changeset.cast(%{trial_expiry_date: Timex.today() |> Timex.shift(years: 100)}, [
+      :trial_expiry_date
+    ])
+    |> Repo.update()
+
+    IO.puts("Admin user created successful!")
+  end
+
+  def migrate do
+    prepare()
+    Enum.each(repos(), &run_migrations_for/1)
+    init_admin()
+    IO.puts("Migrations successful!")
+  end
+
+  def seed do
+    prepare()
+    # Run seed script
+    Enum.each(repos(), &run_seeds_for/1)
+
+    # Signal shutdown
+    IO.puts("Success!")
+  end
+
+  def createdb do
+    prepare()
+    do_create_db()
+    IO.puts("Creation of Db successful!")
+  end
+
+  def rollback do
+    prepare()
+
+    get_step =
+      IO.gets("Enter the number of steps: ")
+      |> String.trim()
+      |> Integer.parse()
+
+    case get_step do
+      {int, _trailing} ->
+        Enum.each(repos(), fn repo -> run_rollbacks_for(repo, int) end)
+        IO.puts("Rollback successful!")
+
+      :error ->
+        IO.puts("Invalid integer")
+    end
+  end
+
+  ##############################
+
+  defp validate_admin({nil, nil, nil}) do
+    random_user = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
+    random_pwd = :crypto.strong_rand_bytes(20) |> Base.encode64() |> binary_part(0, 20)
+    random_email = "#{random_user}@#{System.get_env("HOST")}"
+    IO.puts("generated admin user/password: #{random_email} / #{random_pwd}")
+    {random_email, random_user, random_pwd}
+  end
+
+  defp validate_admin({admin_email, admin_user, admin_password}) do
+    {admin_email, admin_user, admin_password}
+  end
+
+  defp repos do
+    Application.fetch_env!(@app, :ecto_repos)
+  end
+
+  defp run_seeds_for(repo) do
+    # Run the seed script if it exists
+    seed_script = seeds_path(repo)
+
+    if File.exists?(seed_script) do
+      IO.puts("Running seed script..")
+      Code.eval_file(seed_script)
+    end
+  end
+
+  defp run_migrations_for(repo) do
+    app = Keyword.get(repo.config, :otp_app)
+    IO.puts("Running migrations for #{app}")
+    {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
+  end
+
+  defp do_create_db do
+    for repo <- repos() do
+      :ok = ensure_repo_created(repo)
+    end
+    do_create_ch_db()
+  end
+
+
+  defp do_create_ch_db() do
+    db_to_create = Keyword.get(Application.get_env(:plausible, :clickhouse),:database)
+
+    IO.puts("create #{inspect(db_to_create)} clickhouse database/tables if it doesn't exist")
+
+    Clickhousex.query(:clickhouse, "CREATE DATABASE IF NOT EXISTS #{db_to_create}", [])
+
+      tb_events = """
+      CREATE TABLE IF NOT EXISTS #{db_to_create}.events (
+        timestamp DateTime,
+        name String,
+        domain String,
+        user_id UInt64,
+        session_id UInt64,
+        hostname String,
+        pathname String,
+        referrer String,
+        referrer_source String,
+        initial_referrer String,
+        initial_referrer_source String,
+        country_code LowCardinality(FixedString(2)),
+        screen_size LowCardinality(String),
+        operating_system LowCardinality(String),
+        browser LowCardinality(String)
+      ) ENGINE = MergeTree()
+      PARTITION BY toYYYYMM(timestamp)
+      ORDER BY (name, domain, user_id, timestamp)
+      SETTINGS index_granularity = 8192
+      """
+
+    Clickhousex.query(:clickhouse, tb_events, [])
+
+    tb_sessions = """
+      CREATE TABLE IF NOT EXISTS #{db_to_create}.sessions (
+        session_id UInt64,
+        sign Int8,
+        domain String,
+        user_id UInt64,
+        hostname String,
+        timestamp DateTime,
+        start DateTime,
+        is_bounce UInt8,
+        entry_page String,
+        exit_page String,
+        pageviews Int32,
+        events Int32,
+        duration UInt32,
+        referrer String,
+        referrer_source String,
+        country_code LowCardinality(FixedString(2)),
+        screen_size LowCardinality(String),
+        operating_system LowCardinality(String),
+        browser LowCardinality(String)
+      ) ENGINE = CollapsingMergeTree(sign)
+      PARTITION BY toYYYYMM(start)
+      ORDER BY (domain, user_id, session_id, start)
+      SETTINGS index_granularity = 8192
+      """
+
+      Clickhousex.query(:clickhouse, tb_sessions, [])
+  end
+
+
+  defp ensure_repo_created(repo) do
+    IO.puts("create #{inspect(repo)} database if it doesn't exist")
+
+    case repo.__adapter__.storage_up(repo.config) do
+      :ok -> :ok
+      {:error, :already_up} -> :ok
+      {:error, term} -> {:error, term}
+    end
+  end
+
+  defp run_rollbacks_for(repo, step) do
+    app = Keyword.get(repo.config, :otp_app)
+    IO.puts("Running rollbacks for #{app} (STEP=#{step})")
+
+    {:ok, _, _} =
+      Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, all: false, step: step))
+  end
+
+  defp prepare do
+    IO.puts("Loading #{@app}..")
+    # Load the code for myapp, but don't start it
+    :ok = Application.load(@app)
+
+    prepare_clickhouse()
+
+    IO.puts("Starting dependencies..")
+    # Start apps necessary for executing migrations
+    Enum.each(@start_apps, &Application.ensure_all_started/1)
+
+
+    # Start the Repo(s) for myapp
+    IO.puts("Starting repos..")
+    Enum.each(repos(), & &1.start_link(pool_size: 2))
+
+  end
+
+  defp prepare_clickhouse do
+    Application.ensure_all_started(:db_connection)
+    Application.ensure_all_started(:hackney)
+    Clickhousex.start_link([
+      scheme: :http,
+      port: 8123,
+      name: :clickhouse,
+      database: "default",
+      hostname: Keyword.get(Application.get_env(:plausible,:clickhouse),:hostname)
+    ])
+  end
+
+  defp seeds_path(repo), do: priv_path_for(repo, "seeds.exs")
+
+  defp priv_path_for(repo, filename) do
+    app = Keyword.get(repo.config, :otp_app)
+    IO.puts("App: #{app}")
+    repo_underscore = repo |> Module.split() |> List.last() |> Macro.underscore()
+    Path.join([priv_dir(app), repo_underscore, filename])
+  end
+
+  defp priv_dir(app), do: "#{:code.priv_dir(app)}"
+end
diff --git a/lib/plausible_web/controllers/auth_controller.ex b/lib/plausible_web/controllers/auth_controller.ex
index c1053be4..ea3e0be0 100644
--- a/lib/plausible_web/controllers/auth_controller.ex
+++ b/lib/plausible_web/controllers/auth_controller.ex
@@ -21,7 +21,7 @@ defmodule PlausibleWeb.AuthController do
         url = PlausibleWeb.Endpoint.clean_url() <> "/claim-activation?token=#{token}"
         Logger.info(url)
         email_template = PlausibleWeb.Email.activation_email(user, url)
-        Plausible.Mailer.deliver_now(email_template)
+        Plausible.Mailer.send_email(email_template)
         conn |> render("register_success.html", email: user.email, layout: {PlausibleWeb.LayoutView, "focus.html"})
       {:error, changeset} ->
         render(conn, "register_form.html", changeset: changeset, layout: {PlausibleWeb.LayoutView, "focus.html"})
@@ -34,7 +34,7 @@ defmodule PlausibleWeb.AuthController do
         case Auth.create_user(name, email) do
           {:ok, user} ->
             PlausibleWeb.Email.welcome_email(user)
-            |> Plausible.Mailer.deliver_now()
+            |> Plausible.Mailer.send_email()
 
             conn
             |> put_session(:current_user_id, user.id)
diff --git a/lib/plausible_web/controllers/billing_controller.ex b/lib/plausible_web/controllers/billing_controller.ex
index f63ed2a5..7fd51481 100644
--- a/lib/plausible_web/controllers/billing_controller.ex
+++ b/lib/plausible_web/controllers/billing_controller.ex
@@ -6,6 +6,10 @@ defmodule PlausibleWeb.BillingController do
 
   plug PlausibleWeb.RequireAccountPlug
 
+  def admin_email do
+    Application.get_env(:plausible, :admin_email)
+  end
+
   def change_plan_form(conn, _params) do
     subscription = Billing.active_subscription_for(conn.assigns[:current_user].id)
 
diff --git a/lib/plausible_web/controllers/page_controller.ex b/lib/plausible_web/controllers/page_controller.ex
index 1011e685..8d7235b0 100644
--- a/lib/plausible_web/controllers/page_controller.ex
+++ b/lib/plausible_web/controllers/page_controller.ex
@@ -70,7 +70,7 @@ defmodule PlausibleWeb.PageController do
   end
 
   def submit_contact_form(conn, %{"text" => text, "email" => email}) do
-    PlausibleWeb.Email.feedback(email, text) |> Plausible.Mailer.deliver_now
+    PlausibleWeb.Email.feedback(email, text) |> Plausible.Mailer.send_email()
     render(conn, "contact_thanks.html")
   end
 
diff --git a/lib/plausible_web/controllers/stats_controller.ex b/lib/plausible_web/controllers/stats_controller.ex
index 42c5401f..71916976 100644
--- a/lib/plausible_web/controllers/stats_controller.ex
+++ b/lib/plausible_web/controllers/stats_controller.ex
@@ -6,6 +6,10 @@ defmodule PlausibleWeb.StatsController do
 
   plug PlausibleWeb.AuthorizeStatsPlug when action in [:stats, :csv_export]
 
+  def base_domain() do
+    PlausibleWeb.Endpoint.host()
+  end
+
   def stats(conn, _params) do
     site = conn.assigns[:site]
     user = conn.assigns[:current_user]
@@ -14,7 +18,7 @@ defmodule PlausibleWeb.StatsController do
       redirect(conn, to: "/billing/upgrade")
     else
       if Stats.has_pageviews?(site) do
-        demo = site.domain == "plausible.io"
+        demo = site.domain == base_domain()
         offer_email_report = get_session(conn, site.domain <> "_offer_email_report")
 
         conn
@@ -108,4 +112,3 @@ defmodule PlausibleWeb.StatsController do
     end
   end
 end
-
diff --git a/lib/plausible_web/email.ex b/lib/plausible_web/email.ex
index e955bb3c..bc2088d1 100644
--- a/lib/plausible_web/email.ex
+++ b/lib/plausible_web/email.ex
@@ -2,10 +2,18 @@ defmodule PlausibleWeb.Email do
   use Bamboo.Phoenix, view: PlausibleWeb.EmailView
   import Bamboo.PostmarkHelper
 
+  def mailer_email_from do
+    Application.get_env(:plausible, :mailer_email)
+  end
+
+  def admin_email do
+    Application.get_env(:plausible, :admin_email)
+  end
+
   def activation_email(user, link) do
     base_email()
     |> to(user.email)
-    |> from("Uku Taht <uku@plausible.io>")
+    |> from(mailer_email_from())
     |> tag("activation-email")
     |> subject("Activate your Plausible free trial")
     |> render("activation_email.html", name: user.name, link: link)
@@ -14,7 +22,7 @@ defmodule PlausibleWeb.Email do
   def welcome_email(user) do
     base_email()
     |> to(user)
-    |> from("Uku Taht <uku@plausible.io>")
+    |> from(mailer_email_from())
     |> tag("welcome-email")
     |> subject("Welcome to Plausible")
     |> render("welcome_email.html", user: user)
@@ -23,7 +31,7 @@ defmodule PlausibleWeb.Email do
   def create_site_email(user) do
     base_email()
     |> to(user)
-    |> from("Uku Taht <uku@plausible.io>")
+    |> from(mailer_email_from())
     |> tag("create-site-email")
     |> subject("Your Plausible setup: Add your website details")
     |> render("create_site_email.html", user: user)
@@ -32,7 +40,7 @@ defmodule PlausibleWeb.Email do
   def site_setup_help(user, site) do
     base_email()
     |> to(user)
-    |> from("Uku Taht <uku@plausible.io>")
+    |> from(mailer_email_from())
     |> tag("help-email")
     |> subject("Your Plausible setup: Waiting for the first page views")
     |> render("site_setup_help_email.html", user: user, site: site)
@@ -41,7 +49,7 @@ defmodule PlausibleWeb.Email do
   def site_setup_success(user, site) do
     base_email()
     |> to(user)
-    |> from("Uku Taht <uku@plausible.io>")
+    |> from(mailer_email_from())
     |> tag("setup-success-email")
     |> subject("Plausible is now tracking your website stats")
     |> render("site_setup_success_email.html", user: user, site: site)
@@ -50,7 +58,7 @@ defmodule PlausibleWeb.Email do
   def check_stats_email(user) do
     base_email()
     |> to(user)
-    |> from("Uku Taht <uku@plausible.io>")
+    |> from(mailer_email_from())
     |> tag("check-stats-email")
     |> subject("Check your Plausible website stats")
     |> render("check_stats_email.html", user: user)
@@ -59,7 +67,7 @@ defmodule PlausibleWeb.Email do
   def password_reset_email(email, reset_link) do
     base_email()
     |> to(email)
-    |> from("Uku Taht <uku@plausible.io>")
+    |> from(mailer_email_from())
     |> tag("password-reset-email")
     |> subject("Plausible password reset")
     |> render("password_reset_email.html", reset_link: reset_link)
@@ -68,7 +76,7 @@ defmodule PlausibleWeb.Email do
   def trial_one_week_reminder(user) do
     base_email()
     |> to(user)
-    |> from("Uku Taht <uku@plausible.io>")
+    |> from(mailer_email_from())
     |> tag("trial-one-week-reminder")
     |> subject("Your Plausible trial expires next week")
     |> render("trial_one_week_reminder.html", user: user)
@@ -77,7 +85,7 @@ defmodule PlausibleWeb.Email do
   def trial_upgrade_email(user, day, pageviews) do
     base_email()
     |> to(user)
-    |> from("Uku Taht <uku@plausible.io>")
+    |> from(mailer_email_from())
     |> tag("trial-upgrade-email")
     |> subject("Your Plausible trial ends #{day}")
     |> render("trial_upgrade_email.html", user: user, day: day, pageviews: pageviews)
@@ -86,7 +94,7 @@ defmodule PlausibleWeb.Email do
   def trial_over_email(user) do
     base_email()
     |> to(user)
-    |> from("Uku Taht <uku@plausible.io>")
+    |> from(mailer_email_from())
     |> tag("trial-over-email")
     |> subject("Your Plausible trial has ended")
     |> render("trial_over_email.html", user: user)
diff --git a/lib/plausible_web/endpoint.ex b/lib/plausible_web/endpoint.ex
index 9eaf7d93..29c07e81 100644
--- a/lib/plausible_web/endpoint.ex
+++ b/lib/plausible_web/endpoint.ex
@@ -44,11 +44,11 @@ defmodule PlausibleWeb.Endpoint do
 
   def clean_url() do
     url = PlausibleWeb.Endpoint.url
-
-    if Mix.env() == :prod do
-      URI.parse(url) |> Map.put(:port, nil) |> URI.to_string()
-    else
-      url
+    case Application.get_env(:plausible, :environment) do
+      # do not truncate the port in case of dev or test environment
+      env when env in ["dev", "test"] -> url
+      # in most deployments, there's a layer above the above
+      _ -> URI.parse(url) |> Map.put(:port, nil) |> URI.to_string()
     end
   end
 end
diff --git a/lib/plausible_web/router.ex b/lib/plausible_web/router.ex
index efa3edbe..df72da24 100644
--- a/lib/plausible_web/router.ex
+++ b/lib/plausible_web/router.ex
@@ -29,7 +29,7 @@ defmodule PlausibleWeb.Router do
     plug :fetch_session
   end
 
-  if Mix.env == :dev do
+  if Application.get_env(:plausible, :environment) == "dev" do
     forward "/sent-emails", Bamboo.SentEmailViewerPlug
   end
 
diff --git a/lib/plausible_web/templates/email/check_stats_email.html.eex b/lib/plausible_web/templates/email/check_stats_email.html.eex
index 1b60ed9a..047383f4 100644
--- a/lib/plausible_web/templates/email/check_stats_email.html.eex
+++ b/lib/plausible_web/templates/email/check_stats_email.html.eex
@@ -2,7 +2,7 @@ Hey <%= user_salutation(@user) %>,
 <br /><br />
 Plausible is tracking your website stats without compromising the user experience and the privacy of your visitors.
 <br /><br />
-<%= link("View your Plausible dashboard now", to: "https://plausible.io/") %> for the most valuable traffic insights at a glance.
+<%= link("View your Plausible dashboard now", to: "#{plausible_url()}") %> for the most valuable traffic insights at a glance.
 <br /><br />
 Do reply back to this email if you have any questions or need some guidance.
 <br /></br>
@@ -11,4 +11,4 @@ Uku Taht
 <br /><br />
 --
 <br /><br />
-https://plausible.io
+<%= plausible_url() %>
diff --git a/lib/plausible_web/templates/email/create_site_email.html.eex b/lib/plausible_web/templates/email/create_site_email.html.eex
index 8b3dc57a..11c08e6d 100644
--- a/lib/plausible_web/templates/email/create_site_email.html.eex
+++ b/lib/plausible_web/templates/email/create_site_email.html.eex
@@ -2,7 +2,7 @@ Hey <%= user_salutation(@user) %>,
 <br /><br />
 You've activated your free 30-day trial of Plausible, a simple and privacy-friendly website analytics tool.
 <br /><br />
-<%= link("Click here", to: "https://plausible.io/sites/new") %> to add your website URL, your timezone and install our one-line JavaScript snippet to start collecting visitor statistics.
+<%= link("Click here", to: "#{plausible_url()}/sites/new") %> to add your website URL, your timezone and install our one-line JavaScript snippet to start collecting visitor statistics.
 <br /><br />
 Do reply back to this email if you have any questions or need some guidance.
 <br /></br>
@@ -11,4 +11,4 @@ Uku Taht
 <br /><br />
 --
 <br /><br />
-https://plausible.io
+<%= plausible_url() %>
diff --git a/lib/plausible_web/templates/email/site_setup_help_email.html.eex b/lib/plausible_web/templates/email/site_setup_help_email.html.eex
index 3a405cbd..ceba95fc 100644
--- a/lib/plausible_web/templates/email/site_setup_help_email.html.eex
+++ b/lib/plausible_web/templates/email/site_setup_help_email.html.eex
@@ -4,7 +4,7 @@ Hey <%= user_salutation(@user) %>,
   You signed up for a free 30-day trial of Plausible, a simple and privacy-friendly website analytics tool.
   <br /><br />
 <% end %>
-To finish your setup for <%= @site.domain %>, you need to install <%= link("this lightweight line of JavaScript code", to: "https://plausible.io/#{URI.encode_www_form(@site.domain)}/snippet") %> into your site to start collecting visitor statistics.
+To finish your setup for <%= @site.domain %>, you need to install <%= link("this lightweight line of JavaScript code", to: "#{plausible_url()}/#{URI.encode_www_form(@site.domain)}/snippet") %> into your site to start collecting visitor statistics.
 <br /><br />
 This Plausible script is 14 times smaller than Google Analytics script so you’ll have a fast loading site while getting all the important traffic insights on one single page.
 <br /><br />
@@ -15,4 +15,4 @@ Uku Taht
 <br /><br />
 --
 <br /><br />
-https://plausible.io
+<%= plausible_url() %>
diff --git a/lib/plausible_web/templates/email/site_setup_success_email.html.eex b/lib/plausible_web/templates/email/site_setup_success_email.html.eex
index f7ef990b..98fd1c34 100644
--- a/lib/plausible_web/templates/email/site_setup_success_email.html.eex
+++ b/lib/plausible_web/templates/email/site_setup_success_email.html.eex
@@ -2,13 +2,13 @@ Hey <%= user_salutation(@user) %>,
 <br /><br />
 Congrats! The Plausible script has been installed correctly on <%= link(@site.domain, to: "https://#{@site.domain}") %>. Your website traffic is now being tracked without compromising the user experience and the privacy of your visitors.
 <br /><br />
-<%= link("Check your stats", to: "https://plausible.io/#{URI.encode_www_form(@site.domain)}") %>
+<%= link("Check your stats", to: "#{plausible_url()}/#{URI.encode_www_form(@site.domain)}") %>
 <br /><br />
 <%= if Plausible.Billing.on_trial?(@user) do %>
   You're on a 30-day unlimited-use free trial with no obligations so do take your time to explore your simple and privacy-friendly website analytics dashboard.
   <br /><br />
 <% end %>
-PS: Plausible is fully open-source and our public roadmap is defined by the community. <%= link("Leave your feedback", to: "https://plausible.io/feedback") %> and have your say on metrics and features we should be adding next.
+PS: Plausible is fully open-source and our public roadmap is defined by the community. <%= link("Leave your feedback", to: "#{plausible_url()}/feedback") %> and have your say on metrics and features we should be adding next.
 <br /><br />
 Do reply back to this email if you have any questions.
 <br /></br>
@@ -17,4 +17,5 @@ Uku Taht
 <br /><br />
 --
 <br /><br />
-https://plausible.io
+<%= plausible_url() %>
+
diff --git a/lib/plausible_web/templates/email/trial_one_week_reminder.html.eex b/lib/plausible_web/templates/email/trial_one_week_reminder.html.eex
index c2985cf5..16481807 100644
--- a/lib/plausible_web/templates/email/trial_one_week_reminder.html.eex
+++ b/lib/plausible_web/templates/email/trial_one_week_reminder.html.eex
@@ -4,10 +4,10 @@ Time flies! Your 30-day free trial of Plausible will end next week.
 <br /><br />
 Over the last three weeks, I hope you got to experience the potential benefits of having website stats in a simple dashboard while respecting the privacy of your visitors, not annoying them with the cookie and privacy notices and still having a fast loading site.
 <br /><br />
-In order to continue receiving valuable website traffic insights at a glance, you’ll need to <%= link("Upgrade your account", to: "https://plausible.io/billing/upgrade") %>.
+In order to continue receiving valuable website traffic insights at a glance, you’ll need to <%= link("Upgrade your account", to: "#{plausible_url()}/billing/upgrade") %>.
 <br /><br />
 If you have any questions or feedback for me, feel free to reply to this email.
 <br /><br />
 Thanks,<br />
 Uku Taht<br />
-https://plausible.io
+<%= plausible_url() %>
diff --git a/lib/plausible_web/templates/email/trial_over_email.html.eex b/lib/plausible_web/templates/email/trial_over_email.html.eex
index d5c97546..05a6352b 100644
--- a/lib/plausible_web/templates/email/trial_over_email.html.eex
+++ b/lib/plausible_web/templates/email/trial_over_email.html.eex
@@ -2,7 +2,7 @@ Hey <%= user_salutation(@user) %>,
 <br /><br />
 Your free Plausible trial has now expired. Upgrade your account to continue receiving valuable website traffic insights at a glance while respecting the privacy of your visitors and still having a fast loading site. <br /><br />
 
-<%= link("Upgrade now", to: "https://plausible.io/billing/upgrade") %>
+<%= link("Upgrade now", to: "#{plausible_url()}/billing/upgrade") %>
 <br /><br />
 
 We will keep recording stats for another month to give you time to upgrade.
@@ -14,4 +14,4 @@ Founder, Plausible Insights
 <br /><br />
 --
 <br /><br />
-https://plausible.io
+<%= plausible_url() %>
\ No newline at end of file
diff --git a/lib/plausible_web/templates/email/trial_upgrade_email.html.eex b/lib/plausible_web/templates/email/trial_upgrade_email.html.eex
index 5cf424f5..dc99f55d 100644
--- a/lib/plausible_web/templates/email/trial_upgrade_email.html.eex
+++ b/lib/plausible_web/templates/email/trial_upgrade_email.html.eex
@@ -6,11 +6,11 @@ In the last month, your account has used <%= PlausibleWeb.AuthView.delimit_integ
 Based on that we recommend you select the <%= suggested_plan_name(@pageviews) %> plan which runs at <%= suggested_plan_cost(@pageviews) %>.
 You can also go with yearly billing to get 33% off on your plan.
 <br /><br />
-<%= link("Upgrade now", to: "https://plausible.io/billing/upgrade") %>
+<%= link("Upgrade now", to: "#{plausible_url()}/billing/upgrade") %>
 <br /><br />
 Have a question, feedback or need some guidance? Just reply to this email to get in touch!
 <br /><br />
 <br /><br />
 Thanks,<br />
 Uku Taht<br />
-https://plausible.io
+<%= plausible_url() %>
diff --git a/lib/plausible_web/templates/email/welcome_email.html.eex b/lib/plausible_web/templates/email/welcome_email.html.eex
index 875eee08..88de79df 100644
--- a/lib/plausible_web/templates/email/welcome_email.html.eex
+++ b/lib/plausible_web/templates/email/welcome_email.html.eex
@@ -3,7 +3,7 @@ Hey <%= user_salutation(@user) %>,
 I'm building Plausible to provide a simple and ethical approach to tracking website visitors.
 I'm super excited to have you on board!
 <br /><br />
-To start collecting stats, you need to <%= link("add a site on Plausible", to: "https://plausible.io/sites/new") %>.
+To start collecting stats, you need to <%= link("add a site on Plausible", to: "#{plausible_url()}/sites/new") %>.
 <br /><br />
 Have a question, feedback or need some guidance? Do reply back to this email.
 <br /><br />
@@ -12,4 +12,4 @@ Uku Taht
 <br /><br />
 --
 <br /><br />
-https://plausible.io
+<%= plausible_url() %>
diff --git a/lib/plausible_web/templates/layout/_footer.html.eex b/lib/plausible_web/templates/layout/_footer.html.eex
index 33138428..1c14f79c 100644
--- a/lib/plausible_web/templates/layout/_footer.html.eex
+++ b/lib/plausible_web/templates/layout/_footer.html.eex
@@ -6,8 +6,8 @@
     </div>
     <ul class="text-center md:text-left my-4 md:m-0">
       <li>Read our <a href="/blog/" class="light-text font-medium mr-4 underline">Blog</a></li>
-      <li>Study the <a href="https://docs.plausible.io" target="_blank" class="light-text font-medium mr-4 underline">Documentation</a></li>
-      <li>Check out the <a href="/plausible.io" class="light-text font-medium underline">Live Demo</a></li>
+      <li>Study the <%= link("Documentation", to: "https://docs.#{base_domain()}",target: "_blank",class: "light-text font-medium mr-4 underline") %></li>
+      <li>Check out the <%= link("Live Demo", to: "/plausible.io",class: "light-text font-medium underline") %></li>
     </ul>
     <ul class="text-center md:text-left my-4 md:m-0">
       <li>Give us <a href="/feedback" target="_blank" class="light-text font-medium mr-4 underline">Feedback</a></li>
diff --git a/lib/plausible_web/templates/layout/_tracking.html.eex b/lib/plausible_web/templates/layout/_tracking.html.eex
index 03b58844..ddd28660 100644
--- a/lib/plausible_web/templates/layout/_tracking.html.eex
+++ b/lib/plausible_web/templates/layout/_tracking.html.eex
@@ -1,4 +1,4 @@
 <%= if !@conn.assigns[:skip_plausible_tracking] do %>
-  <script async defer src="https://plausible.io/js/plausible.js"></script>
+  <script async defer src="<%="#{plausible_url()}/js/plausible.js"%>"></script>
   <script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</script>
-<% end %>
+<% end %>
\ No newline at end of file
diff --git a/lib/plausible_web/templates/page/privacy.html.eex b/lib/plausible_web/templates/page/privacy.html.eex
index cfc374f2..31db64ae 100644
--- a/lib/plausible_web/templates/page/privacy.html.eex
+++ b/lib/plausible_web/templates/page/privacy.html.eex
@@ -1,6 +1,6 @@
 <div class="max-w-md mx-auto py-12 just-text">
 <h1 class="text-xl font-black mb-4">Privacy Policy</h1>
-<p>Your privacy is important to us. It is Plausible Insights' policy to respect your privacy regarding any information we may collect from you across our website, <a href="http://plausible.io">http://plausible.io</a>, and other sites we own and operate.</p>
+<p>Your privacy is important to us. It is Plausible Analytics' policy to respect your privacy regarding any information we may collect from you across our website, <%= link(plausible_url(), to: plausible_url()) %>, and other sites we own and operate.</p>
 <p>We only ask for personal information when we truly need it to provide a service to you. We collect it by fair and lawful means, with your knowledge and consent. We also let you know why we’re collecting it and how it will be used.</p>
 <p>We only retain collected information for as long as necessary to provide you with your requested service. What data we store, we’ll protect within commercially acceptable means to prevent loss and theft, as well as unauthorised access, disclosure, copying, use or modification.</p>
 <p>We don’t share any personally identifying information publicly or with third-parties, except when required to by law.</p>
diff --git a/lib/plausible_web/templates/page/terms.html.eex b/lib/plausible_web/templates/page/terms.html.eex
index 611fa5c8..448957e2 100644
--- a/lib/plausible_web/templates/page/terms.html.eex
+++ b/lib/plausible_web/templates/page/terms.html.eex
@@ -1,7 +1,7 @@
 <div class="max-w-2xl mx-auto leading-normal py-12 just-text">
   <h1 class="text-2xl font-black mb-4">Terms of Service</h1>
   <h3 class="text-lg font-black">1. Terms</h3>
-  <p>By accessing the website at <a href="http://plausible.io">http://plausible.io</a>, you are agreeing to be bound by these terms of service, all applicable laws and regulations, and agree that you are responsible for compliance with any applicable local laws. If you do not agree with any of these terms, you are prohibited from using or accessing this site. The materials contained in this website are protected by applicable copyright and trademark law.</p>
+  <p>By accessing the website at <%= link(plausible_url(), to: plausible_url()) %>, you are agreeing to be bound by these terms of service, all applicable laws and regulations, and agree that you are responsible for compliance with any applicable local laws. If you do not agree with any of these terms, you are prohibited from using or accessing this site. The materials contained in this website are protected by applicable copyright and trademark law.</p>
   <h3 class="text-lg font-black">2. Use License</h3>
   <ol type="a" class="list-decimal pl-4">
     <li>Permission is granted to temporarily download one copy of the materials (information or software) on Plausible Insights' website for personal, non-commercial transitory viewing only. This is the grant of a license, not a transfer of title, and under this license you may not:
diff --git a/lib/plausible_web/templates/site/custom_domain_dns_setup.html.eex b/lib/plausible_web/templates/site/custom_domain_dns_setup.html.eex
index dc30cba8..4fbfc1cc 100644
--- a/lib/plausible_web/templates/site/custom_domain_dns_setup.html.eex
+++ b/lib/plausible_web/templates/site/custom_domain_dns_setup.html.eex
@@ -3,8 +3,8 @@
   <ol class="list-disc pl-4 my-4">
     <li>Go to your DNS provider’s website</li>
     <li class="mt-4">Create a new CNAME record for <code><%= @site.custom_domain.domain %></code></li>
-    <li class="mt-4">Point the record to <code>custom.plausible.io.</code> (including the dot)</li>
+    <li class="mt-4">Point the record to <code>custom.<%= plausible_url() %>.</code> (including the dot)</li>
   </ol>
 
   <%= link("Done ->", to: "/sites/#{URI.encode_www_form(@site.domain)}/custom-domains/snippet", class: "button w-full mt-6") %>
-</div>
+</div>
\ No newline at end of file
diff --git a/lib/plausible_web/templates/site/settings.html.eex b/lib/plausible_web/templates/site/settings.html.eex
index 4e6a4d5b..2fb4acf4 100644
--- a/lib/plausible_web/templates/site/settings.html.eex
+++ b/lib/plausible_web/templates/site/settings.html.eex
@@ -35,7 +35,7 @@
   <%= if @site.public do %>
       Stats for <%= @site.domain %> are currently <b>public</b>. Anyone with the following link can view the stats:
       <div class="relative text-sm mt-4">
-        <input type="text" id="public-link" value="https://plausible.io/<%= URI.encode_www_form(@site.domain)  %>" class="transition bg-gray-100 appearance-none border border-transparent rounded w-full p-2 pr-16 text-gray-700 appearance-none focus:outline-none" />
+        <input type="text" id="public-link" value="<%= base_domain() <> "/" <> URI.encode_www_form(@site.domain)%>" class="transition bg-gray-100 appearance-none border border-transparent rounded w-full p-2 pr-16 text-gray-700 appearance-none focus:outline-none" />
         <a onclick="var input = document.getElementById('public-link'); input.focus(); input.select(); document.execCommand('copy');" href="javascript:void(0)" class="absolute right-0 text-indigo-700 font-bold p-2">
           <svg class="feather-sm"><use xlink:href="#feather-copy" /></svg>
         </a>
@@ -112,7 +112,7 @@
         </p>
       <% else %>
         <p class="text-gray-700 mt-6">
-        Select the Google Search Console property you would like to pull keyword data from. If you don't see your domain, <%= link("set it up and verify", to: "https://docs.plausible.io/google-search-console-integration#1-add-your-site-on-the-search-console", class: "text-indigo-500") %> on Search Console first.
+        Select the Google Search Console property you would like to pull keyword data from. If you don't see your domain, <%= link("set it up and verify", to: "https://docs.#{base_domain()}/google-search-console-integration#1-add-your-site-on-the-search-console", class: "text-indigo-500") %> on Search Console first.
         </p>
       <% end %>
 
@@ -132,7 +132,7 @@
       <%= button("Continue with Google", to: Plausible.Google.Api.authorize_url(@site.id), class: "button mt-4") %>
 
       <div class="text-gray-700 mt-8">
-        NB: You also need to set up your site on <%= link("Google Search Console", to: "https://search.google.com/search-console/about") %> for the integration to work. <%= link("Read the docs", to: "https://docs.plausible.io/google-search-console-integration", class: "text-indigo-500") %>
+        NB: You also need to set up your site on <%= link("Google Search Console", to: "https://search.google.com/search-console/about") %> for the integration to work. <%= link("Read the docs", to: "https://docs.#{base_domain()}/google-search-console-integration", class: "text-indigo-500") %>
       </div>
     <% end %>
 </div>
diff --git a/lib/plausible_web/views/auth_view.ex b/lib/plausible_web/views/auth_view.ex
index 2c1d95dd..de7acdad 100644
--- a/lib/plausible_web/views/auth_view.ex
+++ b/lib/plausible_web/views/auth_view.ex
@@ -11,6 +11,18 @@ defmodule PlausibleWeb.AuthView do
     "free_10k" => "10k / free"
   }
 
+  def admin_email do
+    Application.get_env(:plausible, :admin_email)
+  end
+
+  def base_domain do
+    PlausibleWeb.Endpoint.host()
+  end
+
+  def plausible_url do
+    PlausibleWeb.Endpoint.clean_url()
+  end
+
   def subscription_name(subscription) do
     @subscription_names[subscription.paddle_plan_id]
   end
diff --git a/lib/plausible_web/views/billing_view.ex b/lib/plausible_web/views/billing_view.ex
index 353f9b9a..9346899f 100644
--- a/lib/plausible_web/views/billing_view.ex
+++ b/lib/plausible_web/views/billing_view.ex
@@ -1,6 +1,18 @@
 defmodule PlausibleWeb.BillingView do
   use PlausibleWeb, :view
 
+  def admin_email do
+    Application.get_env(:plausible, :admin_email)
+  end
+
+  def base_domain do
+    PlausibleWeb.Endpoint.host()
+  end
+
+  def plausible_url do
+    PlausibleWeb.Endpoint.clean_url()
+  end
+
   def present_date(date) do
     Date.from_iso8601!(date)
     |> Timex.format!("{D} {Mshort} {YYYY}")
diff --git a/lib/plausible_web/views/email_view.ex b/lib/plausible_web/views/email_view.ex
index cd4dbea6..c4074c65 100644
--- a/lib/plausible_web/views/email_view.ex
+++ b/lib/plausible_web/views/email_view.ex
@@ -1,6 +1,18 @@
 defmodule PlausibleWeb.EmailView do
   use PlausibleWeb, :view
 
+  def admin_email do
+    Application.get_env(:plausible, :admin_email)
+  end
+
+  def plausible_url do
+    PlausibleWeb.Endpoint.clean_url()
+  end
+
+  def base_domain() do
+    PlausibleWeb.Endpoint.host()
+  end
+
   def user_salutation(user) do
     if user.name do
       String.split(user.name) |> List.first
diff --git a/lib/plausible_web/views/layout_view.ex b/lib/plausible_web/views/layout_view.ex
index d6b88480..4ec2b068 100644
--- a/lib/plausible_web/views/layout_view.ex
+++ b/lib/plausible_web/views/layout_view.ex
@@ -1,6 +1,18 @@
 defmodule PlausibleWeb.LayoutView do
   use PlausibleWeb, :view
 
+  def admin_email do
+    Application.get_env(:plausible, :admin_email)
+  end
+
+  def base_domain do
+    PlausibleWeb.Endpoint.host()
+  end
+
+  def plausible_url do
+    PlausibleWeb.Endpoint.clean_url()
+  end
+
   def home_dest(conn) do
     if conn.assigns[:current_user] do
       "/sites"
diff --git a/lib/plausible_web/views/page_view.ex b/lib/plausible_web/views/page_view.ex
index c4ace790..12119a80 100644
--- a/lib/plausible_web/views/page_view.ex
+++ b/lib/plausible_web/views/page_view.ex
@@ -1,3 +1,15 @@
 defmodule PlausibleWeb.PageView do
   use PlausibleWeb, :view
+
+  def admin_email do
+    Application.get_env(:plausible, :admin_email)
+  end
+
+  def base_domain do
+    PlausibleWeb.Endpoint.host()
+  end
+
+  def plausible_url do
+    PlausibleWeb.Endpoint.clean_url()
+  end
 end
diff --git a/lib/plausible_web/views/site_view.ex b/lib/plausible_web/views/site_view.ex
index f5e68ace..c000da93 100644
--- a/lib/plausible_web/views/site_view.ex
+++ b/lib/plausible_web/views/site_view.ex
@@ -1,7 +1,19 @@
 defmodule PlausibleWeb.SiteView do
   use PlausibleWeb, :view
 
-  def goal_name(%Plausible.Goal{page_path: page_path}) when is_binary(page_path)  do
+  def admin_email do
+    Application.get_env(:plausible, :admin_email)
+  end
+
+  def plausible_url do
+    PlausibleWeb.Endpoint.clean_url()
+  end
+
+  def base_domain() do
+    PlausibleWeb.Endpoint.host()
+  end
+
+  def goal_name(%Plausible.Goal{page_path: page_path}) when is_binary(page_path) do
     "Visit " <> page_path
   end
 
@@ -14,11 +26,12 @@ defmodule PlausibleWeb.SiteView do
   end
 
   def snippet(site) do
-    tracker = if site.custom_domain do
-      "https://" <> site.custom_domain.domain <> "/js/index.js"
-    else
-      "https://plausible.io/js/plausible.js"
-    end
+    tracker =
+      if site.custom_domain do
+        "https://" <> site.custom_domain.domain <> "/js/index.js"
+      else
+        "#{plausible_url()}/js/plausible.js"
+      end
 
     """
     <script async defer data-domain="#{site.domain}" src="#{tracker}"></script>
diff --git a/lib/plausible_web/views/stats_view.ex b/lib/plausible_web/views/stats_view.ex
index 83110edf..96f7e713 100644
--- a/lib/plausible_web/views/stats_view.ex
+++ b/lib/plausible_web/views/stats_view.ex
@@ -1,6 +1,18 @@
 defmodule PlausibleWeb.StatsView do
   use PlausibleWeb, :view
 
+  def admin_email do
+    Application.get_env(:plausible, :admin_email)
+  end
+
+  def base_domain do
+    PlausibleWeb.Endpoint.host()
+  end
+
+  def plausible_url do
+    PlausibleWeb.Endpoint.clean_url()
+  end
+
   def large_number_format(n) do
     cond do
       n >= 1_000 && n < 1_000_000 ->
diff --git a/mix.exs b/mix.exs
index 692d2336..3ebce892 100644
--- a/mix.exs
+++ b/mix.exs
@@ -4,13 +4,23 @@ defmodule Plausible.MixProject do
   def project do
     [
       app: :plausible,
-      version: "0.1.0",
-      elixir: "~> 1.5",
-      elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix, :gettext] ++ Mix.compilers(),
+      version: System.get_env("APP_VERSION", "0.0.1"),
+      elixir: "~> 1.10",
+      elixirc_paths: elixirc_paths(Mix.env()),
+      compilers: [:phoenix, :gettext] ++ Mix.compilers(),
       start_permanent: Mix.env() == :prod,
       aliases: aliases(),
       deps: deps(),
-      test_coverage: [tool: ExCoveralls]
+      test_coverage: [
+        tool: ExCoveralls
+      ],
+      releases: [
+        plausible: [
+          include_executables_for: [:unix],
+          applications: [plausible: :permanent],
+          steps: [:assemble, :tar]
+        ]
+      ]
     ]
   end
 
@@ -20,7 +30,16 @@ defmodule Plausible.MixProject do
   def application do
     [
       mod: {Plausible.Application, []},
-      extra_applications: [:logger, :sentry, :runtime_tools, :timex, :ua_inspector, :ref_inspector, :bamboo]
+      extra_applications: [
+        :logger,
+        :sentry,
+        :runtime_tools,
+        :timex,
+        :ua_inspector,
+        :ref_inspector,
+        :bamboo,
+        :bamboo_smtp
+      ]
     ]
   end
 
@@ -33,7 +52,8 @@ defmodule Plausible.MixProject do
   # Type `mix help deps` for examples and options.
   defp deps do
     [
-      {:browser, "~> 0.4.3"}, # remove
+      # remove
+      {:browser, "~> 0.4.3"},
       {:bcrypt_elixir, "~> 2.0"},
       {:cors_plug, "~> 1.5"},
       {:ecto_sql, "~> 3.0"},
@@ -47,17 +67,20 @@ defmodule Plausible.MixProject do
       {:phoenix_pubsub, "~> 1.1"},
       {:plug_cowboy, "~> 2.0"},
       {:postgrex, ">= 0.0.0"},
-      {:poison, "~> 3.1"}, # Used in paddle_api, can remove
+      #  Used in paddle_api, can remove
+      {:poison, "~> 3.1"},
       {:ref_inspector, "~> 1.3"},
       {:timex, "~> 3.6"},
       {:ua_inspector, "~> 0.18"},
       {:bamboo, "~> 1.3"},
       {:bamboo_postmark, "~> 0.5"},
+      {:bamboo_smtp, "~> 2.1.0"},
       {:sentry, "~> 7.0"},
       {:httpoison, "~> 1.4"},
       {:ex_machina, "~> 2.3", only: :test},
       {:excoveralls, "~> 0.10", only: :test},
       {:double, "~> 0.7.0", only: :test},
+      {:junit_formatter, "~> 3.1", only: [:test]},
       {:joken, "~> 2.0"},
       {:php_serializer, "~> 0.9.0"},
       {:csv, "~> 2.3"},
diff --git a/mix.lock b/mix.lock
index 6cb18970..8b0bb239 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,11 +1,10 @@
 %{
-  "bamboo": {:hex, :bamboo, "1.4.0", "7b9201c49a843e4802061cf45692405b2c00efcf1cebf8b7b64f015ead072392", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b9cad03bf38c7f37b6308876039355665b6ce09fefb46dc529cef4def912cffa"},
+  "bamboo": {:hex, :bamboo, "1.5.0", "1926107d58adba6620450f254dfe8a3686637a291851fba125686fa8574842af", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d5f3d04d154e80176fd685e2531e73870d8700679f14d25a567e448abce6298d"},
   "bamboo_postmark": {:hex, :bamboo_postmark, "0.6.0", "429ee3153497e2f1081f8741242450be13cdca52e2c56166e8eda5ebfcb23c0a", [:mix], [{:bamboo, ">= 1.2.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:hackney, ">= 1.6.5", [hex: :hackney, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "badb3c7677440f641d920e0900ff0cd13c01d517e76562aa5c00e560f46f36ba"},
-  "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm", "fab09b20e3f5db886725544cbcf875b8e73ec93363954eb8a1a9ed834aa8c1f9"},
+  "bamboo_smtp": {:hex, :bamboo_smtp, "2.1.0", "4be58f3c51d9f7875dc169ae58a1d2f08e5b718bf3895f70d130548c0598f422", [:mix], [{:bamboo, "~> 1.2", [hex: :bamboo, repo: "hexpm", optional: false]}, {:gen_smtp, "~> 0.15.0", [hex: :gen_smtp, repo: "hexpm", optional: false]}], "hexpm", "0aad00ef93d0e0c83a0e1ca6998fea070c8a720a990fbda13ce834136215ee49"},
   "bcrypt_elixir": {:hex, :bcrypt_elixir, "2.2.0", "3df902b81ce7fa8867a2ae30d20a1da6877a2c056bfb116fd0bc8a5f0190cea4", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "762be3fcb779f08207531bc6612cca480a338e4b4357abb49f5ce00240a77d1e"},
   "browser": {:hex, :browser, "0.4.4", "bd6436961a6b2299c6cb38d0e49761c1161d869cd0db46369cef2bf6b77c3665", [:mix], [{:plug, "~> 1.2", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d476ca309d4a4b19742b870380390aabbcb323c1f6f8745e2da2dfd079b4f8d7"},
   "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"},
-  "clickhouse_ecto": {:git, "git@github.com:appodeal/clickhouse_ecto.git", "c4fa1c3d2b73e4be698e205ad6e9ace22ac23f7d", []},
   "clickhousex": {:git, "https://github.com/atlas-forks/clickhousex.git", "e010c4eaa6cb6b659e44790a3bea2ec7703ceb31", []},
   "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
   "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"},
@@ -17,50 +16,50 @@
   "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
   "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
   "double": {:hex, :double, "0.7.0", "a7ee4c3488a0acc6d2ad9b69b6c7d3ddf3da2b54488d0f7c2d6ceb3a995887ca", [:mix], [], "hexpm", "f0c387a2266b4452da7bab03598feec11aef8b2acab061ea947dae81bb257329"},
-  "ecto": {:hex, :ecto, "3.4.2", "6890af71025769bd27ef62b1ed1925cfe23f7f0460bcb3041da4b705215ff23e", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3959b8a83e086202a4bd86b4b5e6e71f9f1840813de14a57d502d3fc2ef7132"},
-  "ecto_sql": {:hex, :ecto_sql, "3.4.2", "3d842665a81ba2137b62aa70151afe81dae44824cd09b2076a255937ab4e2dc9", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f2b064102467e1525314a464b6fea0707ff28ee132a15006727ccf51b73492ff"},
+  "ecto": {:hex, :ecto, "3.4.4", "a2c881e80dc756d648197ae0d936216c0308370332c5e77a2325a10293eef845", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4bd3ad62abc3b21fb629f0f7a3dab23a192fca837d257dd08449fba7373561"},
+  "ecto_sql": {:hex, :ecto_sql, "3.4.4", "d28bac2d420f708993baed522054870086fd45016a9d09bb2cd521b9c48d32ea", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "edb49af715dd72f213b66adfd0f668a43c17ed510b5d9ac7528569b23af57fe8"},
   "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
   "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"},
   "ex_machina": {:hex, :ex_machina, "2.4.0", "09a34c5d371bfb5f78399029194a8ff67aff340ebe8ba19040181af35315eabb", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "a20bc9ddc721b33ea913b93666c5d0bdca5cbad7a67540784ae277228832d72c"},
   "excoveralls": {:hex, :excoveralls, "0.12.3", "2142be7cb978a3ae78385487edda6d1aff0e482ffc6123877bb7270a8ffbcfe0", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "568a3e616c264283f5dea5b020783ae40eef3f7ee2163f7a67cbd7b35bcadada"},
-  "extwitter": {:hex, :extwitter, "0.11.0", "9472e19f1711bc60bc7efa594353164532475d7c47ea9f1bb66d4faa889b079e", [:mix], [{:oauther, "~> 1.1", [hex: :oauther, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
   "file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"},
-  "gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm", "3c75b5ea8288e2ee7ea503ff9e30dfe4d07ad3c054576a6e60040e79a801e14d"},
+  "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
+  "gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"},
   "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"},
   "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"},
   "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
   "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"},
   "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"},
   "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
+  "junit_formatter": {:hex, :junit_formatter, "3.1.0", "3f69c61c5413750f9c45e367d77aabbeac9b395acf478d8e70b4ee9d1989c709", [:mix], [], "hexpm", "da52401a93f711fc4f77ffabdda68f9a16fcad5d96f5fce4ae606ab1d73b72f4"},
   "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
   "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
   "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
-  "myxql": {:hex, :myxql, "0.4.0", "d95582db9e4b4707eb3a6a7002b8869a5240247931775f82d811ad450ca06503", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.3", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "27a7ebaf7822cf7c89ea796371e70b942c8ec5e2c4eedcb8cd8e043f014014ad"},
   "nanoid": {:hex, :nanoid, "2.0.2", "f3f7b4bf103ab6667f22beb00b6315825ee3f30100dd2c93d534e5c02164e857", [:mix], [], "hexpm", "3095cb1fac7bbc78843a8ccd99f1af375d0da1d3ebaa8552e846b73438c0c44f"},
   "oauther": {:hex, :oauther, "1.1.1", "7d8b16167bb587ecbcddd3f8792beb9ec3e7b65c1f8ebd86b8dd25318d535752", [:mix], [], "hexpm", "9374f4302045321874cccdc57eb975893643bd69c3b22bf1312dab5f06e5788e"},
   "parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], [], "hexpm", "639b2e8749e11b87b9eb42f2ad325d161c170b39b288ac8d04c4f31f8f0823eb"},
   "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
-  "phoenix": {:hex, :phoenix, "1.4.16", "2cbbe0c81e6601567c44cc380c33aa42a1372ac1426e3de3d93ac448a7ec4308", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "856cc1a032fa53822737413cf51aa60e750525d7ece7d1c0576d90d7c0f05c24"},
+  "phoenix": {:hex, :phoenix, "1.4.17", "1b1bd4cff7cfc87c94deaa7d60dd8c22e04368ab95499483c50640ef3bd838d8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a8e5d7a3d76d452bb5fb86e8b7bd115f737e4f8efe202a463d4aeb4a5809611"},
   "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"},
-  "phoenix_html": {:hex, :phoenix_html, "2.14.1", "7dabafadedb552db142aacbd1f11de1c0bbaa247f90c449ca549d5e30bbc66b4", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "536d5200ad37fecfe55b3241d90b7a8c3a2ca60cd012fc065f776324fa9ab0a9"},
-  "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.1", "274a4b07c4adbdd7785d45a8b0bb57634d0b4f45b18d2c508b26c0344bd59b8f", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "41b4103a2fa282cfd747d377233baf213c648fdcc7928f432937676532490eee"},
+  "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"},
+  "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.2", "38d94c30df5e2ef11000697a4fbe2b38d0fbf79239d492ff1be87bbc33bc3a84", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "a3dec3d28ddb5476c96a7c8a38ea8437923408bc88da43e5c45d97037b396280"},
   "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"},
   "php_serializer": {:hex, :php_serializer, "0.9.2", "59c5fd6bd3096671fd89358fb8229341ac7423b50ad8d45a15213b02ea2edab2", [:mix], [], "hexpm", "34eb835a460944f7fc216773b363c02e7dcf8ac0390c9e9ccdbd92b31a7ca59a"},
-  "plug": {:hex, :plug, "1.10.0", "6508295cbeb4c654860845fb95260737e4a8838d34d115ad76cd487584e2fc4d", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "422a9727e667be1bf5ab1de03be6fa0ad67b775b2d84ed908f3264415ef29d4a"},
-  "plug_cowboy": {:hex, :plug_cowboy, "2.1.2", "8b0addb5908c5238fac38e442e81b6fcd32788eaa03246b4d55d147c47c5805e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "7d722581ce865a237e14da6d946f92704101740a256bd13ec91e63c0b122fc70"},
+  "plug": {:hex, :plug, "1.10.1", "c56a6d9da7042d581159bcbaef873ba9d87f15dce85420b0d287bca19f40f9bd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "b5cd52259817eb8a31f2454912ba1cff4990bca7811918878091cb2ab9e52cb8"},
+  "plug_cowboy": {:hex, :plug_cowboy, "2.2.1", "fcf58aa33227a4322a050e4783ee99c63c031a2e7f9a2eb7340d55505e17f30f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3b43de24460d87c0971887286e7a20d40462e48eb7235954681a20cee25ddeb6"},
   "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
   "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
   "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
-  "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4737ce62a31747b4c63c12b20c62307e51bb4fcd730ca0c32c280991e0606c90"},
+  "postgrex": {:hex, :postgrex, "0.15.4", "5d691c25fc79070705a2ff0e35ce0822b86a0ee3c6fdb7a4fb354623955e1aed", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "306515b9d975fcb2478dc337a1d27dc3bf8af7cd71017c333fe9db3a3d211b0a"},
   "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
-  "ref_inspector": {:hex, :ref_inspector, "1.3.0", "a02b89647440d084f2867ecece7a99895bcd4683482397fe086508bb22a165f3", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:yamerl, "~> 0.7", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "d2069ae6b371112ac696a3cd116fd1e08d5726249b8d1357f377e67f0716cc10"},
+  "ref_inspector": {:hex, :ref_inspector, "1.3.1", "bb0489a4c4299dcd633f2b7a60c41a01f5590789d0b28225a60be484e1fbe777", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:yamerl, "~> 0.7", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "3172eb1b08e5c69966f796e3fe0e691257546fa143a5eb0ecc18a6e39b233854"},
   "sentry": {:hex, :sentry, "7.2.4", "b5bc90b594d40c2e653581e797a5fd2fdf994f2568f6bd66b7fa4971598be8d5", [:mix], [{:hackney, "~> 1.8 or 1.6.5", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "4ee4d368b5013076afcc8b73ed028bdc8ee9db84ea987e3591101e194c1fc24b"},
   "siphash": {:hex, :siphash, "3.2.0", "ec03fd4066259218c85e2a4b8eec4bb9663bc02b127ea8a0836db376ba73f2ed", [:make, :mix], [], "hexpm", "ba3810701c6e95637a745e186e8a4899087c3b079ba88fb8f33df054c3b0b7c3"},
   "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"},
   "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
-  "timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"},
+  "timex": {:hex, :timex, "3.6.2", "845cdeb6119e2fef10751c0b247b6c59d86d78554c83f78db612e3290f819bc2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "26030b46199d02a590be61c2394b37ea25a3664c02fafbeca0b24c972025d47a"},
   "tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"},
   "ua_inspector": {:hex, :ua_inspector, "0.20.0", "01939baf5706f7d6c2dc0affbbd7f5e14309ba43ebf8967aa6479ee2204f23bc", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.0", [hex: :poolboy, repo: "hexpm", optional: false]}, {:yamerl, "~> 0.7", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "30e8623b9f55e7d58be12fc2afd50be8792ec14192c289701d3cc93ad6027f26"},
   "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},
-  "yamerl": {:hex, :yamerl, "0.7.0", "e51dba652dce74c20a88294130b48051ebbbb0be7d76f22de064f0f3ccf0aaf5", [:rebar3], [], "hexpm", "cb5a4481e2e2ad36db83bd9962153e1a9208e2b2484185e33fc2caac6a50b108"},
+  "yamerl": {:hex, :yamerl, "0.8.0", "8214cfe16bbabe5d1d6c14a14aea11c784b9a21903dd6a7c74f8ce180adae5c7", [:rebar3], [], "hexpm", "010634477bf9c208a0767dcca89116c2442cf0b5e87f9c870f85cd1c3e0c2aab"},
 }
diff --git a/priv/repo/migrations/20190618165016_add_public_sites.exs b/priv/repo/migrations/20190618165016_add_public_sites.exs
index a92d2acd..ccc64193 100644
--- a/priv/repo/migrations/20190618165016_add_public_sites.exs
+++ b/priv/repo/migrations/20190618165016_add_public_sites.exs
@@ -1,11 +1,12 @@
 defmodule Plausible.Repo.Migrations.AddPublicSites do
   use Ecto.Migration
+  @host Application.get_env(:plausible, :url, :host)
 
   def change do
     alter table(:sites) do
       add :public, :boolean, null: false, default: false
     end
 
-    execute "update sites set public=true where domain='plausible.io'"
+    execute "update sites set public=true where domain='#{@host}'"
   end
 end
diff --git a/rel/env.bat.eex b/rel/env.bat.eex
new file mode 100644
index 00000000..22be2800
--- /dev/null
+++ b/rel/env.bat.eex
@@ -0,0 +1,6 @@
+@echo off
+rem Set the release to work across nodes. If using the long name format like
+rem the one below (my_app@127.0.0.1), you need to also uncomment the
+rem RELEASE_DISTRIBUTION variable below. Must be "sname", "name" or "none".
+rem set RELEASE_DISTRIBUTION=name
+rem set RELEASE_NODE=<%= @release.name %>@127.0.0.1
diff --git a/rel/env.sh.eex b/rel/env.sh.eex
new file mode 100644
index 00000000..382cd833
--- /dev/null
+++ b/rel/env.sh.eex
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# Sets and enables heart (recommended only in daemon mode)
+# case $RELEASE_COMMAND in
+#   daemon*)
+#     HEART_COMMAND="$RELEASE_ROOT/bin/$RELEASE_NAME $RELEASE_COMMAND"
+#     export HEART_COMMAND
+#     export ELIXIR_ERL_OPTIONS="-heart"
+#     ;;
+#   *)
+#     ;;
+# esac
+
+# Set the release to work across nodes. If using the long name format like
+# the one below (my_app@127.0.0.1), you need to also uncomment the
+# RELEASE_DISTRIBUTION variable below. Must be "sname", "name" or "none".
+# export RELEASE_DISTRIBUTION=name
+# export RELEASE_NODE=<%= @release.name %>@127.0.0.1
diff --git a/rel/overlays/createdb.sh b/rel/overlays/createdb.sh
new file mode 100755
index 00000000..21a221f0
--- /dev/null
+++ b/rel/overlays/createdb.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# Creates the database if needed
+
+
+BIN_DIR=`dirname "$0"`
+
+${BIN_DIR}/bin/plausible eval Plausible.Release.createdb
\ No newline at end of file
diff --git a/rel/overlays/migrate.sh b/rel/overlays/migrate.sh
new file mode 100755
index 00000000..d77c1ece
--- /dev/null
+++ b/rel/overlays/migrate.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+# starts the db migration
+
+BIN_DIR=`dirname "$0"`
+
+${BIN_DIR}/bin/plausible eval Plausible.Release.migrate
\ No newline at end of file
diff --git a/rel/overlays/rollback.sh b/rel/overlays/rollback.sh
new file mode 100755
index 00000000..db69eba6
--- /dev/null
+++ b/rel/overlays/rollback.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+BIN_DIR=`dirname "$0"`
+
+${BIN_DIR}/bin/plausible eval Plausible.Release.rollback
\ No newline at end of file
diff --git a/rel/overlays/seed.sh b/rel/overlays/seed.sh
new file mode 100755
index 00000000..1791f86d
--- /dev/null
+++ b/rel/overlays/seed.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+BIN_DIR=`dirname "$0"`
+
+${BIN_DIR}/bin/plausible eval Plausible.Release.seed
\ No newline at end of file
diff --git a/rel/vm.args.eex b/rel/vm.args.eex
new file mode 100644
index 00000000..957d9832
--- /dev/null
+++ b/rel/vm.args.eex
@@ -0,0 +1,11 @@
+## Customize flags given to the VM: http://erlang.org/doc/man/erl.html
+## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here
+
+## Number of dirty schedulers doing IO work (file, sockets, and others)
+##+SDio 5
+
+## Increase number of concurrent ports/sockets
+##+Q 65536
+
+## Tweak GC to run more often
+##-env ERL_FULLSWEEP_AFTER 10
diff --git a/test/test_helper.exs b/test/test_helper.exs
index 884d9340..3b76c7e3 100644
--- a/test/test_helper.exs
+++ b/test/test_helper.exs
@@ -1,4 +1,5 @@
 {:ok, _} = Application.ensure_all_started(:ex_machina)
+ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter])
 Plausible.Test.ClickhouseSetup.run()
 ExUnit.start()
 Application.ensure_all_started(:double)
-- 
GitLab