diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1f0ae5373d2dfb2efde4317e8b6f1f1d56062bc6
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,6 @@
+---
+include:
+  - project: shore/ci-templates
+    file: templates/pre-commit.yml
+  - project: shore/ci-templates
+    file: templates/pre-commit-repo.yml
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3afd12fbbc0ef0afc2a3e40cb21df26bd48c4d36
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,105 @@
+---
+repos:
+  - repo: https://github.com/pre-commit/pre-commit-hooks.git
+    rev: v4.0.1
+    hooks:
+      - id: check-added-large-files
+      - id: check-executables-have-shebangs
+      - id: check-merge-conflict
+      - id: check-shebang-scripts-are-executable
+      - id: check-yaml
+      - id: detect-private-key
+      - id: end-of-file-fixer
+      - id: trailing-whitespace
+
+  - repo: https://github.com/codespell-project/codespell.git
+    rev: v2.1.0
+    hooks:
+      - id: codespell
+
+  - repo: https://github.com/Yelp/detect-secrets.git
+    rev: v1.1.0
+    hooks:
+      - id: detect-secrets
+
+  - repo: https://gitlab.com/devopshq/gitlab-ci-linter
+    rev: v1.0.3
+    hooks:
+      - id: gitlab-ci-linter
+        args:
+          - "--server"
+          - https://git.shore.co.il
+
+  - repo: https://github.com/amperser/proselint.git
+    rev: 0.10.2
+    hooks:
+      - id: proselint
+        types: [plain-text]
+        exclude: LICENSE
+
+  - repo: https://github.com/adrienverge/yamllint.git
+    rev: v1.26.3
+    hooks:
+      - id: yamllint
+
+  - repo: https://github.com/executablebooks/mdformat.git
+    rev: 0.7.9
+    hooks:
+      - id: mdformat
+
+  - repo: https://github.com/ambv/black.git
+    rev: 21.8b0
+    hooks:
+      - id: black
+        args:
+          - |
+              --line-length=79
+
+  - repo: https://github.com/PyCQA/prospector.git
+    rev: 1.5.1b0
+    hooks:
+      - id: prospector
+        args:
+          - |-
+            --max-line-length=79
+          - |-
+            --with-tool=bandit
+          - |-
+            --without-tool=pep257
+          - |-
+            --doc-warnings
+          - |-
+            --test-warnings
+          - |-
+            --full-pep8
+          - |-
+            --strictness=high
+          - |-
+            --no-autodetect
+        additional_dependencies:
+          - bandit
+
+  - repo: https://gitlab.com/pycqa/flake8.git
+    rev: 3.9.2
+    hooks:
+      - id: flake8
+        args:
+          - |-
+            --doctests
+        additional_dependencies:
+          - flake8-bugbear
+
+  - repo: https://github.com/pre-commit/pre-commit.git
+    rev: v2.15.0
+    hooks:
+      - id: validate_manifest
+
+  - repo: https://git.shore.co.il/nimrod/shell-pre-commit.git
+    rev: v0.6.0
+    hooks:
+      - id: shell-lint
+
+  - repo: https://github.com/shellcheck-py/shellcheck-py.git
+    rev: v0.7.2.1
+    hooks:
+      - id: shellcheck
diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..ece3354f5129b622b797daa04a272d84de6c587a
--- /dev/null
+++ b/.pre-commit-hooks.yaml
@@ -0,0 +1,72 @@
+---
+- id: shell-validate
+  name: Lint shell scripts
+  description: Run /bin/sh -en against shell scripts.
+  language: script
+  entry: ./hooks/shell-validate
+  types: [shell]
+  minimum_pre_commit_version: 0.15.0 #  Because of types.
+
+- id: ansible-syntax-check
+  name: Syntax check Ansible playbooks
+  description: Check Ansible playbooks for syntax errors.
+  language: python
+  entry: ansible-playbook
+  files: playbook\.yml
+  types: [yaml]
+  args: ['--inventory=localhost,', '--syntax-check']
+
+- id: ansible-vault-check
+  name: Verify vaulted files
+  description: Verify that Ansible Vault files are vaulted.
+  language: pygrep
+  files: vault
+  entry: |-
+      ANSIBLE_VAULT
+
+- id: docker-compose
+  name: docker-compose config
+  description: Validate the Docker Compose file using docker-compose config
+  minimum_pre_commit_version: '0.18.0'
+  language: python
+  entry: docker-compose-validate
+  files: docker-compose
+  types: [yaml]
+
+- id: terraform-fmt
+  name: Format Terraform files
+  description: Format Terraform files using terraform fmt
+  language: python
+  types: [terraform]
+  entry: terraform-fmt
+
+- id: terraform-validate
+  name: Validate Terraform modules
+  description: Validate Terraform modules using terraform validate
+  language: python
+  types: [terraform]
+  entry: terraform-validate
+
+- id: poetry-check
+  name: poetry check
+  description: Validate pyproject.toml files using Poetry
+  language: python
+  entry: poetry-check
+  types: [toml]
+  files: pyproject
+
+- id: branch-merge-conflicts
+  name: branch merge conflicts
+  description: Checks for merge conflicts with a specific branch.
+  language: script
+  entry: ./hooks/branch-merge-conflicts
+  pass_filenames: false
+  always_run: true
+
+- id: pip-outdated
+  name: pip-outdated
+  description: Find outdated dependencies in your requirements files.
+  language: python
+  entry: pip-outdated
+  args: ['--verbose']
+  files: 'requirements.*\.txt$'
diff --git a/README.md b/README.md
index aed7be1ebc3c289021232abc543836ea51ecb0dd..9b608bdf94654025e36d6d92d912c3155f5a538b 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,23 @@
 # pre-commit hooks
 
