diff --git a/.ansible-lint b/.ansible-lint
new file mode 100644
index 0000000000000000000000000000000000000000..b5f26cad5fbaa942e5fcf9873ba285aee4241756
--- /dev/null
+++ b/.ansible-lint
@@ -0,0 +1,23 @@
+exclude_paths:
+  - roles
+  - .tox
+  - .venv
+
+parseable: true
+
+quiet: false
+
+skip_list:
+  - '201'  # Trailing whitespace
+  - '204'  # Lines should be no longer than 160 chars
+  - '206'  # Variables should have spaces before and after: {{ var_name }}'
+  - '208'  # File permissions not mentioned
+  - '301'  # Commands should not change things if nothing needs doing'
+  - '305'  # Use shell only when shell functionality is required'
+  - '306'  # Shells that use pipes should set the pipefail option'
+  - '502'  # All tasks should be named
+  - '505'  # Referenced missing file
+
+use_default_rules: true
+
+verbosity: 1
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fc05dfe9d019dbd8083c63194b1f659dca8027f7
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,33 @@
+---
+name: Run Linters
+on:
+  - push
+  - pull_request
+jobs:
+  linters:
+    name: Run Linters
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-python@v2
+        with:
+          python-version: "3.6"
+
+      - name: Run ansible-lint
+        uses: ansible/ansible-lint-action@master
+        with:
+          targets: |
+            tests/*.yml
+            tests/*/*.yml
+            tests/*/*/*.yml
+            playbooks/*.yml
+            playbooks/*/*.yml
+        env:
+          ANSIBLE_MODULE_UTILS: plugins/module_utils
+          ANSIBLE_LIBRARY: plugins/modules
+
+      - name: Run yaml-lint
+        uses: ibiqlik/action-yamllint@v1
+
+      - name: Run Python linters
+        uses: rjeffman/python-lint-action@master
diff --git a/.yamllint b/.yamllint
new file mode 100644
index 0000000000000000000000000000000000000000..3671e728732701929e1201c7c2f8ba0a307ae4a6
--- /dev/null
+++ b/.yamllint
@@ -0,0 +1,28 @@
+---
+ignore: |
+  /.tox/
+  /.venv/
+  /.github/
+
+extends: default
+
+rules:
+  braces:
+    max-spaces-inside: 1
+    level: error
+  brackets:
+    max-spaces-inside: 1
+    level: error
+  truthy:
+    allowed-values: ["yes", "no", "true", "false", "True", "False"]
+    level: error
+  # Disabled rules
+  document-start: disable
+  indentation: disable
+  line-length: disable
+  colons: disable
+  empty-lines: disable
+  comments: disable
+  comments-indentation: disable
+  trailing-spaces: disable
+  new-line-at-end-of-file: disable
diff --git a/molecule/resources/playbooks/prepare-build.yml b/molecule/resources/playbooks/prepare-build.yml
index d65236990d75f241286f4a2c1d4029beff5e2d16..41f513d4f2dac98f488d01d66363bef3ff1faca0 100644
--- a/molecule/resources/playbooks/prepare-build.yml
+++ b/molecule/resources/playbooks/prepare-build.yml
@@ -11,7 +11,7 @@
   - name: Ensure nss package is updated
     package:
       name: nss
-      state: latest
+      state: latest  # noqa 403
 
   - include_role:
       name: ipaserver
diff --git a/setup.cfg b/setup.cfg
index d87f6f06d4666a885b32bdfc3808c793dc4b5f4b..4d60e31fa33eda466101920e0db8757e259eeb95 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -22,7 +22,8 @@ data_files =
     /usr/share/ansible/roles/ipareplica = roles/ipareplica/*
 
 [flake8]
-extend-ignore = E203
+extend-ignore = E203, D1, D212, D203, D400, D401
+exclude = .git,__pycache__,.tox,.venv
 per-file-ignores =
     plugins/*:E402
     roles/*:E402
diff --git a/tests/azure/azure-pipelines.yml b/tests/azure/azure-pipelines.yml
index db3d52f502b012d2ce8836bf7facf191b3664065..d8914d96b1569c36d42143eb5ec834d71dd84a6e 100644
--- a/tests/azure/azure-pipelines.yml
+++ b/tests/azure/azure-pipelines.yml
@@ -18,27 +18,6 @@ pool:
   vmImage: 'ubuntu-18.04'
 
 stages:
-- stage: Linters
-  jobs:
-  - job: RunLinters
-    displayName: Run Linters
-    steps:
-    - task: UsePythonVersion@0
-      inputs:
-        versionSpec: '3.6'
-
-    - script: python -m pip install --upgrade pip setuptools wheel
-      displayName: Install tools
-
-    - script: pip install pydocstyle flake8
-      displayName: Install dependencies
-
-    - script: flake8 .
-      displayName: Run flake8 checks
-
-    - script: pydocstyle .
-      displayName: Verify docstings
-
 - stage: Centos7
   dependsOn: []
   jobs:
diff --git a/utils/lint_check.sh b/utils/lint_check.sh
new file mode 100755
index 0000000000000000000000000000000000000000..4970026687d693953fda6e7937d711cf93b0f0b7
--- /dev/null
+++ b/utils/lint_check.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+topdir=`dirname $(dirname $0)`
+
+flake8 .
+pydocstyle .
+
+ANSIBLE_LIBRARY=${ANSIBLE_LIBRARY:-"${topdir}/plugins/modules"}
+ANSIBLE_MODULE_UTILS=${ANSIBLE_MODULE_UTILS:-"${topdir}/plugins/module_utils"}
+
+export ANSIBLE_LIBRARY ANSIBLE_MODULE_UTILS
+
+yaml_dirs=(
+    "${topdir}/tests/*.yml"
+    "${topdir}/tests/*/*.yml"
+    "${topdir}/tests/*/*/*.yml"
+    "${topdir}/playbooks/*.yml"
+    "${topdir}/playbooks/*/*.yml"
+    "${topdir}/molecule/*/*.yml"
+    "${topdir}/molecule/*/*/*.yml"
+)
+
+ansible-lint --force-color ${yaml_dirs[@]}
+
+yamllint -f colored ${yaml_dirs[@]}