diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f92da7b142f506a53baa2e4028ac3e501c5919d9..5cce64e6565b848858b8c8c78540da1123f6aeb9 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,3 +2,55 @@
 include:
   - project: shore/ci-templates
     file: templates/pre-commit.yml
+
+stages:
+  - test
+  - build
+  - deploy
+
+# One day I may want to start tagging release and for that the TAG variable is
+# needed, but that would mean releasing ALL of the images when tagging (sort of
+# a periodic release). The images are not really related so for now TAG is
+# hard-coded to latest.
+variables:
+  TAG: latest
+
+.build:
+  stage: build
+  variables:
+    DOCKER_BUILDKIT: "1"
+  tags: &tags [ns4.shore.co.il]
+  script:
+    - docker build --pull --no-cache --iidfile iid "$IMAGE"
+  after_script:
+    - echo "HASH=$(cat iid)" > dot.env
+  artifacts:
+    reports:
+      dotenv: dot.env
+  rules: &rules
+    - if: $CI_PIPELINE_SOURCE == "schedule"
+    - if: $CI_PIPELINE_SOURCE == "push"
+      changes:
+        - $IMAGE/*
+        - $IMAGE/**/*
+
+.push:
+  stage: deploy
+  tags: *tags
+  script:
+    - docker tag "$HASH" "registry.shore.co.il/$IMAGE:$TAG"
+    - docker push "registry.shore.co.il/$IMAGE:$TAG"
+  rules: *rules
+
+build-cgit:
+  extends: .build
+  variables:
+    IMAGE: cgit
+
+push-cgit:
+  extends: .push
+  variables:
+    IMAGE: cgit
+  needs:
+    - job: build-cgit
+      artifacts: true
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 26e5e87d1cf5a62214459d77657f1da4c6211b5e..39aa265860f59f0fe48674768c0e9a8e5a7e6eb9 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -8,6 +8,7 @@ repos:
       - id: check-merge-conflict
       - id: check-symlinks
       - id: trailing-whitespace
+        exclude: .\.diff$
 
   - repo: https://github.com/Yelp/detect-secrets
     rev: v1.1.0
diff --git a/cgit/.dockerignore b/cgit/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..a954727123fc094640d5c2af445f87b791d66cea
--- /dev/null
+++ b/cgit/.dockerignore
@@ -0,0 +1,3 @@
+*
+!cgitrc
+!patch.diff
diff --git a/cgit/Dockerfile b/cgit/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..ab69b347a3c6895d7d6f82715911bef2afb77193
--- /dev/null
+++ b/cgit/Dockerfile
@@ -0,0 +1,40 @@
+FROM debian:buster-slim
+# hadolint ignore=DL3008,DL3015
+RUN apt-get update && \
+    DEBIAN_FRONTEND=noninteractive apt-get install -y \
+        apache2 \
+        cgit \
+        groff-base \
+        libcap2-bin \
+        patch \
+        python3-docutils \
+        python3-markdown \
+        python3-pygments \
+        wget \
+    && \
+    setcap CAP_NET_BIND_SERVICE=+ep /usr/sbin/apache2 && \
+    a2enmod cgid && \
+    a2enconf cgit && \
+    a2enmod status && \
+    install -d -o www-data -g www-data -m 755 /var/cache/cgit && \
+    install -d -o www-data -g www-data -m 755 /run/apache2 && \
+    install -d -o www-data -g www-data -m 755 /var/log/apache2 && \
+    ln -sf /dev/stdout /var/log/apache2/access.log && \
+    ln -sf /dev/stderr /var/log/apache2/error.log && \
+    ln -sf /dev/stdout /var/log/apache2/other_vhosts_access.log && \
+    rm -rf /tmp/* /var/tmp/* /var/lib/apt/lists/* /var/cache/apt/archives/*
+COPY --chown=root:root patch.diff /root/
+COPY --chown=root:root cgitrc /etc/
+ENV APACHE_RUN_DIR=/run/apache2 \
+    APACHE_LOG_DIR=/var/log/apache2 \
+    APACHE_RUN_USER=www-data \
+    APACHE_RUN_GROUP=www-data \
+    APACHE_PID_FILE=/run/apache2/apache2.pid
+RUN patch --strip 0 --verbose --directory /etc/apache2 --input /root/patch.diff && \
+    apache2 -t
+EXPOSE 80
+CMD [ "apache2", "-DFOREGROUND" ]
+VOLUME ["/srv/git"]
+USER "www-data"
+WORKDIR /var/www
+HEALTHCHECK CMD wget --spider --quiet http://localhost:80/cgit/ --user-agent 'Healthcheck' || exit 1
diff --git a/cgit/README.md b/cgit/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a4f4e4d46ca03d9e3429fea4b6ffb55659a0eb62
--- /dev/null
+++ b/cgit/README.md
@@ -0,0 +1,17 @@
+# cgit
+
+cgit container image.
+
+## Usage
+
+This container runs Apache that is configured with cgit at `/cgit`. It exposes
+port 80 and serves the repositories under `/srv/git`. The container runs as
+a limited user (`www-data`), so make sure to have the content of `/srv/git`
+readble by it. Also, if you wish to persist the cache, the location is
+`/var/cache/cgit`.
+
+## Example
+
+```
+docker -v '/srv/git:/srv/git:ro' -p '80:80' adarnimrod/cgit
+```
diff --git a/cgit/cgitrc b/cgit/cgitrc
new file mode 100644
index 0000000000000000000000000000000000000000..94620973ae4b4fa0b1e6969fe288a45c0d242600
--- /dev/null
+++ b/cgit/cgitrc
@@ -0,0 +1,43 @@
+#
+# cgit config
+# see cgitrc(5) for details
+
+about-filter=/usr/lib/cgit/filters/about-formatting.sh
+cache-size=2000
+css=/cgit-css/cgit.css
+enable-git-config=1
+favicon=/cgit-css/favicon.ico
+logo=/cgit-css/cgit.png
+readme=:README.md
+readme=:readme.md
+readme=:README.mkd
+readme=:readme.mkd
+readme=:README.rst
+readme=:readme.rst
+readme=:README.html
+readme=:readme.html
+readme=:README.htm
+readme=:readme.htm
+readme=:README.txt
+readme=:readme.txt
+readme=:README
+readme=:readme
+readme=:INSTALL.md
+readme=:install.md
+readme=:INSTALL.mkd
+readme=:install.mkd
+readme=:INSTALL.rst
+readme=:install.rst
+readme=:INSTALL.html
+readme=:install.html
+readme=:INSTALL.htm
+readme=:install.htm
+readme=:INSTALL.txt
+readme=:install.txt
+readme=:INSTALL
+readme=:install
+remove-suffix=1
+source-filter=/usr/lib/cgit/filters/syntax-highlighting.py
+
+# Needs to be last.
+scan-path=/srv/git/
diff --git a/cgit/patch.diff b/cgit/patch.diff
new file mode 100644
index 0000000000000000000000000000000000000000..7b9e2f813ee4247913ef4738bdf435796e191967
--- /dev/null
+++ b/cgit/patch.diff
@@ -0,0 +1,11 @@
+--- mods-available/status.conf	2019-04-03 00:13:44.000000000 +0300
++++ mods-available/status.conf	2019-11-09 19:59:09.642896703 +0200
+@@ -5,7 +5,7 @@
+ 
+ 	<Location /server-status>
+ 		SetHandler server-status
+-		Require local
++		#Require local
+ 		#Require ip 192.0.2.0/24
+ 	</Location>
+