diff --git a/.azure/steps.yml b/.azure/steps.yml
deleted file mode 100644
index f2467bd17c6944c84517cbb9a48b8d0bc7afdfb9..0000000000000000000000000000000000000000
--- a/.azure/steps.yml
+++ /dev/null
@@ -1,77 +0,0 @@
-steps:
-  - task: NodeTool@0
-    inputs:
-      versionSpec: '$(nodeVersion)'
-    displayName: 'Install Node.js'
-
-  - task: UsePythonVersion@0
-    inputs:
-      versionSpec: '$(pythonVersion)'
-    displayName: 'Install Python'
-
-  - bash: python -m pip install --upgrade pip
-    displayName: 'Install python tools'
-
-  - bash: |
-      set -e
-      git config --global user.email 'bot@renovateapp.com'
-      git config --global user.name  'Renovate Bot'
-      node --version
-      python --version
-      yarn --version
-      pip --version
-    displayName: 'Init platform'
-
-  - checkout: self
-    fetchDepth: 5
-    path: renovate
-
-  - task: Cache@2
-    inputs:
-      key: yarn_cache | $(Agent.OS) | $(Build.SourcesDirectory)/yarn.lock
-      path: $(YARN_CACHE_FOLDER)
-    displayName: Cache Yarn packages
-    continueOnError: true
-
-  - task: Cache@2
-    inputs:
-      key: yarn | "$(nodeVersion)" | $(Agent.OS) | $(Build.SourcesDirectory)/yarn.lock
-      path: $(Build.SourcesDirectory)/node_modules
-    displayName: Cache node_modules
-    continueOnError: true
-
-  - bash: |
-      set -e
-      yarn install --frozen-lockfile --link-duplicates
-      pip install --user -r requirements.txt
-    displayName: 'Installing Dependencies'
-
-  - bash: |
-      set -e
-      yarn lint
-      yarn test-schema
-      yarn type-check
-    displayName: 'Lint'
-
-  - bash: yarn build
-    displayName: 'Build'
-
-  - bash: yarn jest --maxWorkers=2 --ci --reporters=default --reporters=jest-junit --coverageReporters=text-summary --coverageReporters=cobertura
-    displayName: 'Unit Tests'
-
-  - task: PublishTestResults@2
-    inputs:
-      testResultsFiles: 'junit.xml'
-      testRunTitle: 'CI Tests $(Agent.OS)'
-    displayName: 'Publish test results'
-    condition: succeededOrFailed()
-
-  - task: PublishCodeCoverageResults@1
-    inputs:
-      codeCoverageTool: Cobertura
-      summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/*coverage.xml'
-    displayName: 'Publish coverage results'
-    condition: succeededOrFailed()
-
-  - bash: yarn test-e2e
-    displayName: 'E2E Tests'
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0f23c23e86a4518156433700e642375f6d34db18
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,103 @@
+name: Test CI
+
+on:
+  push:
+    branches:
+      - master
+
+  pull_request:
+
+env:
+  CI: true
+
+jobs:
+  test:
+    name: test
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      max-parallel: 4
+      matrix:
+        # linux tests run by circle-ci
+        os: [macos-latest, windows-latest, ubuntu-latest]
+        node-version: [10.x, 12.x]
+        python-version: [3.8]
+
+    steps:
+      - name: Set up Node.js ${{ matrix.node-version }}
+        uses: actions/setup-node@v1
+        with:
+          node-version: ${{ matrix.node-version }}
+
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v1
+        with:
+          python-version: ${{ matrix.python-version }}
+
+      - name: Init platform
+        id: init
+        shell: bash
+        run: |
+          echo "::set-output name=yarn_cache::$(yarn cache dir)"
+          python -c "from pip._internal.locations import USER_CACHE_DIR; print('::set-output name=pip_cache::' + USER_CACHE_DIR)"
+          git config --global core.autocrlf false
+          git config --global core.symlinks true
+          git config --global user.email 'bot@renovateapp.com'
+          git config --global user.name  'Renovate Bot'
+          node --version
+          python --version
+          yarn --version
+          pip --version
+
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 1
+
+      - name: Cache Yarn packages
+        uses: actions/cache@v1
+        with:
+          path: ${{ steps.init.outputs.yarn_cache }}
+          key: ${{ runner.os }}-yarn_cache-${{ hashFiles('**/yarn.lock') }}
+          restore-keys: |
+            ${{ runner.os }}-yarn_cache-
+
+      - name: Cache node_modules
+        uses: actions/cache@v1
+        with:
+          path: node_modules
+          key: ${{ runner.os }}-${{ matrix.node-version }}-yarn-${{ hashFiles('**/yarn.lock') }}
+          restore-keys: |
+            ${{ runner.os }}-${{ matrix.node-version }}-yarn-
+
+      - name: Cache pip packages
+        uses: actions/cache@v1
+        with:
+          path: ${{ steps.init.outputs.pip_cache }}
+          key: ${{ runner.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('**/requirements.txt') }}
+          restore-keys: |
+            ${{ runner.os }}-${{ matrix.python-version }}-pip-
+
+      - name: Installing dependencies
+        shell: bash
+        run: |
+          python -m pip install --upgrade pip
+          yarn install --frozen-lockfile --link-duplicates
+          pip install --user -r requirements.txt
+
+      - name: Lint
+        run: yarn lint
+
+      - name: Test schema
+        run: yarn test-schema
+
+      - name: Type check
+        run: yarn type-check
+
+      - name: Build
+        run: yarn build
+
+      - name: Unit tests
+        run: yarn jest --maxWorkers=2 --ci --coverageReporters=text-summary
+
+      - name: E2E Test
+        run: yarn test-e2e
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
deleted file mode 100644
index 380fcbf87889fa383e0e7ac9c5b8bd4ac7f74af6..0000000000000000000000000000000000000000
--- a/azure-pipelines.yml
+++ /dev/null
@@ -1,59 +0,0 @@
-trigger:
-  batch: true
-  branches:
-    include:
-      - 'master'
-pr:
-  autoCancel: true
-  branches:
-    include:
-      - '*'
-
-variables:
-  YARN_CACHE_FOLDER: $(Pipeline.Workspace)/.yarn
-  CI: true
-  Agent.Source.Git.ShallowFetchDepth: 1
-
-jobs:
-  - job: 'Windows'
-    strategy:
-      matrix:
-        Node10 Py3.8:
-          nodeVersion: '10.x'
-          pythonVersion: '3.8'
-        Node12 Py3.8:
-          nodeVersion: '12.x'
-          pythonVersion: '3.8'
-    pool:
-      vmImage: windows-2019
-    steps:
-      - bash: |
-          set -e
-          git config --global core.autocrlf false
-          git config --global core.symlinks true
-        displayName: 'Preserve LF endings and symbolic links on check out'
-
-      - template: .azure/steps.yml
-
-  - job: 'Linux'
-    strategy:
-      matrix:
-        Node10 Py3.8:
-          nodeVersion: '10.x'
-          pythonVersion: '3.8'
-    pool:
-      vmImage: ubuntu-18.04
-    steps:
-      - template: .azure/steps.yml
-
-  - job: 'MacOS'
-    strategy:
-      matrix:
-        Node10 Py3.8:
-          nodeVersion: '10.x'
-          pythonVersion: '3.8'
-    pool:
-      vmImage: macOS-10.13
-
-    steps:
-      - template: .azure/steps.yml