diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..63ad170
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,107 @@
+name: Build
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+on:
+  workflow_call:
+    inputs:
+      # Selects the version of Postgres for running tests
+      # See: https://github.com/docker-library/docs/blob/master/postgres/README.md#supported-tags-and-respective-dockerfile-links
+      postgres_image:
+        required: true
+        type: string
+
+      # Determines whether to install Node and run `yarn install`
+      use_node:
+        required: false
+        type: boolean
+        default: true
+
+      # Sets BUNDLE_APP_CONFIG environment variable
+      # See: https://bundler.io/man/bundle-config.1.html
+      bundle_app_config:
+        required: false
+        type: string
+        default: .bundle/ci-build
+
+      # Selects the runner on which the workflow will run
+      # See: https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
+      runner:
+        required: false
+        type: string
+        default: ubuntu-20.04
+
+      # Defines which scripts will run on CI
+      # Format: space-delimited paths to scripts
+      # Example: 'bin/audit bin/lint bin/test'
+      ci_steps:
+        required: true
+        type: string
+    secrets:
+      VAULT_ADDR:
+        required: true
+      VAULT_AUTH_METHOD:
+        required: true
+      VAULT_AUTH_USER_ID:
+        required: true
+      VAULT_AUTH_APP_ID:
+        required: true
+
+jobs:
+  build:
+    name: 'Build'
+    runs-on: ${{ inputs.runner }}
+    env:
+      BUNDLE_APP_CONFIG: ${{ inputs.bundle_app_config }}
+      RUBOCOP_CACHE_ROOT: .rubocop-cache
+    services:
+      postgres:
+        image: postgres:${{ inputs.postgres_image }}
+        env:
+          POSTGRES_HOST_AUTH_METHOD: trust
+        ports:
+          - 5432:5432
+        options: --name=postgres
+    steps:
+      - name: Git checkout
+        uses: actions/checkout@v2
+      - name: Set up Ruby
+        uses: ruby/setup-ruby@v1
+        with:
+          bundler-cache: true
+      - name: Prepare RuboCop cache
+        uses: actions/cache@v2
+        with:
+          path: ${{ env.RUBOCOP_CACHE_ROOT }}
+          key: ${{ runner.os }}-rubocop-cache-${{ github.sha }}
+          restore-keys: |
+            ${{ runner.os }}-rubocop-cache-
+      - name: Set up Node
+        uses: actions/setup-node@v2
+        if: ${{ inputs.use_node }}
+        with:
+          node-version-file: '.node-version'
+      - name: Prepare node_modules cache
+        uses: actions/cache@v2
+        if: ${{ inputs.use_node }}
+        with:
+          path: node_modules
+          key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
+          restore-keys: |
+            ${{ runner.os }}-modules-
+      - name: Install JS packages
+        if: ${{ inputs.use_node }}
+        run: yarn install --frozen-lockfile
+      - name: Prepare CI
+        run: bin/prepare_ci
+        env:
+          VAULT_ADDR: ${{ secrets.VAULT_ADDR }}
+          VAULT_AUTH_METHOD: ${{ secrets.VAULT_AUTH_METHOD }}
+          VAULT_AUTH_USER_ID: ${{ secrets.VAULT_AUTH_USER_ID }}
+          VAULT_AUTH_APP_ID: ${{ secrets.VAULT_AUTH_APP_ID }}
+      - name: Wait for Postgres to be ready
+        run: until docker exec postgres pg_isready; do sleep 1; done
+      - name: CI steps
+        run: 'parallel --lb -k -j0 ::: ${{ inputs.ci_steps }}'
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000..8bbfa69
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,62 @@
+name: Deploy
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+
+on:
+  workflow_call:
+    inputs:
+      # Sets the Mina environment (e.g. staging, production)
+      # A task by the same name must exist in config/deploy.rb
+      environment:
+        required: true
+        type: string
+
+      # Sets the Git branch which will be checked out
+      branch:
+        required: true
+        type: string
+
+      # Determines who can manually trigger the workflow
+      # Example: "@github_username1 @github_username2"
+      # See: https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow
+      deployers:
+        required: false
+        type: string
+        default: ''
+
+      # Sets BUNDLE_APP_CONFIG environment variable
+      # See: https://bundler.io/man/bundle-config.1.html
+      bundle_app_config:
+        required: false
+        type: string
+        default: .bundle/ci-deploy
+
+      # Selects the runner on which the workflow will run
+      # See: https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
+      runner:
+        required: false
+        type: string
+        default: ubuntu-20.04
+    secrets:
+      SSH_PRIVATE_KEY:
+        required: true
+
+jobs:
+  deploy:
+    name: Deploy
+    runs-on: ${{ inputs.runner }}
+    env:
+      BUNDLE_APP_CONFIG: ${{ inputs.bundle_app_config }}
+    if: ${{ github.event_name == 'workflow_dispatch' && contains(inputs.deployers, format('@{0}', github.actor)) || github.event.workflow_run.conclusion == 'success' }}
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          ref: ${{ inputs.branch }}
+      - uses: ruby/setup-ruby@v1
+        with:
+          bundler-cache: true
+      - uses: webfactory/ssh-agent@v0.5.4
+        with:
+          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
+      - run: bin/deploy ${{ inputs.environment }}
diff --git a/README.md b/README.md
index a0ebbe2..d878019 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,22 @@ then run if needed:
   rbenv global #{latest_ruby}
 ```
 
+### GitHub Actions
+
+This template uses GitHub Actions for CI/CD. In order for workflows to work properly some [secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) have to be set up.
+
+For build workflow to work, the following secrets must exist (usually set up by DevOps):
+- `VAULT_ADDR`
+- `VAULT_AUTH_METHOD`
+- `VAULT_AUTH_USER_ID`
+- `VAULT_AUTH_APP_ID`
+
+For deploy workflows, you need to generate private/public SSH key pairs for each environment. Public key should be added to the server to which you're deploying. Private key should be added as a secret to GitHub and named `SSH_PRIVATE_KEY_#{ENVIRONMENT}`, where `ENVIRONMENT` is replaced with an appropriate environment name (`STAGING`, `PRODUCTION`, etc.).
+
+### Frontend
+
+If your application will have a frontend (the template will ask you that), you must have Node installed on your machine. The template creates a `.node-version` file with the Node version set to the version you're currently running (check by executing `node -v`). Therefore, ensure that you have the latest [Active LTS](https://nodejs.org/en/about/releases/) version of Node running on your machine before using the template.
+
 ## Usage
 
 ```shell
diff --git a/build.yml b/build.yml
new file mode 100644
index 0000000..7b65f1f
--- /dev/null
+++ b/build.yml
@@ -0,0 +1,17 @@
+name: Build
+
+on: [push]
+
+jobs:
+  build:
+    name: Build
+    uses: infinum/default_rails_template/.github/workflows/build.yml@v1
+    with:
+      postgres_image: '13.2'
+      use_node: false
+      ci_steps: 'bin/audit bin/lint bin/test'
+    secrets:
+      VAULT_ADDR: ${{ secrets.VAULT_ADDR }}
+      VAULT_AUTH_METHOD: ${{ secrets.VAULT_AUTH_METHOD }}
+      VAULT_AUTH_USER_ID: ${{ secrets.VAULT_AUTH_USER_ID }}
+      VAULT_AUTH_APP_ID: ${{ secrets.VAULT_AUTH_APP_ID }}
diff --git a/deploy-production.yml b/deploy-production.yml
new file mode 100644
index 0000000..02631e1
--- /dev/null
+++ b/deploy-production.yml
@@ -0,0 +1,19 @@
+name: Deploy production
+
+on:
+  workflow_dispatch:
+  # workflow_run: # UNCOMMENT THIS IF YOU WANT AUTOMATIC PRODUCTION DEPLOYS
+  #   workflows: [Build]
+  #   branches: [master]
+  #   types: [completed]
+
+jobs:
+  deploy:
+    name: Deploy
+    uses: infinum/default_rails_template/.github/workflows/deploy.yml@v1
+    with:
+      environment: production
+      branch: master
+      deployers: 'DEPLOY USERS GO HERE' # Example: '@github_username1 @github_username2'
+    secrets:
+      SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY_PRODUCTION }}
diff --git a/deploy-staging.yml b/deploy-staging.yml
new file mode 100644
index 0000000..c695b6a
--- /dev/null
+++ b/deploy-staging.yml
@@ -0,0 +1,19 @@
+name: Deploy staging
+
+on:
+  workflow_dispatch:
+  workflow_run:
+    workflows: [Build]
+    branches: [staging]
+    types: [completed]
+
+jobs:
+  deploy:
+    name: Deploy
+    uses: infinum/default_rails_template/.github/workflows/deploy.yml@v1
+    with:
+      environment: staging
+      branch: staging
+      deployers: 'DEPLOY USERS GO HERE' # Example: '@github_username1 @github_username2'
+    secrets:
+      SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY_STAGING }}
diff --git a/template.rb b/template.rb
index 85f2151..d4a3b74 100644
--- a/template.rb
+++ b/template.rb
@@ -135,55 +135,67 @@
 HEREDOC
 create_file 'bin/update', BIN_UPDATE, force: true
 
-BIN_BUILD = <<~HEREDOC.strip_heredoc
+BIN_PREPARE_CI = <<~HEREDOC.strip_heredoc
   #!/usr/bin/env bash
 
   set -o errexit
   set -o pipefail
   set -o nounset
 
-  echo "=========== setting env variables ==========="
-  export RAILS_ENV='test'
-  export BUNDLE_APP_CONFIG='.bundle/ci-build'
-
-  echo "=========== install bundler ==========="
-  bundler_version=`tail -n 1 Gemfile.lock | xargs`
-  time gem install bundler:$bundler_version --no-document
-
-  echo "=========== bundle install ==========="
-  time bundle install
+  echo "=========== pull secrets ==========="
+  bundle exec secrets pull -e development -y
+HEREDOC
+create_file 'bin/prepare_ci', BIN_PREPARE_CI, force: true
+chmod 'bin/prepare_ci', 0755, verbose: false
 
-  echo "=========== secrets pull ============="
-  time bundle exec secrets pull -e development -y
+BIN_AUDIT = <<~HEREDOC.strip_heredoc
+  #!/usr/bin/env bash
 
-  echo "=========== rails db:test:prepare ==========="
-  time bundle exec rails db:test:prepare
+  set -o errexit
+  set -o pipefail
+  set -o nounset
 
   echo "=========== bundle audit ==========="
   time bundle exec bundle-audit update --quiet
   time bundle exec bundle-audit check
 
-  #############################################
-  # Uncomment this if you need yarn libraries #
-  # for running your tests                    #
-  #############################################
-  # echo "=========== yarn install ==========="
-  # time yarn install
+  echo "=========== brakeman ==========="
+  time bundle exec brakeman -q --color
+HEREDOC
+create_file 'bin/audit', BIN_AUDIT, force: true
+chmod 'bin/audit', 0755, verbose: false
+
+BIN_LINT = <<~HEREDOC.strip_heredoc
+  #!/usr/bin/env bash
+
+  set -o errexit
+  set -o pipefail
+  set -o nounset
 
   echo "=========== zeitwerk check ==========="
   time bundle exec rails zeitwerk:check
 
-  echo "=========== brakeman ==========="
-  time bundle exec brakeman -q
-
   echo "=========== rubocop  ==========="
-  time bundle exec rubocop --format simple
+  time bundle exec rubocop --format simple --format github --color --parallel
+HEREDOC
+create_file 'bin/lint', BIN_LINT, force: true
+chmod 'bin/lint', 0755, verbose: false
+
+BIN_TEST = <<~HEREDOC.strip_heredoc
+  #!/usr/bin/env bash
+
+  set -o errexit
+  set -o pipefail
+  set -o nounset
+
+  echo "=========== rails db:test:prepare ==========="
+  time RAILS_ENV=test bundle exec rails db:test:prepare
 
   echo "=========== rspec ==========="
-  time bundle exec rspec
+  time bundle exec rspec --force-color
 HEREDOC
-create_file 'bin/build', BIN_BUILD, force: true
-chmod 'bin/build', 0755, verbose: false
+create_file 'bin/test', BIN_TEST, force: true
+chmod 'bin/test', 0755, verbose: false
 
 BIN_DEPLOY = <<~HEREDOC.strip_heredoc
   #!/usr/bin/env bash
@@ -194,18 +206,11 @@
 
   echo "=========== setting env variables ==========="
   environment=$1
-  export RAILS_ENV='test'
-  export BUNDLE_APP_CONFIG='.bundle/ci-deploy'
-
-  echo "=========== install bundler ==========="
-  bundler_version=`tail -n 1 Gemfile.lock | xargs`
-  time gem install bundler:$bundler_version --no-document
-
-  echo "=========== bundle install ==========="
-  time bundle install
 
   echo "=========== mina deploy =============="
-  time bundle exec mina $environment ssh_keyscan_domain setup deploy
+  time bundle exec mina $environment ssh_keyscan_domain
+  time bundle exec mina $environment setup
+  time bundle exec mina $environment deploy
 
   #############################################
   # Uncomment this if you need to publish dox #
@@ -228,6 +233,10 @@
 create_file 'bin/deploy', BIN_DEPLOY, force: true
 chmod 'bin/deploy', 0755, verbose: false
 
+# get("#{BASE_URL}/build.yml", '.github/workflows/build.yml')
+# get("#{BASE_URL}/deploy-staging.yml", '.github/workflows/deploy-staging.yml')
+# get("#{BASE_URL}/deploy-production.yml", '.github/workflows/deploy-production.yml')
+
 # bundler config
 BUNDLER_CI_BUILD_CONFIG = <<~HEREDOC.strip_heredoc
   ---
@@ -578,6 +587,10 @@ def run
 
   get("#{BASE_URL}/.slim-lint.yml", '.slim-lint.yml')
 
+  node_version = `node -v`.chomp.sub('v', '')
+
+  create_file '.node-version', node_version
+
   PACKAGE_JSON_FILE = <<~HEREDOC
     {
       "name": "#{app_name}",
@@ -592,6 +605,9 @@ def run
       },
       "eslintConfig": {
         "extends": "@infinumrails/eslint-config-js"
+      },
+      "engines": {
+        "node": "#{node_version}"
       }
     }
   HEREDOC
@@ -634,7 +650,7 @@ def run
 
   create_file '.stylelintignore', STYLELINTIGNORE_FILE
 
-  append_to_file 'bin/build', after: "time bundle exec rubocop --format simple\n" do
+  append_to_file 'bin/lint' do
     <<~HEREDOC
 
       echo "=========== slim lint ==========="
@@ -649,6 +665,8 @@ def run
   end
 
   run 'yarn add --dev @infinumrails/eslint-config-js @infinumrails/stylelint-config-scss eslint postcss stylelint'
+
+  gsub_file('.github/workflows/build.yml', /^.*use_node: false.*\n/, '')
 end
 
 ## Ask about default PR reviewers
@@ -659,6 +677,13 @@ def run
   HEREDOC
 end
 
+## Users allowed to manually trigger deploys
+staging_deployers = ask('Who can manually trigger a deploy to staging? (Example: @username1 @username2)', :green)
+gsub_file('.github/workflows/deploy-staging.yml', 'DEPLOY USERS GO HERE', staging_deployers)
+
+production_deployers = ask('Who can manually trigger a deploy to production? (Example: @username1 @username2)', :green)
+gsub_file('.github/workflows/deploy-production.yml', 'DEPLOY USERS GO HERE', production_deployers)
+
 # add annotate task file and ignore its rubocop violations
 rails_command 'generate annotate:install'
 ANNOTATE_TASK_FILE = 'lib/tasks/auto_annotate_models.rake'