diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..6ca01def58822a40edcba574cd0d2efb48d05316
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,3 @@
+*
+!app.py
+!requirements.txt
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b26fcd74f6e4ddf13c4ba84434c8b7f677b7c0df
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,17 @@
+---
+include:
+  - project: shore/ci-templates
+    file: templates/pre-commit.yml
+  - project: shore/ci-templates
+    file: templates/docker.yml
+
+stages:
+  - test
+  - build
+  - deploy
+
+build:
+  extends: .docker-build-shore
+
+push:
+  extends: .docker-push
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..7b2ecc35f82333abcd29b57c6be44208e9f8ebf6
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,82 @@
+---
+repos:
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v3.4.0
+    hooks:
+      - id: check-executables-have-shebangs
+      - id: check-merge-conflict
+      - id: check-toml
+        files: Pipfile
+      - id: trailing-whitespace
+
+  - repo: https://github.com/Yelp/detect-secrets
+    rev: v0.14.3
+    hooks:
+      - id: detect-secrets
+
+  - repo: https://github.com/adrienverge/yamllint
+    rev: v1.25.0
+    hooks:
+      - id: yamllint
+
+  - repo: https://github.com/amperser/proselint/
+    rev: 0.10.2
+    hooks:
+      - id: proselint
+        types: [plain-text]
+        exclude: LICENSE
+
+  - repo: https://github.com/ambv/black
+    rev: 20.8b1
+    hooks:
+      - id: black
+        args:
+          - |
+              --line-length=79
+
+  - repo: https://github.com/PyCQA/prospector
+    rev: 1.3.1
+    hooks:
+      - id: prospector
+        args:
+          - |-
+            --max-line-length=79
+          - |-
+            --with-tool=pyroma
+          - |-
+            --with-tool=bandit
+          - |-
+            --without-tool=pep257
+          - |-
+            --doc-warnings
+          - |-
+            --test-warnings
+          - |-
+            --full-pep8
+          - |-
+            --strictness=high
+          - |-
+            --no-autodetect
+        additional_dependencies:
+          - bandit
+          - pyroma
+
+  - repo: https://gitlab.com/pycqa/flake8.git
+    rev: 3.8.4
+    hooks:
+      - id: flake8
+        args:
+          - |-
+            --doctests
+        additional_dependencies:
+          - flake8-bugbear
+
+  - repo: https://github.com/executablebooks/mdformat.git
+    rev: 0.5.3
+    hooks:
+      - id: mdformat
+
+  - repo: https://git.shore.co.il/nimrod/docker-pre-commit.git/
+    rev: v0.3.0
+    hooks:
+      - id: hadolint
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..dc26b8a04977ffc1d70847e43dcecd605990f0cf
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,9 @@
+FROM registry.hub.docker.com/library/python:3.9-alpine3.13
+WORKDIR /app
+COPY requirements.txt ./
+RUN pip install -r requirements.txt
+COPY * ./
+EXPOSE 8080
+ENV FORWARDED_ALLOW_IPS "*"
+HEALTHCHECK CMD wget --spider --quiet http://localhost:8080/ping --user-agent 'Docker Healthcheck' || exit 1
+CMD ["gunicorn", "--bind", "0.0.0.0:8080", "--log-file", "-", "--workers", "2", "app:app"]
diff --git a/README.md b/README.md
index b3a6c71fb0c40fd837ab5e3f529e552a99736488..13393b83b98b1717d61239ff3b9c83d6f1c2419a 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
 # Nextcloud notifier
 
+[![pipeline status](https://git.shore.co.il/nimrod/nextcloud-notifier/badges/master/pipeline.svg)](https://git.shore.co.il/nimrod/nextcloud-notifier/-/commits/master)
+
 A simple web service to push notifications to me without authentication.
 
 ## Rationale
diff --git a/app.py b/app.py
new file mode 100644
index 0000000000000000000000000000000000000000..171219caaffe3af6e9a6eee04718fa405669a431
--- /dev/null
+++ b/app.py
@@ -0,0 +1,16 @@
+"""Anonymous push notification service for Nextcloud."""
+# pylint: disable=import-error
+
+import os
+import docker
+from flask import Flask
+from ruamel.yaml import YAML
+
+
+app = Flask(__name__)
+
+
+@app.route("/ping")
+def ping():
+    """Healthcheck."""
+    return "pong"
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3107c49b69203d7d1a4e26911e02f899a52fae56
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+docker
+flask
+gunicorn
+ruamel.yaml