diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8d09adde407c69b24da518d3c48a24fe4f7287d0..3637ceea1552b4e4f7ed72d9b470ad4c1533cc74 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,5 +1,7 @@
 ---
 include:
+  - project: shore/ci-stuff
+    file: templates/docker.yml
   - project: shore/ci-stuff
     file: templates/pre-commit.yml
   - project: shore/ci-stuff
@@ -13,17 +15,45 @@ default:
 AWS Terraform plan:
   extends: .tf_plan
   stage: test
-  #rules: &aws_tf_rules
-  #  - changes:
-  #      - ${TF_ROOT}/
   variables: &aws_tf_vars
     TF_ROOT: Terraform/AWS
+  rules: &tf_rules
+    - changes:
+        - $TF_ROOT/*
+        - $TF_ROOT/**/*
 
 AWS Terraform apply:
   extends: .tf_apply
   stage: deploy
-  #rules: *aws_tf_rules
+  rules: *tf_rules
   needs:
     - job: AWS Terraform plan
       artifacts: true
   variables: *aws_tf_vars
+
+web-proxy kodi build:
+  extends: .compose-build
+  tags: ["kodi.shore.co.il"]
+  variables:
+    WORKDIR: Compose/web-proxy/kodi
+  # rules: &compose-rules
+  #   - if: $CI_PIPELINE_SOURCE == "schedule"
+  #   - if: $CI_PIPELINE_SOURCE == "push"
+  #     changes:
+  #       - $WORKDIR/*
+  #       - $WORKDIR/**/*
+
+web-proxy kodi pull:
+  extends: .compose-pull
+  tags: ["kodi.shore.co.il"]
+  variables:
+    WORKDIR: Compose/web-proxy/kodi
+  # rules: *compose-rules
+
+web-proxy kodi run:
+  extends: .compose-run
+  tags: ["kodi.shore.co.il"]
+  variables:
+    WORKDIR: Compose/web-proxy/kodi
+  when: manual
+  # rules: *compose-rules
diff --git a/Compose/web-proxy/README.md b/Compose/web-proxy/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..0d1eaaad9d21360e13b3f66482d0ef6ab2db2677
--- /dev/null
+++ b/Compose/web-proxy/README.md
@@ -0,0 +1,4 @@
+# Web proxy
+
+Nginx proxy and SSL termination for web sites and services on different hosts.
+Each directory is in a different host.
diff --git a/Compose/web-proxy/kodi/.dockerignore b/Compose/web-proxy/kodi/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..380e2e62d48d3718eee6fb713bc578042f0ab6fd
--- /dev/null
+++ b/Compose/web-proxy/kodi/.dockerignore
@@ -0,0 +1,4 @@
+*
+!conf.d/
+!www/
+!snippets/
diff --git a/Compose/web-proxy/kodi/.env b/Compose/web-proxy/kodi/.env
new file mode 100644
index 0000000000000000000000000000000000000000..2f5dd33f1fa1c5ff48896989639d1af9a803ecab
--- /dev/null
+++ b/Compose/web-proxy/kodi/.env
@@ -0,0 +1 @@
+COMPOSE_PROJECT_NAME=web-proxy
diff --git a/Compose/web-proxy/kodi/Dockerfile b/Compose/web-proxy/kodi/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..521391c07be678f0d29f8329d64f8acc392478cf
--- /dev/null
+++ b/Compose/web-proxy/kodi/Dockerfile
@@ -0,0 +1,4 @@
+# hadolint ignore=DL3006
+FROM registry.shore.co.il/nginx
+COPY --chown=root:root conf.d/ /etc/nginx/conf.d/
+RUN nginx -t
diff --git a/Compose/web-proxy/kodi/conf.d/kodi.shore.co.il.conf b/Compose/web-proxy/kodi/conf.d/kodi.shore.co.il.conf
new file mode 100644
index 0000000000000000000000000000000000000000..6f380b93ff120c9ed7e42d53b917f7bdbb9fb335
--- /dev/null
+++ b/Compose/web-proxy/kodi/conf.d/kodi.shore.co.il.conf
@@ -0,0 +1,31 @@
+# vim: ft=nginx
+map $host $kodi { default 172.18.0.1; }
+
+server {
+    listen      80;
+    listen      [::]:80;
+    server_name kodi.shore.co.il;
+    include     snippets/robots-disallow-all.conf;
+    include     snippets/ads-txt.conf;
+    include     snippets/security-txt.conf;
+    include     snippets/www-acme-challenge.conf;
+    include     snippets/redirect-https.conf;
+}
+
+server {
+    listen      443 ssl http2;
+    listen      [::]:443 ssl http2;
+    server_name kodi.shore.co.il;
+    include     snippets/robots-disallow-all.conf;
+    include     snippets/ads-txt.conf;
+    include     snippets/security-txt.conf;
+    include     snippets/ssl-legacy.conf;
+    include     snippets/ldap-auth.conf;
+
+    location / {
+        proxy_pass              http://$kodi:8080;
+        proxy_http_version      1.1;
+        include                 snippets/proxy-headers.conf;
+        include                 snippets/websockets.conf;
+    }
+}
diff --git a/Compose/web-proxy/kodi/conf.d/library.shore.co.il.conf b/Compose/web-proxy/kodi/conf.d/library.shore.co.il.conf
new file mode 100644
index 0000000000000000000000000000000000000000..99d9ed9e0eea0118b75463065c533293f27864b8
--- /dev/null
+++ b/Compose/web-proxy/kodi/conf.d/library.shore.co.il.conf
@@ -0,0 +1,30 @@
+# vim: ft=nginx
+map $host $library { default transmission-webdav; }
+
+server {
+    listen      80;
+    listen      [::]:80;
+    server_name library.shore.co.il;
+    include     snippets/robots-disallow-all.conf;
+    include     snippets/ads-txt.conf;
+    include     snippets/security-txt.conf;
+    include     snippets/www-acme-challenge.conf;
+    include     snippets/redirect-https.conf;
+}
+
+server {
+    listen      443 ssl http2;
+    listen      [::]:443 ssl http2;
+    server_name library.shore.co.il;
+    include     snippets/robots-disallow-all.conf;
+    include     snippets/ads-txt.conf;
+    include     snippets/security-txt.conf;
+    include     snippets/ssl-legacy.conf;
+    include     snippets/ldap-auth.conf;
+
+    location / {
+        proxy_pass              http://$library:80;
+        proxy_http_version      1.1;
+        include                 snippets/proxy-headers.conf;
+    }
+}
diff --git a/Compose/web-proxy/kodi/conf.d/transmission.shore.co.il.conf b/Compose/web-proxy/kodi/conf.d/transmission.shore.co.il.conf
new file mode 100644
index 0000000000000000000000000000000000000000..0fb970f50d1e85dc9884fc9afbf5aba66065c479
--- /dev/null
+++ b/Compose/web-proxy/kodi/conf.d/transmission.shore.co.il.conf
@@ -0,0 +1,30 @@
+# vim: ft=nginx
+map $host $transmission { default transmission-daemon; }
+
+server {
+    listen      80;
+    listen      [::]:80;
+    server_name transmission.shore.co.il;
+    include     snippets/robots-disallow-all.conf;
+    include     snippets/ads-txt.conf;
+    include     snippets/security-txt.conf;
+    include     snippets/www-acme-challenge.conf;
+    include     snippets/redirect-https.conf;
+}
+
+server {
+    listen      443 ssl http2;
+    listen      [::]:443 ssl http2;
+    server_name transmission.shore.co.il;
+    include     snippets/robots-disallow-all.conf;
+    include     snippets/ads-txt.conf;
+    include     snippets/security-txt.conf;
+    include     snippets/ssl-legacy.conf;
+    include     snippets/ldap-auth.conf;
+
+    location / {
+        proxy_pass              http://$transmission:9091;
+        proxy_http_version      1.1;
+        include                 snippets/proxy-headers.conf;
+    }
+}
diff --git a/Compose/web-proxy/kodi/docker-compose.yml b/Compose/web-proxy/kodi/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..558fe80947a7e78ee16d034c85cfc48298053276
--- /dev/null
+++ b/Compose/web-proxy/kodi/docker-compose.yml
@@ -0,0 +1,46 @@
+---
+version: '3.5'
+services:
+  proxy:
+    build:
+      context: ./
+    # command: ["nginx", "-g", "daemon off;"]
+    hostname: &hostname kodi.shore.co.il
+    networks:
+      default:
+        aliases:
+          - *hostname
+          - jellyfin.shore.co.il
+          - library.shore.co.il
+          - transmission.shore.co.il
+    ports:
+      - '80:80'
+      - '443:443'
+    restart: always
+    volumes:
+      - '/var/www/www.shore.co.il/.well-known/acme-challenge:/var/www/www.shore.co.il/.well-known/acme-challenge:ro'
+      - '/var/ssl/site.key:/var/ssl/site.key:ro'
+      - '/var/ssl/site.crt:/var/ssl/site.crt:ro'
+      - '/var/ssl/dhparams:/var/ssl/dhparams:ro'
+
+  vouch:
+    environment:
+      OAUTH_AUTH_URL: https://nextcloud.shore.co.il/apps/oauth2/authorize
+      OAUTH_CALLBACK_URLS: https://vouch.shore.co.il/auth
+      OAUTH_CLIENT_ID: "${VOUCH_OAUTH_CLIENT_ID}"
+      # yamllint disable-line rule:line-length
+      OAUTH_CLIENT_SECRET: "${VOUCH_OAUTH_CLIENT_SECRET}"  # pragma: allowlist secret
+      OAUTH_PROVIDER: nextcloud
+      OAUTH_SCOPES: 'openid,email.profile'
+      OAUTH_TOKEN_URL: https://nextcloud.shore.co.il/apps/oauth2/api/v1/token
+      # yamllint disable-line rule:line-length
+      OAUTH_USER_INFO_URL: https://nextcloud.shore.co.il/ocs/v2.php/cloud/user?format=json
+      VOUCH_DOMAINS: shore.co.il
+      VOUCH_JWT_MAXAGE: 10080  # 1 week.
+      VOUCH_JWT_SECRET: "${VOUCH_JWT_SECRET}"  # pragma: allowlist secret
+    image: quay.io/vouch/vouch-proxy:alpine-0.36.0
+    restart: always
+
+networks:
+  default:
+    name: shore