diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1c927ea61645e48d34e40b872a742915a5ce05cb..612c8483cbcf535b64d33a91bc59b4d0d21e6aee 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -16,7 +16,7 @@ zola check:
   script:
   - zola check
 
-docker build:
+.build:
   stage: build
   interruptible: true
   image:
@@ -28,5 +28,78 @@ docker build:
        --context $CI_PROJECT_DIR
        --dockerfile $CI_PROJECT_DIR/Dockerfile
        --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
+       --digest-file DIGESTFILE
+       --build-arg BASE_URL=$BASE_URL
        --cache=true
        --cleanup
+  artifacts:
+    paths:
+      - DIGESTFILE
+
+
+build production:
+  extends: .build
+  only:
+    - master
+
+build review:
+  extends: .build
+  variables:
+    BASE_URL: https://$CI_COMMIT_REF_SLUG.review.teckids.org
+  only:
+    - merge_requests
+
+.deploy:
+  stage: deploy
+  image: line/kubectl-kustomize:latest
+  tags:
+    - teckids-trusted
+  before_script:
+    - echo $KUBECONFIG | base64 -d > /tmp/kubeconfig
+
+deploy production:
+  extends: .deploy
+  environment:
+    name: production
+    url: https://teckids.org
+  script:
+    - cd deploy/overlays/production
+    - kustomize edit set image registry.edugit.org/teckids/team-pr/teckids.org:$CI_COMMIT_REF_NAME@$(cat ../../../DIGESTFILE)
+    - kustomize build > output.yaml
+    - kubectl --kubeconfig=/tmp/kubeconfig apply -f output.yaml
+  only:
+    - master
+
+deploy review:
+  extends: .deploy
+  script: deploy_review
+  environment:
+    name: review/$CI_COMMIT_REF_SLUG
+    url: https://$CI_COMMIT_REF_SLUG.review.teckids.org
+    on_stop: delete review
+    auto_stop_in: 3 days
+  only:
+    - merge_requests
+  when: manual
+  script:
+    - cd deploy/overlays/review
+    - kustomize edit set image registry.edugit.org/teckids/team-pr/teckids.org:$CI_COMMIT_REF_NAME@$(cat ../../../DIGESTFILE)
+    - kustomize edit set nameprefix $CI_COMMIT_REF_SLUG
+    - kustomize build | sed s/__REVIEW_NAME__/$CI_COMMIT_REF_SLUG/g > output.yaml
+    - kubectl --kubeconfig=/tmp/kubeconfig apply -f output.yaml
+
+delete review:
+  extends: .deploy
+  script: delete_review
+  environment:
+    name: review/$CI_COMMIT_REF_SLUG
+    action: stop
+  before_script:
+    - echo $KUBECONFIG | base64 -d > /tmp/kubeconfig
+    - cd deploy/overlays/review
+    - kustomize build | sed s/__REVIEW_NAME__/$CI_COMMIT_REF_SLUG/g > output.yaml
+  script:
+    - kubectl --kubeconfig=/tmp/kubeconfig delete -f output.yaml
+  when: manual
+  only:
+    - merge_requests
diff --git a/Caddyfile b/Caddyfile
index 7416457ab3200456d636ccb1fb3bb751e489b1e8..b87b72e871892c133c7c558202ec4bbfd54f294b 100644
--- a/Caddyfile
+++ b/Caddyfile
@@ -1,4 +1,4 @@
-http://teckids.org {
+http:// {
     root * /srv
     encode zstd gzip
     file_server
diff --git a/Dockerfile b/Dockerfile
index 54609ce176a041415468669e56fe5d1566c58aa6..21ced1fbddd431428b1f9a3015aed5e3a1b4aea7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,12 +1,14 @@
 FROM alpine:latest AS build
 
+ARG BASE_URL=https://teckids.org
+
 RUN apk add --update-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community/ zola yarn
 
 COPY . /src
 WORKDIR /src
 
 RUN yarn install
-RUN zola build
+RUN zola build -u $BASE_URL
 
 
 FROM caddy:alpine AS serve
