diff --git a/setup.cfg b/.flake8
similarity index 67%
rename from setup.cfg
rename to .flake8
index ad3574a48896e04dfa02a99eb69374985d954666..327f4114d76af564726e858a32d9cd9c058edd9e 100644
--- a/setup.cfg
+++ b/.flake8
@@ -1,7 +1,7 @@
 [flake8]
 max-line-length = 120
-exclude=build,.eggs,.git,src,arkindex/*/migrations/0001_initial.py,.cache
+exclude=build,.cache,.eggs,.git,src,arkindex/*/migrations/0001_initial.py
 # Flake8 ignores multiple errors by default;
 # the only interesting ignore is W503, which goes against PEP8.
 # See https://lintlyci.github.io/Flake8Rules/rules/W503.html
-ignore=W503
+ignore = E203,E501,W503
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d0083fdb560d67a9647b6d304c671d00538e58ab..09d23d6efcd896ebd46a0170ed842231e7d21dd5 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -42,11 +42,23 @@ backend-tests:
     - codecov
 
 backend-lint:
-  extends: .backend-setup
+  image: python:3
   stage: test
 
+  cache:
+    paths:
+      - .cache/pip
+      - .cache/pre-commit
+
+  variables:
+    PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
+    PRE_COMMIT_HOME: "$CI_PROJECT_DIR/.cache/pre-commit"
+
+  before_script:
+    - pip install pre-commit
+
   script:
-    - flake8
+    - pre-commit run -a
 
 backend-migrations:
   extends: .backend-setup
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..91d535dffa06e2a3dc253d91f7bb2f847dd6e2ff
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,14 @@
+repos:
+  - repo: https://gitlab.com/pycqa/flake8
+    rev: 3.8.3
+    hooks:
+      - id: flake8
+        additional_dependencies:
+          - 'flake8-copyright==0.2.2'
+          - 'flake8-debugger==3.1.0'
+  - repo: meta
+    hooks:
+      - id: check-useless-excludes
+
+default_language_version:
+  python: python3.7
diff --git a/README.md b/README.md
index 7239ce9d90f987a1d1326c7771d1e1f548f17d85..7ee405e3315145ec5eeb7a5527318de16401bcae 100644
--- a/README.md
+++ b/README.md
@@ -124,8 +124,23 @@ Once your code appears to be working on a local server, a few checks have to be
 * **Migrations:** Ensure that all migrations have been created by typing `./manage.py makemigrations`.
 * **Unit tests:** Run `./manage.py test` to perform unit tests.  
   Use `./manage.py test module_name` to perform tests on a single module, if you wish to spend less time waiting for all tests to complete.
-* **Code linting:** Type `flake8` inside the `backend/arkindex` directory. Our Flake8 settings should allow 120 characters per line instead of PEP8's 80.
 
+### Linting
+
+We use [pre-commit](https://pre-commit.com/) to check the Python source code syntax of this project.
+
+To be efficient, you should run pre-commit before committing (hence the name...).
+
+To do that, run once :
+
+```
+pip install pre-commit
+pre-commit install
+```
+
+The linting workflow will now run on modified files before committing, and may fix issues for you.
+
+If you want to run the full workflow on all the files: `pre-commit run -a`.
 ## Debugging tools
 
 Run `pip install ipython django-debug-toolbar django_extensions` to install all the available optional dev tools for the backend.
diff --git a/tests-requirements.txt b/tests-requirements.txt
index 59c491dd0bef9522e85086913e6bd7d9c7826bf8..1fafac3a6751c6658a1d9d23b713383572693d1a 100644
--- a/tests-requirements.txt
+++ b/tests-requirements.txt
@@ -1,4 +1,3 @@
-flake8>=3.8
 tripoli
 django-nose
 coverage