diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 75be8c31bf24fec0e1fe61c7712f2d4168206ce6..05c5d0ee319caf460180bfa0917e7b479c5f35dd 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,6 +9,7 @@ stages:
 include:
 - actual/pipeline.yml
 - jellyfin/pipeline.yml
+- languagetool/pipeline.yml
 - mailu/pipeline.yml
 - oauth2-proxy/pipeline.yml
 - quassel/pipeline.yml
diff --git a/languagetool/Chart.yaml b/languagetool/Chart.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..cb56fcd58fe11fbd9984ab1c0a6d1f35fecbe797
--- /dev/null
+++ b/languagetool/Chart.yaml
@@ -0,0 +1,6 @@
+apiVersion: v2
+name: languagetool
+description: Helm Chart for languagetool
+type: application
+version: 1.0.0
+appVersion: "340fa7f"
diff --git a/languagetool/pipeline.yml b/languagetool/pipeline.yml
new file mode 100644
index 0000000000000000000000000000000000000000..269db7340493ac7e14ffcea72b9be8f02e2de01f
--- /dev/null
+++ b/languagetool/pipeline.yml
@@ -0,0 +1,12 @@
+lint-languagetool:
+  stage: lint
+  script:
+    - helm lint languagetool
+
+release-languagetool:
+  stage: release
+  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 languagetool repo
diff --git a/languagetool/templates/_helpers.tpl b/languagetool/templates/_helpers.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..1066e257704e9bee96225ba05a27aba98659dceb
--- /dev/null
+++ b/languagetool/templates/_helpers.tpl
@@ -0,0 +1,56 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "languagetool-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 "languagetool-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 "languagetool-helm.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "languagetool-helm.labels" -}}
+helm.sh/chart: {{ include "languagetool-helm.chart" . }}
+{{ include "languagetool-helm.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "languagetool-helm.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "languagetool-helm.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+
+{{- define "languagetool-helm.sslPath" -}}
+/certs
+{{- end }}
diff --git a/languagetool/templates/configmap.yaml b/languagetool/templates/configmap.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3a77fe87126efaeb519ef508bb7bc71c94e243f0
--- /dev/null
+++ b/languagetool/templates/configmap.yaml
@@ -0,0 +1,29 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ include "languagetool-helm.fullname" . }}
+  labels:
+    {{- include "languagetool-helm.labels" . | nindent 4 }}
+data:
+  "models.conf": |-
+    {{ if .Values.ngrams.enabled }}
+    languageModel={{ .Values.ngrams.path }}
+    {{ end }}
+    {{ if .Values.word2vec.enabled }}
+    word2vecModel={{ .Values.word2vec.path }}
+    {{ end }}
+    {{ if .Values.beolingus.enabled }}
+    beolingusFile={{ .Values.beolingus.path }}
+    {{ end }}
+    {{ if .Values.fasttext.enabled }}
+    fasttextModel={{ .Values.fasttext.path }}
+    fasttextBinary=/languagetool/fasttext
+    {{ end }}
+    {{ if .Values.metrics.enabled }}
+    prometheusMonitoring=true
+    {{ end }}
+    {{ if .Values.redis.enabled }}
+    redisHost={{ .Values.redis.host }}
+    redisDatabase={{ .Values.redis.database }}
+    redisPassword={{ .Values.redis.password }}
+    {{ end }}
diff --git a/languagetool/templates/deployment.yaml b/languagetool/templates/deployment.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5ea84356eac965f61c7aeff7cbffeb99981738ed
--- /dev/null
+++ b/languagetool/templates/deployment.yaml
@@ -0,0 +1,88 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "languagetool-helm.fullname" . }}
+  labels:
+    {{- include "languagetool-helm.labels" . | nindent 4 }}
+spec:
+  replicas: {{ .Values.replicaCount }}
+  selector:
+    matchLabels:
+      {{- include "languagetool-helm.selectorLabels" . | nindent 6 }}
+  template:
+    metadata:
+      {{- with .Values.podAnnotations }}
+      annotations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      labels:
+        {{- include "languagetool-helm.selectorLabels" . | nindent 8 }}
+    spec:
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      securityContext:
+        {{- toYaml .Values.podSecurityContext | nindent 8 }}
+      volumes:
+        - name: tmp
+          emptyDir: {}
+        - name: models
+          {{- .Values.volume | nindent 10 }}
+        - name: config
+          configMap:
+            name: {{ include "languagetool-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 }}
+          args:
+            - "--config"
+            - "/config/models.conf"
+            - "--port"
+            - "8080"
+            - "--public"
+            - "--allow-origin"
+            - "{{ .Values.ingress.origins }}"
+          ports:
+            - name: http
+              containerPort: 8080
+              protocol: TCP
+            - name: metrics
+              containerPort: 9301
+              protocol: TCP
+          startupProbe:
+            httpGet:
+              path: /v2/healthcheck
+              port: http
+          livenessProbe:
+            httpGet:
+              path: /v2/healthcheck
+              port: http
+          readinessProbe:
+            httpGet:
+              path: /v2/healthcheck
+              port: http
+          resources:
+            {{- toYaml .Values.resources | nindent 12 }}
+          volumeMounts:
+            - mountPath: "/tmp"
+              name: tmp
+            - mountPath: "/models"
+              name: models
+            - mountPath: "/config"
+              name: config
+      {{- 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/languagetool/templates/ingress.yaml b/languagetool/templates/ingress.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..2ff5bfeb4fe030e23fe8e4376321d50e0f7d65f7
--- /dev/null
+++ b/languagetool/templates/ingress.yaml
@@ -0,0 +1,20 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: {{ include "languagetool-helm.fullname" . }}
+  labels:
+    {{- include "languagetool-helm.labels" . | nindent 4 }}
+  annotations:
+    {{- .Values.ingress.annotations | toYaml | nindent 4 }}
+spec:
+  rules:
+    - host: "{{ .Values.ingress.host }}"
+      http:
+        paths:
+          - path: "{{ .Values.ingress.path }}"
+            backend:
+              service:
+                name: {{ include "languagetool-helm.fullname" . }}
+                port:
+                  name: http
+            pathType: Prefix
diff --git a/languagetool/templates/service.yaml b/languagetool/templates/service.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fde6aabc0a6d14f7b7def23eaf2bcab0df235b01
--- /dev/null
+++ b/languagetool/templates/service.yaml
@@ -0,0 +1,25 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "languagetool-helm.fullname" . }}
+  {{ if .Values.metrics.enabled }}
+  annotations:
+    prometheus.io/path: "/metrics"
+    prometheus.io/port: "{{ .Values.metrics.port }}"
+    prometheus.io/scrape: "true"
+  {{ end }}
+  labels:
+    {{- include "languagetool-helm.labels" . | nindent 4 }}
+spec:
+  type: {{ .Values.service.type }}
+  ports:
+    - port: 80
+      targetPort: http
+      protocol: TCP
+      name: http
+    - port: 9090
+      targetPort: metrics
+      protocol: TCP
+      name: metrics
+  selector:
+    {{- include "languagetool-helm.selectorLabels" . | nindent 4 }}
diff --git a/languagetool/values.yaml b/languagetool/values.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c7a8fd09a60aa94a25bc804e9487d79018786082
--- /dev/null
+++ b/languagetool/values.yaml
@@ -0,0 +1,72 @@
+replicaCount: 1
+
+image:
+  repository: k8r.eu/justjanne/languagetool-docker
+  pullPolicy: IfNotPresent
+  tag: ""
+
+imagePullSecrets: [ ]
+nameOverride: ""
+fullnameOverride: ""
+
+ngrams:
+  enabled: false
+  path: "/models/ngrams"
+
+word2vec:
+  enabled: false
+  path: "/models/word2vec"
+
+beolingus:
+  enabled: false
+  path: "/models/beolingus/de-en.txt"
+
+fasttext:
+  enabled: false
+  path: "/models/fasttext/lid.176.bin"
+
+redis:
+  enabled: false
+  host: "example.com"
+  password: ""
+  database: 0
+
+volume: |-
+  emptyDir: {}
+
+metrics:
+  enabled: true
+
+service:
+  type: ClusterIP
+
+ingress:
+  host: "example.com"
+  path: "/"
+  annotations: { }
+
+podAnnotations: { }
+
+podSecurityContext:
+  fsGroup: 2000
+
+securityContext:
+  capabilities:
+    drop:
+      - ALL
+  runAsNonRoot: true
+  runAsUser: 1000
+
+resources:
+  limits:
+    cpu: "800m"
+    memory: 4Gi
+  requests:
+    cpu: 100m
+    memory: 512Mi
+
+nodeSelector: { }
+
+tolerations: [ ]
+
+affinity: { }