diff --git a/deploy/base/deployment.yaml b/deploy/base/deployment.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..adeb72283fb65ace8c124a73a2e8e4d5a9c3b9bb
--- /dev/null
+++ b/deploy/base/deployment.yaml
@@ -0,0 +1,35 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: teckids-website
+  namespace: teckids-website
+  labels:
+    app: teckids-website
+spec:
+  replicas: 3
+  selector:
+    matchLabels:
+      app: teckids-website
+  template:
+    metadata:
+      labels:
+        app: teckids-website
+    spec:
+      containers:
+      - name: caddy
+        image: registry.edugit.org/teckids/team-pr/teckids.org:IMAGE_TAG
+        ports:
+          - containerPort: 80
+            name: http
+        livenessProbe:
+          httpGet:
+            scheme: HTTP
+            port: http
+            path: /
+          timeoutSeconds: 10
+        readinessProbe:
+          httpGet:
+            scheme: HTTP
+            port: http
+            path: /
+          timeoutSeconds: 10
diff --git a/deploy/base/ingress.yaml b/deploy/base/ingress.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a33be22f08181157de6d107a821c42d425e07a14
--- /dev/null
+++ b/deploy/base/ingress.yaml
@@ -0,0 +1,25 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  annotations:
+    cert-manager.io/cluster-issuer: letsencrypt-prod
+    kubernetes.io/tls-acme: "true"
+  name: teckids-website-caddy
+  namespace: teckids-website
+spec:
+  ingressClassName: nginx
+  rules:
+  - host: teckids.org
+    http:
+      paths:
+      - backend:
+          service:
+            name: teckids-website
+            port:
+              number: 80
+        path: /
+        pathType: Prefix
+  tls:
+  - hosts:
+    - teckids.org
+    secretName: teckids-website-tls
diff --git a/deploy/base/kustomization.yaml b/deploy/base/kustomization.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..05be69a0a2858bfcff8a0fe91ea4aad6252ade01
--- /dev/null
+++ b/deploy/base/kustomization.yaml
@@ -0,0 +1,12 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+commonAnnotations:
+  source-repository: https://edugit.org/Teckids/team-pr/teckids.org
+resources:
+- deployment.yaml
+- service.yaml
+- ingress.yaml
+labels:
+- includeSelectors: true
+  pairs:
+    app: teckids-website
diff --git a/deploy/base/service.yaml b/deploy/base/service.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6af84b0e7671025a5a0ee16cb516b1927184ddca
--- /dev/null
+++ b/deploy/base/service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: teckids-website
+  namespace: teckids-website
+spec:
+  ports:
+  - name: http
+    port: 80
+    protocol: TCP
+    targetPort: 80
+  selector:
+    app: teckids-website
+  sessionAffinity: None
+  type: ClusterIP
diff --git a/deploy/overlays/production/ingress_redirects.yaml b/deploy/overlays/production/ingress_redirects.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3cab142af933fed97d74aaa24ab112af1965c8df
--- /dev/null
+++ b/deploy/overlays/production/ingress_redirects.yaml
@@ -0,0 +1,91 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  annotations:
+    cert-manager.io/cluster-issuer: letsencrypt-prod
+    kubernetes.io/tls-acme: "true"
+  name: teckids-website-redirects
+  namespace: teckids-website
+spec:
+  ingressClassName: nginx
+  rules:
+  - host: www.teckids.org
+    http:
+      paths:
+      - backend:
+          service:
+            name: teckids-website
+            port:
+              number: 80
+        path: /
+        pathType: Prefix
+  - host: hacknfun.camp
+    http:
+      paths:
+      - backend:
+          service:
+            name: teckids-website
+            port:
+              number: 80
+        path: /
+        pathType: Prefix
+  - host: www.hacknfun.camp
+    http:
+      paths:
+      - backend:
+          service:
+            name: teckids-website
+            port:
+              number: 80
+        path: /
+        pathType: Prefix
+  - host: hacknsun.camp
+    http:
+      paths:
+      - backend:
+          service:
+            name: teckids-website
+            port:
+              number: 80
+        path: /
+        pathType: Prefix
+  - host: www.hacknsun.camp
+    http:
+      paths:
+      - backend:
+          service:
+            name: teckids-website
+            port:
+              number: 80
+        path: /
+        pathType: Prefix
+  - host: schul-frei.dev
+    http:
+      paths:
+      - backend:
+          service:
+            name: teckids-website
+            port:
+              number: 80
+        path: /
+        pathType: Prefix
+  - host: www.schul-frei.dev
+    http:
+      paths:
+      - backend:
+          service:
+            name: teckids-website
+            port:
+              number: 80
+        path: /
+        pathType: Prefix
+  tls:
+  - hosts:
+    - www.teckids.org
+    - hacknfun.camp
+    - www.hacknfun.camp
+    - hacknsun.camp
+    - www.hacknsun.camp
+    - schul-frei.dev
+    - www.schul-frei.dev
+    secretName: teckids-website-redirects-tls
diff --git a/deploy/overlays/production/kustomization.yaml b/deploy/overlays/production/kustomization.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3c22f836f3b8c0cf42cb078ebd0ccf0557af81ba
--- /dev/null
+++ b/deploy/overlays/production/kustomization.yaml
@@ -0,0 +1,24 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+commonAnnotations:
+  source-repository: https://edugit.org/Teckids/team-pr/teckids.org
+namespace: teckids-website
+patches:
+- patch: |-
+    - op: replace
+      path: /spec/rules/0/host
+      value: teckids.org
+    - op: replace
+      path: /spec/tls/0/hosts/0
+      value: teckids.org
+  target:
+    kind: Ingress
+    name: teckids-website-caddy
+resources:
+- ../../base
+- ingress_redirects.yaml
+labels:
+- includeSelectors: true
+  pairs:
+    app: teckids-website
+    environment: production
diff --git a/deploy/overlays/review/kustomization.yaml b/deploy/overlays/review/kustomization.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..07e58d7eca10d24f8121304f528d76e08b88824b
--- /dev/null
+++ b/deploy/overlays/review/kustomization.yaml
@@ -0,0 +1,26 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+commonAnnotations:
+  source-repository: https://edugit.org/Teckids/team-pr/teckids.org
+namespace: teckids-website
+patches:
+- patch: |-
+    - op: replace
+      path: /spec/rules/0/host
+      value: __REVIEW_NAME__.review.teckids.org
+    - op: replace
+      path: /spec/tls/0/hosts/0
+      value: __REVIEW_NAME__.review.teckids.org
+    - op: replace
+      path: /spec/tls/0/secretName
+      value: __REVIEW_NAME__-website-tls
+  target:
+    kind: Ingress
+    name: teckids-website-caddy
+resources:
+- ../../base
+labels:
+- includeSelectors: true
+  pairs:
+    app: teckids-website
+    environment: __REVIEW_NAME__