diff --git a/.travis/default.yml b/.ci/default.yml
similarity index 100%
rename from .travis/default.yml
rename to .ci/default.yml
diff --git a/.travis/test.yml b/.ci/test.yml
similarity index 100%
rename from .travis/test.yml
rename to .ci/test.yml
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 630a7eebb685e6bf0c70cf0e3e17dbb73def1482..422f6fdc220c0555e18409d78f1ddab12bddb541 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,60 +1,129 @@
-version: 2
+version: 2.1
 
 general:
   branches:
     ignore:
       - l10n_develop
+      - imgbot
 
-jobs:
-  webpack-build:
-    working_directory: /misskey
+executors:
+  default:
+    working_directory: /tmp/workspace
+    docker:
+      - image: misskey/ci:latest
+      - image: circleci/mongo:latest
+      - image: circleci/redis:latest
+  docker:
+    working_directory: /tmp/workspace
     docker:
-      - image: yukimochi/misskey-builder:latest
+      - image: docker:latest
+
+jobs:
+  build:
+    executor: default
     steps:
       - checkout
+      - restore_cache:
+          name: Restore npm package caches
+          keys:
+            - npm-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}-package-{{ checksum "package.json" }}-lock-{{ checksum "package-lock.json" }}-
+            - npm-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}-package-{{ checksum "package.json" }}-
+            - npm-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}-
+            - npm-v1-arch-{{ arch }}-
+            - npm-v1-
       - run:
-          name: Setup Dependencies
-          command: |
-            yarn install
-            yarn global add web-push
-      - run:
-          name: Import default.yml
+          name: Install Dependencies
           command: |
-            echo ${IMPORT_DEFAULT_YML} | base64 -d | gzip -d > .config/default.yml
+            npm install
       - run:
-          name: Build Webpack
+          name: Configure
           command: |
-            yarn run build
+            cp .ci/default.yml .config
+            cp .ci/test.yml .config
       - run:
-          name: Compress clients
+          name: Build
           command: |
-            find ./built/client -name "*.js" -or -name "*.js.map" -or -name "*.css" -or -name "*.svg" -or -name "*.html" -or -name "*.json" | xargs -t gzip -k -9
-            find ./built/client -name "*.js" -or -name "*.js.map" -or -name "*.css" -or -name "*.svg" -or -name "*.html" -or -name "*.json" | xargs -t brotli -q 10
-            tar cfz ~/built-${CIRCLE_SHA1}.tar.gz built
+            npm run build || (echo -e '\033[0;34mRebuild modules\033[0;39m' && ls -1A node_modules | grep '^[^@]' | xargs npm rebuild && ls -1A node_modules | grep '^@' | xargs -I%1 sh -c 'ls -1A node_modules/'%1' | xargs -P0 -I%2 npm rebuild node_modules/'%1'/%2' && npm run build)
+            ls -1ARl node_modules > ls
+      - save_cache:
+          name: Cache npm packages
+          key: npm-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}-package-{{ checksum "package.json" }}-lock-{{ checksum "package-lock.json" }}-ls-{{ checksum "ls" }}
+          paths:
+            - node_modules
+      - store_artifacts:
+          path: built
+      - persist_to_workspace:
+          root: .
+          paths:
+            - .
+  test:
+    parameters:
+      without_redis:
+        type: string
+        default: ""
+    executor: default
+    steps:
+      - attach_workspace:
+          at: /tmp/workspace
+      - when:
+          condition: <<parameters.without_redis>>
+          steps:
+            - run:
+                name: Configure
+                command: |
+                  mv .config/test.yml .config/test_redis.yml
+                  touch .config/test.yml
+                  cat .config/test_redis.yml | while IFS= read line; do if [[ "$line" = '# __REDIS__' ]]; then break; else echo "$line" >> .config/test.yml; fi; done
       - run:
-          name: Send built s3
+          name: Test
           command: |
-            mc config host add ykmc ${s3_endpoint} ${s3_accesskey} ${s3_secretkey}
-            mc cp ~/built-${CIRCLE_SHA1}.tar.gz ${backet}/${CIRCLE_BRANCH}/
-  docker-build:
-    docker:
-      - image: docker:17-git
+            npm run test || (npm rebuild && npm run test) || ((node-gyp configure && node-gyp build && npm run build || (echo -e '\033[0;34mRebuild modules\033[0;39m' && ls -1A node_modules | grep '^[^@]' | xargs npm rebuild && ls -1A node_modules | grep '^@' | xargs -I%1 sh -c 'ls -1A node_modules/'%1' | xargs -P0 -I%2 npm rebuild node_modules/'%1'/%2' && npm run build)) && npm run test)
+            ls -1ARl node_modules > ls
+      - save_cache:
+          name: Cache npm packages
+          key: npm-v1-arch-{{ arch }}-env-{{ .Environment.variableName }}-package-{{ checksum "package.json" }}-lock-{{ checksum "package-lock.json" }}-ls-{{ checksum "ls" }}
+          paths:
+            - node_modules
+
+  docker:
+    parameters:
+      with_deploy:
+        type: string
+        default: ""
+    executor: docker
     steps:
       - checkout
       - setup_remote_docker
       - run:
-          name: build docker image
+          name: Build
           command: |
-            docker build -t misskey:latest .
-      - run:
-          name: upload image to docker hub.
-          command: |
-            docker login --username=${DOCKER_USER} --password=${DOCKER_PASS}
-            docker push ${DOCKER_USER}/misskey:latest
+            docker build .
+      - when:
+          condition: <<parameters.with_deploy>>
+          steps:
+            - run:
+                name: Deploy
+                command: |
+                  docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD && docker push misskey/misskey
 
 workflows:
   version: 2
-  build:
+  build-and-test:
     jobs:
-      - webpack-build
-      - docker-build
+      - build
+      - test:
+          requires:
+            - build
+      - test:
+          without_redis: "true"
+          requires:
+            - build
+      - docker:
+          filters:
+            branches:
+              ignore: master
+      - docker:
+          with_deploy: "true"
+          filters:
+            branches:
+              only: master
diff --git a/.travis.yml b/.travis.yml
index bf00d28daf2595dcbdd0c4567ef8c0146803f1b1..80ecb236c0d91e092959db9028c49d19556f9591 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -35,7 +35,7 @@ before_script:
   - npm install
 
   # 設定ファイルを配置
-  - cp ./.travis/default.yml ./.config
-  - cp ./.travis/test.yml ./.config
+  - cp ./.ci/default.yml ./.config
+  - cp ./.ci/test.yml ./.config
 
   - travis_wait npm run build
diff --git a/Dockerfile b/Dockerfile
index a95789c01d26ac3a2ca8231415263c27e17cd0e7..e31af0099b8e75bf053413eb4a3e4591b29932b4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -21,11 +21,12 @@ RUN apk add --no-cache \
     pkgconfig \
     libtool \
     zlib-dev
-RUN npm install \
-    && npm install -g node-gyp \
-    && node-gyp configure \
-    && node-gyp build \
-    && npm run build
+RUN npm i -g npm@latest \
+ && npm i \
+ && npm i -g node-gyp \
+ && node-gyp configure \
+ && node-gyp build \
+ && npm run build
 
 FROM base AS runner