-A collection of [pre-commit](https://pre-commit.com/) hooks. 
+A collection of [pre-commit](https://pre-commit.com/) hooks.
 
 ## Example .pre-commit-config.yaml
 
-
 ```yaml
 ---
 - repo: https://git.shore.co.il/nimrod/pre-commit-hooks.git
-  rev: 0.1.0
+  rev: 0.1.0  # Check for the latest tag or run pre-commit autoupdate.
   hooks:
+    - id: shell-validate
+    - id: ansible-syntax-check
+    - id: ansible-vault-check
+    - id: docker-compose
+    - id: terraform-fmt  # uses the installed system terraform.
+    - id: terraform-validate  # uses the installed system terraform.
+    - id: poetry-check
+    - id: branch-merge-conflict
+    - id: pip-outdated
 ```
 
 ## License
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000000000000000000000000000000000000..8acdd82b765e8e0b8cd8787f7f18c7fe2ec52493
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.0.1
diff --git a/hooks/__init__.py b/hooks/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/hooks/branch-merge-conflicts b/hooks/branch-merge-conflicts
new file mode 100755
index 0000000000000000000000000000000000000000..7ba009be93aa19a6971ccaf6c6f1d5e8a1fe078e
--- /dev/null
+++ b/hooks/branch-merge-conflicts
@@ -0,0 +1,14 @@
+#!/bin/sh
+set -eu
+
+default_branch="$(git symbolic-ref refs/remotes/origin/HEAD | cut -d/ -f4)"
+dest="${1:-$default_branch}"
+current="$(git symbolic-ref --short HEAD)" || exit 0  # Detached head.
+
+[ "$current" != "$dest" ] || exit 0
+
+patch="$(git format-patch "$(git merge-base HEAD "$dest")..$dest" --stdout)"
+
+[ "$patch" != "" ] || exit 0
+
+echo "$patch" | git apply --check -
diff --git a/hooks/docker_compose_validate.py b/hooks/docker_compose_validate.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef9088fc634e61275c0a55cbe0cf2040d5fa4637
--- /dev/null
+++ b/hooks/docker_compose_validate.py
@@ -0,0 +1,24 @@
+"""Validate Docker Compose files."""
+
+import argparse
+import pathlib
+import sys
+import hooks.utils
+
+
+def main():
+    """Main entrypoint."""
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument("file", nargs="+", type=pathlib.Path)
+    args = parser.parse_args()
+    hooks.utils.check_executable("docker-compose")
+    return hooks.utils.bulk_check(
+        lambda x: hooks.utils.check_file(
+            ["docker-compose", "--file", x, "config"], file=x
+        ),
+        args.file,
+    )
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/hooks/poetry_check.py b/hooks/poetry_check.py
new file mode 100644
index 0000000000000000000000000000000000000000..942c99e565be0233bd1ec6f7af62f05fede811b9
--- /dev/null
+++ b/hooks/poetry_check.py
@@ -0,0 +1,22 @@
+"""Validate Docker Compose files."""
+
+import argparse
+import pathlib
+import sys
+import hooks.utils
+
+
+def main():
+    """Main entrypoint."""
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument("file", nargs="+", type=pathlib.Path)
+    args = parser.parse_args()
+    hooks.utils.check_executable("poetry")
+    return hooks.utils.bulk_check(
+        lambda x: hooks.utils.check_dir(["poetry", "check"], dir=x),
+        hooks.utils.unique_directories(args.file),
+    )
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/hooks/shell-validate b/hooks/shell-validate
new file mode 100755
index 0000000000000000000000000000000000000000..ac875715a3b13900b985eeecd16736909b6a8c45
--- /dev/null
+++ b/hooks/shell-validate
@@ -0,0 +1,7 @@
+#!/bin/sh
+set -eu
+for filename in "$@"
+do
+    /usr/bin/env -i /bin/sh -en "$filename" || failed=1
+done
+[ "${failed:-0}" -eq 0 ]
diff --git a/hooks/terraform_fmt.py b/hooks/terraform_fmt.py
new file mode 100644
index 0000000000000000000000000000000000000000..e6cf375746a52e1f0ab964999fd37f5ae3a9ad57
--- /dev/null
+++ b/hooks/terraform_fmt.py
@@ -0,0 +1,25 @@
+"""Format Terraform modules."""
+
+import argparse
+import os
+import pathlib
+import sys
+import hooks.utils
+
+
+def main():
+    """Main entrypoint."""
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument("file", nargs="+", type=pathlib.Path)
+    args = parser.parse_args()
+    hooks.utils.check_executable("terraform")
+    os.putenv("TF_INPUT", "0")
+    os.putenv("TF_IN_AUTOMATION", "1")
+    return hooks.utils.bulk_check(
+        lambda x: hooks.utils.check_file(["terraform", "fmt", "-diff", x]),
+        hooks.utils.unique_directories(args.file),
+    )
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/hooks/terraform_validate.py b/hooks/terraform_validate.py
new file mode 100644
index 0000000000000000000000000000000000000000..abf921ee2516c0f7f92edc9f5098968e0b1fcfdb
--- /dev/null
+++ b/hooks/terraform_validate.py
@@ -0,0 +1,44 @@
+"""Validate Terraform modules."""
+
+import argparse
+import os
+import pathlib
+import sys
+import hooks.utils
+
+
+def checker():
+    def check(directory):
+        if (
+            hooks.utils.check(
+                ["terraform", "init", "-backend=false"], directory=directory
+            )
+            > 0
+        ):
+            return 1
+        if (
+            hooks.utils.check(["terraform", "validate"], directory=directory)
+            > 0
+        ):
+            return 1
+
+        return 0
+
+    return check
+
+
+def main():
+    """Main entrypoint."""
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument("file", nargs="+", type=pathlib.Path)
+    args = parser.parse_args()
+    hooks.utils.check_executable("terraform")
+    os.putenv("TF_INPUT", "0")
+    os.putenv("TF_IN_AUTOMATION", "1")
+    return hooks.utils.bulk_check(
+        checker, hooks.utils.unique_directories(args.file)
+    )
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/hooks/utils.py b/hooks/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..b88db640c83cfb385b7b7d04da34f8330ffda809
--- /dev/null
+++ b/hooks/utils.py
@@ -0,0 +1,90 @@
+"""Utilities for Python hooks.
+
+Mainly, executing external processes.
+"""
+
+import contextlib
+import os
+import pathlib
+import shutil
+import subprocess  # nosec
+import sys
+
+
+def unique_directories(files):
+    """Returns a list of directories (pathlib.Path objects) for the files
+    passed without repetitions."""
+    return list({pathlib.Path(x).parent for x in files})
+
+
+@contextlib.contextmanager
+def chdir(path):
+    """Context manager for changing the working directory.
+
+    >>> import os
+    >>> os.chdir("/")
+    >>> os.getcwd()
+    '/'
+    >>> with chdir("/tmp"):
+    ...     assert os.getcwd() == "/tmp"
+    ...
+    >>> assert os.getcwd() == "/"
+    """
+    cwd = os.getcwd()
+    os.chdir(path)
+    yield
+    os.chdir(cwd)
+
+
+def check_executable(executable):
+    """Checks if an executable exists, logs and exits otherwise."""
+    if shutil.which(executable) is None:
+        print(f"{executable} is not in the PATH.", file=sys.stderr)
+        sys.exit(1)
+
+
+def run(args):
+    """Wrapper for subprocess.run."""
+    return subprocess.run(  # nosec
+        args,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.STDOUT,
+        universal_newlines=True,
+        check=False,
+    )
+
+
+def check_file(args, file=None):
+    """A simple check for a file, may be used to build more complex checks."""
+    proc = run(args)
+    if proc.returncode > 0:
+        if file is not None:
+            print(f"In file {file}:")
+        print(proc.stdout)
+    return proc.returncode
+
+
+def check_directory(args, directory):
+    "A simple check for a directory, may be used to build more complex checks."
+    with chdir(directory):
+        proc = run(args)
+        if proc.returncode > 0:
+            print(f"In {directory}:")
+            print(proc.stdout)
+        return proc.returncode
+
+
+def bulk_check(checker, items):
+    """Bulk check files.
+
+    Some programs can only accept a single file or directory to process at a
+    time. This function receives a function that returns the check function and
+    list to go through. The function returns 0 if all checks returned 0 or 1
+    otherwise.
+    """
+    returncode = 0
+    for item in items:
+        check = checker(item)
+        if check() > 0:
+            returncode = 1
+    return returncode
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae225c3e13af1d1f95b680ee04b5bab3496399f3
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,26 @@
+from setuptools import setup
+
+with open("VERSION", "r", encoding="utf-8") as fh:
+    VERSION = fh.read().strip()
+
+setup(
+    name="shore-co-il-pre-commit-hooks",
+    url="https://git.shore.co.il/nimrod/pre-commit-hooks",
+    author="Nimrod Adar",
+    author_email="nimrod@shore.co.il",
+    version=VERSION,
+    install_requires=[
+        "ansible>=4",
+        "docker-compose>=1.20",
+        "pip-outdated",
+        "poetry",
+    ],
+    entry_points={
+        "console_scripts": [
+            "docker-compose-validate=hooks.docker_compose_validate:main",
+            "terraform-validate=hooks.terraform_validate:main",
+            "terraform-fmt=hooks.terraform_fmt:main",
+            "poetry-check=hook.poetry_check:main",
+        ]
+    },
+)