diff --git a/kubernetes/README.md b/kubernetes/README.md
new file mode 100644
index 0000000..a1844f9
--- /dev/null
+++ b/kubernetes/README.md
@@ -0,0 +1,38 @@
+# Plausible Analytics in Kubernetes
+
+This guide is designed to extend the [normal self-hosting guide](https://plausible.io/docs/self-hosting), please refer to it before following this guide.
+
+## 1. Clone the hosting repo
+
+To deploy Plausible Analytics into Kubernetes first download the [plausible/hosting](https://github.com/plausible/hosting) repo.
+
+```bash
+git clone https://github.com/plausible/hosting
+cd hosting
+```
+
+## 2. Add required configuration
+
+Like the original self hosting guide configure your server in the `plausible-conf.env` file.
+
+## 3. Deploy the server
+
+Once you've entered your secret key base, base url and admin credentials, you're ready to deploy the server:
+
+```bash
+kubectl create namespace plausible # Create a new namespace for all resources
+kubectl -n plausible create secret generic plausible-config --from-env-file=plausible-conf.env # Create a configmap from the plausible-conf.env file
+# Please change the Postgres and Clickhouse passwords to something more secure here!
+kubectl -n plausible create secret generic plausible-db-user --from-literal='username=postgres' --from-literal='password=postgres' # Create the Postgres user
+kubectl -n plausible create secret generic plausible-events-db-user --from-literal='username=clickhouse' --from-literal='password=clickhouse' # Create the Clickhouse user
+kubectl -n plausible apply -f ./kubernetes
+```
+
+You can now navigate to http://{hostname}:8000 and see the login screen.
+
+When you first log in with your admin credentials, you will be prompted to enter a verification code which has been sent to your email. Please configure your server for SMTP to receive this email. [Here are Plausible's SMTP configuration options](https://plausible.io/docs/self-hosting-configuration#mailersmtp-setup).
+Otherwise, run this command to verify all users in the database:
+
+```bash
+kubectl -n plausible exec statefulset/plausible-db -- /bin/bash -c 'psql -U $POSTGRES_USER -d $POSTGRES_DB -c "UPDATE users SET email_verified = true;"'
+```
diff --git a/kubernetes/plausible-db.yaml b/kubernetes/plausible-db.yaml
new file mode 100644
index 0000000..f1353d1
--- /dev/null
+++ b/kubernetes/plausible-db.yaml
@@ -0,0 +1,114 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: plausible-db
+ labels:
+ app.kubernetes.io/name: postgres
+ app.kubernetes.io/component: database
+ app.kubernetes.io/part-of: plausible
+spec:
+ type: ClusterIP
+ ports:
+ - name: db
+ port: 5432
+ targetPort: 5432
+ protocol: TCP
+ selector:
+ app.kubernetes.io/name: postgres
+ app.kubernetes.io/component: database
+ app.kubernetes.io/part-of: plausible
+---
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+ name: plausible-db
+ labels:
+ app.kubernetes.io/name: postgres
+ app.kubernetes.io/component: database
+ app.kubernetes.io/part-of: plausible
+spec:
+ replicas: 1
+ serviceName: plausible-db
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: postgres
+ app.kubernetes.io/component: database
+ app.kubernetes.io/part-of: plausible
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: postgres
+ app.kubernetes.io/component: database
+ app.kubernetes.io/part-of: plausible
+ spec:
+ restartPolicy: Always
+ # see https://github.com/docker-library/postgres/blob/6bbf1c7b308d1c4288251d73c37f6caf75f8a3d4/14/buster/Dockerfile
+ securityContext:
+ runAsUser: 999
+ runAsGroup: 999
+ fsGroup: 999
+ containers:
+ - name: plausible-db
+ image: postgres:latest
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 5432
+ volumeMounts:
+ - name: data
+ mountPath: /var/lib/postgresql/data
+ env:
+ - name: POSTGRES_DB
+ value: plausible
+ - name: PGDATA
+ value: /var/lib/postgresql/data/pgdata
+ - name: POSTGRES_USER
+ valueFrom:
+ secretKeyRef:
+ name: plausible-db-user
+ key: username
+ - name: POSTGRES_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: plausible-db-user
+ key: password
+ securityContext:
+ allowPrivilegeEscalation: false
+ resources:
+ limits:
+ memory: 2Gi
+ cpu: 1500m
+ requests:
+ memory: 65Mi
+ cpu: 15m
+ readinessProbe:
+ exec:
+ command:
+ - /bin/sh
+ - -c
+ - pg_isready -U postgres
+ initialDelaySeconds: 20
+ failureThreshold: 6
+ periodSeconds: 10
+ livenessProbe:
+ exec:
+ command:
+ - /bin/sh
+ - -c
+ - pg_isready -U postgres
+ initialDelaySeconds: 30
+ failureThreshold: 3
+ periodSeconds: 10
+ volumeClaimTemplates:
+ - metadata:
+ name: data
+ labels:
+ app.kubernetes.io/name: postgres
+ app.kubernetes.io/component: database
+ app.kubernetes.io/part-of: plausible
+ spec:
+ accessModes: ["ReadWriteOnce"]
+ resources:
+ requests:
+ storage: 128Mi
+ limits:
+ storage: 15Gi
diff --git a/kubernetes/plausible-events-db.yaml b/kubernetes/plausible-events-db.yaml
new file mode 100644
index 0000000..086d16d
--- /dev/null
+++ b/kubernetes/plausible-events-db.yaml
@@ -0,0 +1,150 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: plausible-events-db
+ labels:
+ app.kubernetes.io/name: clickhouse
+ app.kubernetes.io/component: database
+ app.kubernetes.io/part-of: plausible
+spec:
+ type: ClusterIP
+ ports:
+ - name: db
+ port: 8123
+ targetPort: 8123
+ protocol: TCP
+ selector:
+ app.kubernetes.io/name: clickhouse
+ app.kubernetes.io/component: database
+ app.kubernetes.io/part-of: plausible
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: plausible-events-db-config
+data:
+ clickhouse-config.xml: |
+
+
+ warning
+ true
+
+
+
+
+
+
+
+
+
+
+ clickhouse-user-config.xml: |
+
+
+
+ 0
+ 0
+
+
+
+---
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+ name: plausible-events-db
+ labels:
+ app.kubernetes.io/name: clickhouse
+ app.kubernetes.io/component: database
+ app.kubernetes.io/part-of: plausible
+spec:
+ replicas: 1
+ serviceName: plausible-events-db
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: clickhouse
+ app.kubernetes.io/component: database
+ app.kubernetes.io/part-of: plausible
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: clickhouse
+ app.kubernetes.io/component: database
+ app.kubernetes.io/part-of: plausible
+ spec:
+ restartPolicy: Always
+ # see https://github.com/ClickHouse/ClickHouse/blob/master/docker/server/Dockerfile
+ securityContext:
+ runAsUser: 101
+ runAsGroup: 101
+ fsGroup: 101
+ containers:
+ - name: plausible-events-db
+ image: yandex/clickhouse-server:latest
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 8123
+ volumeMounts:
+ - name: data
+ mountPath: /var/lib/clickhouse
+ - name: config
+ mountPath: /etc/clickhouse-server/config.d/logging.xml
+ subPath: clickhouse-config.xml
+ readOnly: true
+ - name: config
+ mountPath: /etc/clickhouse-server/users.d/logging.xml"
+ subPath: clickhouse-user-config.xml
+ readOnly: true
+ env:
+ - name: CLICKHOUSE_DB
+ value: plausible
+ - name: CLICKHOUSE_USER
+ valueFrom:
+ secretKeyRef:
+ name: plausible-events-db-user
+ key: username
+ - name: CLICKHOUSE_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: plausible-events-db-user
+ key: password
+ securityContext:
+ allowPrivilegeEscalation: false
+ resources:
+ limits:
+ memory: 2Gi
+ cpu: 1500m
+ requests:
+ memory: 80Mi
+ cpu: 10m
+ readinessProbe:
+ httpGet:
+ path: /ping
+ port: 8123
+ initialDelaySeconds: 20
+ failureThreshold: 6
+ periodSeconds: 10
+ livenessProbe:
+ httpGet:
+ path: /ping
+ port: 8123
+ initialDelaySeconds: 30
+ failureThreshold: 3
+ periodSeconds: 10
+ volumes:
+ - name: config
+ configMap:
+ name: plausible-events-db-config
+ volumeClaimTemplates:
+ - metadata:
+ name: data
+ labels:
+ app.kubernetes.io/name: clickhouse
+ app.kubernetes.io/component: database
+ app.kubernetes.io/part-of: plausible
+ spec:
+ accessModes: ["ReadWriteOnce"]
+ resources:
+ requests:
+ storage: 128Mi
+ limits:
+ storage: 20Gi
diff --git a/kubernetes/plausible-mail.yaml b/kubernetes/plausible-mail.yaml
new file mode 100644
index 0000000..b551ef7
--- /dev/null
+++ b/kubernetes/plausible-mail.yaml
@@ -0,0 +1,70 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: plausible-smtp
+ labels:
+ app.kubernetes.io/name: smtp
+ app.kubernetes.io/component: email
+ app.kubernetes.io/part-of: plausible
+spec:
+ type: ClusterIP
+ ports:
+ - name: smtp
+ port: 25
+ targetPort: 25
+ protocol: TCP
+ selector:
+ app.kubernetes.io/name: smtp
+ app.kubernetes.io/component: database
+ app.kubernetes.io/part-of: plausible
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: plausible-smtp
+ labels:
+ app.kubernetes.io/name: smtp
+ app.kubernetes.io/component: email
+ app.kubernetes.io/part-of: plausible
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: smtp
+ app.kubernetes.io/component: email
+ app.kubernetes.io/part-of: plausible
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: smtp
+ app.kubernetes.io/component: email
+ app.kubernetes.io/part-of: plausible
+ spec:
+ restartPolicy: Always
+ containers:
+ - name: plausible-smtp
+ image: bytemark/smtp:latest
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 25
+ securityContext:
+ allowPrivilegeEscalation: false
+ resources:
+ limits:
+ memory: 512Mi
+ cpu: 500m
+ requests:
+ memory: 5Mi
+ cpu: 1m
+ readinessProbe:
+ tcpSocket:
+ port: 25
+ initialDelaySeconds: 20
+ failureThreshold: 6
+ periodSeconds: 10
+ livenessProbe:
+ tcpSocket:
+ port: 25
+ initialDelaySeconds: 30
+ failureThreshold: 3
+ periodSeconds: 10
diff --git a/kubernetes/plausible.yaml b/kubernetes/plausible.yaml
new file mode 100644
index 0000000..994006f
--- /dev/null
+++ b/kubernetes/plausible.yaml
@@ -0,0 +1,150 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: plausible
+ labels:
+ app.kubernetes.io/name: plausible
+ app.kubernetes.io/component: server
+spec:
+ type: LoadBalancer
+ ports:
+ - name: http
+ port: 8000
+ targetPort: 8000
+ protocol: TCP
+ selector:
+ app.kubernetes.io/name: plausible
+ app.kubernetes.io/component: server
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: plausible
+ labels:
+ app.kubernetes.io/name: plausible
+ app.kubernetes.io/component: server
+spec:
+ # Plausible is not currently designed to run in a clustered scenario. Increasing the replicas of this deployment is highly NOT recommended!
+ replicas: 1
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: plausible
+ app.kubernetes.io/component: server
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: plausible
+ app.kubernetes.io/component: server
+ spec:
+ restartPolicy: Always
+ # see https://github.com/plausible/analytics/blob/master/Dockerfile
+ securityContext:
+ runAsUser: 1000
+ runAsGroup: 1000
+ fsGroup: 1000
+ initContainers:
+ - name: plausible-init
+ image: plausible/analytics:latest
+ command:
+ - "/bin/sh"
+ - "-c"
+ args:
+ - sleep 30 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh db init-admin
+ envFrom:
+ - secretRef:
+ name: plausible-config
+ env:
+ - name: POSTGRES_USER
+ valueFrom:
+ secretKeyRef:
+ name: plausible-db-user
+ key: username
+ - name: POSTGRES_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: plausible-db-user
+ key: password
+ - name: CLICKHOUSE_USER
+ valueFrom:
+ secretKeyRef:
+ name: plausible-events-db-user
+ key: username
+ - name: CLICKHOUSE_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: plausible-events-db-user
+ key: password
+ - name: DATABASE_URL
+ value: postgres://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@$(PLAUSIBLE_DB_SERVICE_HOST):$(PLAUSIBLE_DB_SERVICE_PORT)/plausible
+ - name: CLICKHOUSE_DATABASE_URL
+ value: http://$(CLICKHOUSE_USER):$(CLICKHOUSE_PASSWORD)@$(PLAUSIBLE_EVENTS_DB_SERVICE_HOST):$(PLAUSIBLE_EVENTS_DB_SERVICE_PORT)/plausible
+ - name: SMTP_HOST_ADDR
+ value: $(PLAUSIBLE_SMTP_SERVICE_HOST)
+ securityContext:
+ allowPrivilegeEscalation: false
+ resources:
+ limits:
+ memory: 2Gi
+ cpu: 1500m
+ requests:
+ memory: 50Mi
+ cpu: 10m
+ containers:
+ - name: plausible
+ image: plausible/analytics:latest
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 8000
+ envFrom:
+ - secretRef:
+ name: plausible-config
+ env:
+ - name: POSTGRES_USER
+ valueFrom:
+ secretKeyRef:
+ name: plausible-db-user
+ key: username
+ - name: POSTGRES_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: plausible-db-user
+ key: password
+ - name: CLICKHOUSE_USER
+ valueFrom:
+ secretKeyRef:
+ name: plausible-events-db-user
+ key: username
+ - name: CLICKHOUSE_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: plausible-events-db-user
+ key: password
+ - name: DATABASE_URL
+ value: postgres://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@$(PLAUSIBLE_DB_SERVICE_HOST):$(PLAUSIBLE_DB_SERVICE_PORT)/plausible
+ - name: CLICKHOUSE_DATABASE_URL
+ value: http://$(CLICKHOUSE_USER):$(CLICKHOUSE_PASSWORD)@$(PLAUSIBLE_EVENTS_DB_SERVICE_HOST):$(PLAUSIBLE_EVENTS_DB_SERVICE_PORT)/plausible
+ - name: SMTP_HOST_ADDR
+ value: $(PLAUSIBLE_SMTP_SERVICE_HOST)
+ securityContext:
+ allowPrivilegeEscalation: false
+ resources:
+ limits:
+ memory: 2Gi
+ cpu: 1500m
+ requests:
+ memory: 140Mi
+ cpu: 10m
+ readinessProbe:
+ httpGet:
+ path: /api/health
+ port: 8000
+ initialDelaySeconds: 35
+ failureThreshold: 6
+ periodSeconds: 10
+ livenessProbe:
+ httpGet:
+ path: /api/health
+ port: 8000
+ initialDelaySeconds: 45
+ failureThreshold: 3
+ periodSeconds: 10