diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 44b6dcdae585c7754007bb9d0d49603e34ff0051..8b29408ba408358cf7f39d2866a921b57e17c66d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -15,6 +15,7 @@ include:
 - /mailu/pipeline.yml
 - /mastodon/pipeline.yml
 - /oauth2-proxy/pipeline.yml
+- /powerdns/pipeline.yml
 - /quassel/pipeline.yml
 - /restic/pipeline.yml
 - /rtorrent/pipeline.yml
diff --git a/powerdns/Chart.yaml b/powerdns/Chart.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..55e9bac6b7e83e0afa5598538a4a92e56ed3bf36
--- /dev/null
+++ b/powerdns/Chart.yaml
@@ -0,0 +1,6 @@
+apiVersion: v2
+name: powerdns
+description: Helm Chart for powerdns-Core
+type: application
+version: 0.0.1
+appVersion: "v4.4.1"
diff --git a/powerdns/pipeline.yml b/powerdns/pipeline.yml
new file mode 100644
index 0000000000000000000000000000000000000000..74986c4f3a75e6906b7790c6700ee6e711e3309a
--- /dev/null
+++ b/powerdns/pipeline.yml
@@ -0,0 +1,21 @@
+lint-powerdns:
+  stage: lint
+  rules:
+    - changes:
+        - powerdns/**/*
+  script:
+    - helm lint powerdns
+
+release-powerdns:
+  stage: release
+  needs:
+    - lint-powerdns
+  rules:
+    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
+      changes:
+        - powerdns/**/*
+  script:
+    - apk add --no-cache git
+    - helm plugin install https://github.com/chartmuseum/helm-push.git
+    - helm repo add --username gitlab-ci-token --password $CI_JOB_TOKEN repo ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/stable
+    - helm cm-push powerdns repo
diff --git a/powerdns/templates/_helpers.tpl b/powerdns/templates/_helpers.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..aad04af394e6791fcbdf9d280bbe36332aa310e4
--- /dev/null
+++ b/powerdns/templates/_helpers.tpl
@@ -0,0 +1,56 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "powerdns-helm.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "powerdns-helm.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "powerdns-helm.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "powerdns-helm.labels" -}}
+helm.sh/chart: {{ include "powerdns-helm.chart" . }}
+{{ include "powerdns-helm.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "powerdns-helm.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "powerdns-helm.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+
+{{- define "powerdns-helm.sslPath" -}}
+/certs
+{{- end }}
diff --git a/powerdns/templates/configmap.yaml b/powerdns/templates/configmap.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c7fd0ccb1311368694a687ddc0076b8a7ebf69cb
--- /dev/null
+++ b/powerdns/templates/configmap.yaml
@@ -0,0 +1,26 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ include "powerdns-helm.fullname" . }}
+  labels:
+    {{- include "powerdns-helm.labels" . | nindent 4 }}
+data:
+  webserver.conf: |-
+    api={{ .Values.api }}
+    webserver-address=0.0.0.0
+    webserver-port=8080
+    webserver-print-arguments=no
+    webserver-allow-from=0.0.0.0/0,::/0
+  database.conf: |-
+    launch=gpgsql
+    gpgsql-host={{ .Values.database.hostname }}
+    gpgsql-port={{ .Values.database.port }}
+    gpgsql-dbname={{ .Values.database.database }}
+    gpgsql-user={{ .Values.database.username }}
+    gpgsql-dnssec={{ .Values.database.dnssec }}
+  dnsupdate.conf: |-
+    dnsupdate=yes
+    allow-dnsupdate-from=127.0.0.0/8 10.244.0.0/16
+  listen.conf: |-
+    local-port=5353
+
diff --git a/powerdns/templates/deployment.yaml b/powerdns/templates/deployment.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8af71122f71e97f4c9905577bad09c3aefba1c4e
--- /dev/null
+++ b/powerdns/templates/deployment.yaml
@@ -0,0 +1,94 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "powerdns-helm.fullname" . }}
+  labels:
+    {{- include "powerdns-helm.labels" . | nindent 4 }}
+spec:
+  replicas: {{ .Values.replicaCount }}
+  selector:
+    matchLabels:
+      {{- include "powerdns-helm.selectorLabels" . | nindent 6 }}
+  template:
+    metadata:
+      {{- with .Values.podAnnotations }}
+      annotations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      labels:
+        {{- include "powerdns-helm.selectorLabels" . | nindent 8 }}
+    spec:
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      securityContext:
+        {{- toYaml .Values.podSecurityContext | nindent 8 }}
+      volumes:
+        - name: secrets
+          secret:
+            secretName: {{ include "powerdns-helm.fullname" . }}
+        - name: configs
+          configMap:
+            name: {{ include "powerdns-helm.fullname" . }}
+      containers:
+        - name: {{ .Chart.Name }}
+          securityContext:
+            {{- toYaml .Values.securityContext | nindent 12 }}
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          env:
+            - name: MYSQL_AUTOCONF
+              value: "false"
+          ports:
+            - name: http
+              containerPort: 8080
+              protocol: TCP
+            - name: dns-udp
+              containerPort: 5353
+              protocol: UDP
+            - name: dns-tcp
+              containerPort: 5353
+              protocol: TCP
+          #startupProbe:
+          #  httpGet:
+          #    path: /healthz
+          #    port: metrics
+          #livenessProbe:
+          #  httpGet:
+          #    path: /healthz
+          #    port: metrics
+          #readinessProbe:
+          #  httpGet:
+          #    path: /healthz
+          #    port: metrics
+          resources:
+            {{- toYaml .Values.resources | nindent 12 }}
+          volumeMounts:
+            - mountPath: /etc/pdns/conf.d/secrets.conf
+              name: secrets
+              subPath: secrets.conf
+            - mountPath: /etc/pdns/conf.d/database.conf
+              name: configs
+              subPath: database.conf
+            - mountPath: /etc/pdns/conf.d/dnsupdate.conf
+              name: configs
+              subPath: dnsupdate.conf
+            - mountPath: /etc/pdns/conf.d/listen.conf
+              name: configs
+              subPath: listen.conf
+            - mountPath: /etc/pdns/conf.d/webserver.conf
+              name: configs
+              subPath: webserver.conf
+      {{- with .Values.nodeSelector }}
+      nodeSelector:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.affinity }}
+      affinity:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.tolerations }}
+      tolerations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
diff --git a/powerdns/templates/ingress.yaml b/powerdns/templates/ingress.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c9fc46b3062eff6f02f76a64e7bb82bfca812a5c
--- /dev/null
+++ b/powerdns/templates/ingress.yaml
@@ -0,0 +1,21 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: {{ include "powerdns-helm.fullname" . }}
+  labels:
+    {{- include "powerdns-helm.labels" . | nindent 4 }}
+  annotations:
+    kubernetes.io/ingress.class: nginx
+    nginx.ingress.kubernetes.io/rewrite-target: /$1
+spec:
+  rules:
+    - host: "{{ .Values.ingress.host }}"
+      http:
+        paths:
+          - path: "/api($|/.*)"
+            backend:
+              service:
+                name: {{ include "powerdns-helm.fullname" . }}
+                port:
+                  name: http
+            pathType: Prefix
diff --git a/powerdns/templates/secret.yaml b/powerdns/templates/secret.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0b15b0c68126a6c93795626b9c9124e050f2b454
--- /dev/null
+++ b/powerdns/templates/secret.yaml
@@ -0,0 +1,11 @@
+apiVersion: v1
+kind: Secret
+metadata:
+  name: {{ include "powerdns-helm.fullname" . }}
+  labels:
+    {{- include "powerdns-helm.labels" . | nindent 4 }}
+stringData:
+  apikey: {{ .Values.apiKey }}
+  secrets.conf: |-
+    api-key={{ .Values.apiKey }}
+    gpgsql-password={{ .Values.database.password }}
diff --git a/powerdns/templates/service.yaml b/powerdns/templates/service.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1f062e9dd35af0f196a5228779971fd335ff84a5
--- /dev/null
+++ b/powerdns/templates/service.yaml
@@ -0,0 +1,23 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "powerdns-helm.fullname" . }}
+  labels:
+    {{- include "powerdns-helm.labels" . | nindent 4 }}
+spec:
+  type: {{ .Values.service.type }}
+  ports:
+    - name: http
+      port: 80
+      targetPort: 8080
+      protocol: TCP
+    - name: dns-udp
+      port: 53
+      targetPort: 5353
+      protocol: UDP
+    - name: dns-tcp
+      port: 53
+      targetPort: 5353
+      protocol: TCP
+  selector:
+    {{- include "powerdns-helm.selectorLabels" . | nindent 4 }}
diff --git a/powerdns/values.yaml b/powerdns/values.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..ffd5e7b5aa747d7056d42101207e78d8ef2891e3
--- /dev/null
+++ b/powerdns/values.yaml
@@ -0,0 +1,53 @@
+replicaCount: 1
+
+image:
+  repository: psitrax/powerdns
+  pullPolicy: IfNotPresent
+  tag: ""
+
+imagePullSecrets: [ ]
+nameOverride: ""
+fullnameOverride: ""
+
+apiKey: "hunter2"
+
+ingress:
+  host: "dns.example.tld"
+
+database:
+  hostname: "localhost"
+  port: 5432
+  database: "powerdns"
+  username: "powerdns"
+  password: "hunter2"
+  dnssec: "yes"
+
+podAnnotations: { }
+
+podSecurityContext:
+  fsGroup: 2000
+
+securityContext:
+  capabilities:
+    drop:
+      - ALL
+  readOnlyRootFilesystem: true
+  runAsNonRoot: true
+  runAsUser: 1000
+
+service:
+  type: ClusterIP
+
+resources:
+  limits:
+    cpu: 500m
+    memory: 512Mi
+  requests:
+    cpu: 20m
+    memory: 64Mi
+
+nodeSelector: { }
+
+tolerations: [ ]
+
+affinity: { }