diff --git a/.drone.yml b/.drone.yml
deleted file mode 100644
index b0f2050046b67ab39ada4d5736288960b9e5f5ae..0000000000000000000000000000000000000000
--- a/.drone.yml
+++ /dev/null
@@ -1,52 +0,0 @@
----
-kind: pipeline
-type: docker
-name: gcc_build
-
-steps:
-  - name: lint
-    image: vesoft/nebula-dev:ubuntu1804
-    commands:
-      - ./ci/test.sh lint
-
-  - name: gcc_build
-    pull: always
-    image: vesoft/nebula-dev:ubuntu1804
-    environment:
-      NEBULA_TEST_LOGS_DIR: /tmp/nebula-ci-logs
-      NEBULA_STORAGE_REPO_URL:
-        from_secret: NEBULA_STORAGE_REPO_URL
-      NEBULA_COMMON_REPO_URL:
-        from_secret: NEBULA_COMMON_REPO_URL
-    commands:
-      - ./ci/test.sh
-    depends_on:
-      - lint
-
----
-kind: pipeline
-type: docker
-name: clang_build
-
-steps:
-  - name: lint
-    image: vesoft/nebula-dev:ubuntu1804
-    commands:
-      - ./ci/test.sh lint
-
-  - name: clang_build
-    pull: always
-    image: vesoft/nebula-dev:ubuntu1804
-    environment:
-      NEBULA_TEST_LOGS_DIR: /tmp/nebula-ci-logs
-      NEBULA_STORAGE_REPO_URL:
-        from_secret: NEBULA_STORAGE_REPO_URL
-      NEBULA_COMMON_REPO_URL:
-        from_secret: NEBULA_COMMON_REPO_URL
-    commands:
-      - ./ci/test.sh prepare
-      - ./ci/test.sh clang
-      - ./ci/test.sh ctest
-      - ./ci/test.sh test
-    depends_on:
-      - lint
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e3ac97aaa3b3fa1c1b9c6a267a315a519e7034a7
--- /dev/null
+++ b/.github/workflows/pull_request.yml
@@ -0,0 +1,153 @@
+name: pull_request
+
+on:
+  pull_request:
+    types: [synchronize, reopened, labeled]
+    branches:
+      - master
+      - 'v[0-9]+.*'
+
+jobs:
+  lint:
+    name: lint
+    if: ${{ contains(github.event.pull_request.labels.*.name, 'ready-for-testing') }}
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        shell: bash
+    outputs:
+      num_source_files: ${{ steps.filter-source-files.outputs.num_source_files }}
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 2
+      - name: Cpplint
+        run: |
+          ln -snf $PWD/.linters/cpp/hooks/pre-commit.sh $PWD/.linters/cpp/pre-commit.sh
+          .linters/cpp/pre-commit.sh $(git --no-pager diff --diff-filter=d --name-only HEAD^ HEAD)
+      - name: Filter source files
+        id: filter-source-files
+        run: |
+          diff_commits=$(git log --oneline -n 1 | cut -d' ' -f1,5)
+          num_source_files=$(git --no-pager diff --name-only $diff_commits | grep '^src\|^CMakeLists.txt\|^cmake\|^.github/workflows' | wc -l)
+          echo "::set-output name=num_source_files::${num_source_files}"
+
+  build:
+    name: build
+    needs: lint
+    if: ${{ needs.lint.outputs.num_source_files != 0 }}
+    runs-on: self-hosted
+    defaults:
+      run:
+        shell: bash
+    strategy:
+      fail-fast: false
+      matrix:
+        os:
+          - centos7
+          - ubuntu1804
+        compiler:
+          - gcc-9.2
+          - clang-9
+        exclude:
+          - os: centos7
+            compiler: clang-9
+    container:
+      image: vesoft/nebula-dev:${{ matrix.os }}
+      env:
+        TOOLSET_DIR: /opt/vesoft/toolset/clang/9.0.0
+        CCACHE_DIR: /tmp/ccache/nebula-graph/${{ matrix.os }}-${{ matrix.compiler }}
+        CCACHE_MAXSIZE: 1G
+        PYPI_MIRROR: https://mirrors.aliyun.com/pypi/simple/
+      volumes:
+        - /tmp/ccache/nebula-graph/${{ matrix.os }}-${{ matrix.compiler }}:/tmp/ccache/nebula-graph/${{ matrix.os }}-${{ matrix.compiler }}
+        - /run/secrets/GITHUB_PAT:/run/secrets/GITHUB_PAT
+      options: --mount type=tmpfs,destination=/tmp/ccache/nebula-graph,tmpfs-size=1073741824 --cap-add=SYS_PTRACE
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 1
+      - name: Prepare environment
+        id: prepare
+        run: |
+          [ -d build/ ] && rm -rf build/* || mkdir -p build
+          pip3 install -U setuptools -i $PYPI_MIRROR
+          pip3 install -r tests/requirements.txt -i $PYPI_MIRROR
+          echo "::set-output name=pat::$(cat /run/secrets/GITHUB_PAT)"
+      - name: Checkout common
+        uses: actions/checkout@v2
+        with:
+          repository: ${{ github.repository_owner }}/nebula-common
+          token: ${{ steps.prepare.outputs.pat }}
+          path: modules/common
+      - name: Checkout storage
+        uses: actions/checkout@v2
+        with:
+          repository: ${{ github.repository_owner }}/nebula-storage
+          token: ${{ steps.prepare.outputs.pat }}
+          path: modules/storage
+      - name: CMake
+        run: |
+          case ${{ matrix.compiler }} in
+          gcc-*)
+            case ${{ matrix.os }} in
+            centos7)
+              # build with Release type
+              cmake \
+                  -DCMAKE_CXX_COMPILER=$TOOLSET_DIR/bin/g++ \
+                  -DCMAKE_C_COMPILER=$TOOLSET_DIR/bin/gcc \
+                  -DCMAKE_BUILD_TYPE=Release \
+                  -DENABLE_TESTING=on \
+                  -DENABLE_BUILD_STORAGE=on \
+                  -DMODULE_BUILDING_JOBS=$(nproc) \
+                  -B build
+              ;;
+            ubuntu1804)
+              # build with Debug type
+              cmake \
+                  -DCMAKE_CXX_COMPILER=$TOOLSET_DIR/bin/g++ \
+                  -DCMAKE_C_COMPILER=$TOOLSET_DIR/bin/gcc \
+                  -DCMAKE_BUILD_TYPE=Debug \
+                  -DENABLE_TESTING=on \
+                  -DENABLE_BUILD_STORAGE=on \
+                  -DMODULE_BUILDING_JOBS=$(nproc) \
+                  -B build
+              ;;
+            esac
+            ;;
+          clang-*)
+            # build with Sanitizer
+            cmake \
+                -DCMAKE_CXX_COMPILER=$TOOLSET_DIR/bin/clang++ \
+                -DCMAKE_C_COMPILER=$TOOLSET_DIR/bin/clang \
+                -DCMAKE_BUILD_TYPE=Debug \
+                -DENABLE_ASAN=on \
+                -DENABLE_TESTING=on \
+                -DENABLE_BUILD_STORAGE=on \
+                -DMODULE_BUILDING_JOBS=$(nproc) \
+                -B build
+            ;;
+          esac
+      - name: Make common
+        run: cmake --build modules/common/ -j $(nproc)
+      - name: Make storage
+        run: |
+          cmake --build modules/storage --target nebula-storaged -j$(nproc)
+          cmake --build modules/storage --target nebula-metad -j$(nproc)
+      - name: Make graph
+        run: cmake --build build/ -j $(nproc)
+      - name: CTest
+        env:
+          ASAN_SYMBOLIZER_PATH: $TOOLSET_DIR/bin/llvm-symbolizer
+          ASAN_OPTIONS: fast_unwind_on_malloc=1
+        run: ctest -j $(($(nproc)/2+1)) --timeout 400 --output-on-failure
+        working-directory: build/
+        timeout-minutes: 15
+      - name: Pytest
+        env:
+          NEBULA_TEST_LOGS_DIR: ${{ github.workspace }}/build/results
+        run: ./ci/test.sh test
+        timeout-minutes: 25
+      - name: Cleanup
+        if: ${{ always() }}
+        run: rm -rf build modules/common modules/storage
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 501438fe3a7019f229c45c46b06baf6ad2a23f14..2bc4a5b8cea35cf8311a0d5b1040e2b506ab6acd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,6 +24,7 @@
 #   ENABLE_JEMALLOC                -- Link jemalloc into all executables
 #   ENABLE_NATIVE                  -- Build native client
 #   ENABLE_TESTING                 -- Build unit test
+#   ENABLE_UPDATE_EXTERNAL_PROJ    -- Whether to update external project
 #
 # CMake version check
 cmake_minimum_required(VERSION 3.5.0)
@@ -141,11 +142,13 @@ option(ENABLE_STATIC_ASAN "Whether directs the GCC driver to link libasan static
 option(ENABLE_STATIC_UBSAN "Whether directs the GCC driver to link libubsan statically" OFF)
 option(ENABLE_PACK_ONE "Whether to package into one" ON)
 option(ENABLE_BUILD_STORAGE "Whether to build storage" OFF)
+option(ENABLE_UPDATE_EXTERNAL_PROJ "Whether to update external project" OFF)
 
 message(STATUS "ENABLE_ASAN: ${ENABLE_ASAN}")
 message(STATUS "ENABLE_TESTING: ${ENABLE_TESTING}")
 message(STATUS "ENABLE_UBSAN: ${ENABLE_UBSAN}")
 message(STATUS "ENABLE_FUZZ_TEST: ${ENABLE_FUZZ_TEST}")
+message(STATUS "ENABLE_UPDATE_EXTERNAL_PROJ: ${ENABLE_UPDATE_EXTERNAL_PROJ}")
 
 add_compile_options(-fno-strict-aliasing)
 
diff --git a/cmake/AddDependentProject.cmake b/cmake/AddDependentProject.cmake
index e55a4ab20df81a5d0acb594d54db37f11eadbdb9..89c08e45b4ecdddaf08aa402b594e9c8a20e6137 100644
--- a/cmake/AddDependentProject.cmake
+++ b/cmake/AddDependentProject.cmake
@@ -16,12 +16,14 @@ macro(add_dependent_project)
     set(CLONE_DIR ${DEP_PROJ_BASE}/${DEP_PROJ_NAME})
     # Clone or update the repo
     if(EXISTS ${CLONE_DIR}/.git)
-        message(STATUS "Updating from the repo \"" ${DEP_PROJ_REPO} "\"")
-        execute_process(
-            COMMAND ${GIT_EXECUTABLE} pull --depth=1
-            WORKING_DIRECTORY ${CLONE_DIR}
-            RESULT_VARIABLE clone_result
-        )
+        message(STATUS "Updating from the repo \"" ${DEP_PROJ_REPO} "\", update option: ${ENABLE_UPDATE_EXTERNAL_PROJ}")
+        if (ENABLE_UPDATE_EXTERNAL_PROJ)
+            execute_process(
+                COMMAND ${GIT_EXECUTABLE} pull --depth=1
+                WORKING_DIRECTORY ${CLONE_DIR}
+                RESULT_VARIABLE clone_result
+            )
+        endif()
     else()
         message(STATUS "Cloning from the repo \"" ${DEP_PROJ_REPO} "\"")
         execute_process(