diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index 8745e7d8e9e5170da8fab7a1c291e217b8062136..62546e593682c3878f258cad41eb7bb7369ca523 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -27,7 +27,7 @@
 review-docs-deploy:
   extends: .review-docs
   script:
-    - ./scripts/trigger-build-docs deploy
+    - ./scripts/trigger-build docs deploy
 
 # Cleanup remote environment of gitlab-docs
 review-docs-cleanup:
@@ -36,7 +36,7 @@ review-docs-cleanup:
     name: review-docs/$DOCS_GITLAB_REPO_SUFFIX-$CI_MERGE_REQUEST_IID
     action: stop
   script:
-    - ./scripts/trigger-build-docs cleanup
+    - ./scripts/trigger-build docs cleanup
 
 docs lint:
   extends:
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index 2ea26985fcf77d0cc19c0ff9085575fa38b12432..71d8a8fc7f94d6f06d505d7cc683ea72bd03b546 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -445,8 +445,8 @@ In case the review app URL returns 404, follow these steps to debug:
 If you want to know the in-depth details, here's what's really happening:
 
 1. You manually run the `review-docs-deploy` job in a merge request.
-1. The job runs the [`scripts/trigger-build-docs`](https://gitlab.com/gitlab-org/gitlab/blob/master/scripts/trigger-build-docs)
-   script with the `deploy` flag, which in turn:
+1. The job runs the [`scripts/trigger-build`](https://gitlab.com/gitlab-org/gitlab/blob/master/scripts/trigger-build)
+   script with the `docs deploy` flag, which in turn:
    1. Takes your branch name and applies the following:
       - The `docs-preview-` prefix is added.
       - The product slug is used to know the project the review app originated
diff --git a/scripts/trigger-build b/scripts/trigger-build
index 9f0df21e7f184de5f478cb59a41712d101a1a26e..7cca2f3e30b1104d972fded8b19e52a70a162da9 100755
--- a/scripts/trigger-build
+++ b/scripts/trigger-build
@@ -29,10 +29,17 @@ module Trigger
   end
 
   class Base
-    def invoke!(post_comment: false, downstream_job_name: nil)
+    # Can be overridden
+    def self.access_token
+      ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN']
+    end
+
+    def initialize
       # gitlab-bot's token "GitLab multi-project pipeline polling"
-      Gitlab.private_token = access_token
+      Gitlab.private_token = self.class.access_token
+    end
 
+    def invoke!(post_comment: false, downstream_job_name: nil)
       pipeline_variables = variables
 
       puts "Triggering downstream pipeline on #{downstream_project_path}"
@@ -74,14 +81,9 @@ module Trigger
       raise NotImplementedError
     end
 
-    # Must be overridden
+    # Can be overridden
     def trigger_token
-      raise NotImplementedError
-    end
-
-    # Must be overridden
-    def access_token
-      raise NotImplementedError
+      ENV['CI_JOB_TOKEN']
     end
 
     # Can be overridden
@@ -133,14 +135,6 @@ module Trigger
       ENV['OMNIBUS_BRANCH'] || 'master'
     end
 
-    def trigger_token
-      ENV['CI_JOB_TOKEN']
-    end
-
-    def access_token
-      ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN']
-    end
-
     def extra_variables
       # Use CI_MERGE_REQUEST_SOURCE_BRANCH_SHA for omnibus checkouts due to pipeline for merged results
       # and fallback to CI_COMMIT_SHA for the `detached` pipelines.
@@ -176,10 +170,6 @@ module Trigger
       ENV['BUILD_TRIGGER_TOKEN']
     end
 
-    def access_token
-      ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN']
-    end
-
     def extra_variables
       edition = Trigger.ee? ? 'EE' : 'CE'
 
@@ -205,6 +195,108 @@ module Trigger
     end
   end
 
+  class Docs < Base
+    SUCCESS_MESSAGE = <<~MSG
+    => You should now be able to preview your changes under the following URL:
+
+    %<app_url>s
+
+    => For more information, see the documentation
+    => https://docs.gitlab.com/ee/development/documentation/index.html#previewing-the-changes-live
+
+    => If something doesn't work, drop a line in the #docs chat channel.
+    MSG
+
+    # Create a remote branch in gitlab-docs and immediately cancel the pipeline
+    # to avoid race conditions, since a triggered pipeline will also run right
+    # after the branch creation. This only happens the very first time a branch
+    # is created and will be skipped in subsequent runs. Read more in
+    # https://gitlab.com/gitlab-org/gitlab-docs/issues/154.
+    #
+    def deploy!
+      create_remote_branch!
+      cancel_latest_pipeline!
+      invoke!.wait!
+      display_success_message
+    end
+
+    #
+    # Remove a remote branch in gitlab-docs.
+    #
+    def cleanup!
+      Gitlab.delete_branch(downstream_project_path, ref)
+      puts "=> Remote branch '#{downstream_project_path}' deleted"
+    end
+
+    private
+
+    def downstream_project_path
+      ENV['DOCS_PROJECT_PATH'] || 'gitlab-org/gitlab-docs'
+    end
+
+    def ref
+      if ENV['CI_MERGE_REQUEST_IID'].nil?
+        "docs-preview-#{slug}-#{ENV['CI_COMMIT_REF_SLUG']}"
+      else
+        "docs-preview-#{slug}-#{ENV['CI_MERGE_REQUEST_IID']}"
+      end
+    end
+
+    def extra_variables
+      {
+        "BRANCH_#{slug.upcase}" => ENV['CI_COMMIT_REF_NAME']
+      }
+    end
+
+    def slug
+      case ENV['CI_PROJECT_PATH']
+      when 'gitlab-org/gitlab-foss'
+        'ce'
+      when 'gitlab-org/gitlab'
+        'ee'
+      when 'gitlab-org/gitlab-runner'
+        'runner'
+      when 'gitlab-org/omnibus-gitlab'
+        'omnibus'
+      when 'gitlab-org/charts/gitlab'
+        'charts'
+      end
+    end
+
+    def app_url
+      "http://#{ref}.#{ENV['DOCS_REVIEW_APPS_DOMAIN']}/#{slug}"
+    end
+
+    def create_remote_branch!
+      Gitlab.create_branch(downstream_project_path, ref, 'master')
+      puts "=> Remote branch '#{ref}' created"
+    end
+
+    def cancel_latest_pipeline!
+      pipelines = nil
+
+      # Wait until the pipeline is started
+      loop do
+        sleep 1
+        puts "=> Waiting for pipeline to start..."
+        pipelines = Gitlab.pipelines(downstream_project_path, { ref: ref })
+        break if pipelines.any?
+      end
+
+      # Get the first pipeline ID which should be the only one for the branch
+      pipeline_id = pipelines.first.id
+
+      # Cancel the pipeline
+      Gitlab.cancel_pipeline(downstream_project_path, pipeline_id)
+    rescue Gitlab::Error::BadRequest
+      puts "=> Remote branch '#{ref}' already exists!"
+    end
+
+    def display_success_message
+      format(SUCCESS_MESSAGE, app_url: app_url)
+    end
+  end
+
   class CommitComment
     def self.post!(downstream_pipeline)
       Gitlab.create_commit_comment(
@@ -282,6 +374,18 @@ when 'omnibus'
   Trigger::Omnibus.new.invoke!(post_comment: true, downstream_job_name: 'Trigger:qa-test').wait!
 when 'cng'
   Trigger::CNG.new.invoke!.wait!
+when 'docs'
+  docs_trigger = Trigger::Docs.new
+
+  case ARGV[1]
+  when 'deploy'
+    docs_trigger.deploy!
+  when 'cleanup'
+    docs_trigger.cleanup!
+  else
+    puts 'usage: trigger-build docs <deploy|cleanup>'
+    exit 1
+  end
 else
   puts "Please provide a valid option:
   omnibus - Triggers a pipeline that builds the omnibus-gitlab package
diff --git a/scripts/trigger-build-docs b/scripts/trigger-build-docs
deleted file mode 100755
index 2957dde6fc01cd2fbb477800c0dc3676f102db31..0000000000000000000000000000000000000000
--- a/scripts/trigger-build-docs
+++ /dev/null
@@ -1,136 +0,0 @@
-#!/usr/bin/env ruby
-
-require 'gitlab'
-
-#
-# Configure credentials to be used with gitlab gem
-#
-Gitlab.configure do |config|
-  config.endpoint      = 'https://gitlab.com/api/v4'
-  config.private_token = ENV["DOCS_API_TOKEN"] # GitLab Docs bot access token with Developer access to gitlab-docs
-end
-
-#
-# The remote docs project
-#
-GITLAB_DOCS_REPO = 'gitlab-org/gitlab-docs'.freeze
-
-#
-# This is the branch that will be created in the gitlab-docs project.
-# Name it after the product we're previewing and the ID of the MR that
-# kicked the review app.
-#
-def docs_branch
-  # Check if CI_MERGE_REQUEST_IID is present. This requires pipelines
-  # for merge requests to be enabled.
-  if ENV["CI_MERGE_REQUEST_IID"].nil?
-    "docs-preview-#{slug}-#{ENV["CI_COMMIT_REF_SLUG"]}"
-  else
-    "docs-preview-#{slug}-#{ENV["CI_MERGE_REQUEST_IID"]}"
-  end
-end
-
-#
-# Create a remote branch in gitlab-docs and immediately cancel the pipeline
-# to avoid race conditions, since a triggered pipeline will also run right
-# after the branch creation. This only happens the very first time a branch
-# is created and will be skipped in subsequent runs. Read more in
-# https://gitlab.com/gitlab-org/gitlab-docs/issues/154.
-#
-def create_remote_branch
-  Gitlab.create_branch(GITLAB_DOCS_REPO, docs_branch, 'master')
-  puts "=> Remote branch '#{docs_branch}' created"
-
-  pipelines = nil
-
-  # Wait until the pipeline is started
-  loop do
-    sleep 1
-    puts "=> Waiting for pipeline to start..."
-    pipelines = Gitlab.pipelines(GITLAB_DOCS_REPO, { ref: docs_branch })
-    break if pipelines.any?
-  end
-
-  # Get the first pipeline ID which should be the only one for the branch
-  pipeline_id = pipelines.first.id
-
-  # Cancel the pipeline
-  Gitlab.cancel_pipeline(GITLAB_DOCS_REPO, pipeline_id)
-rescue Gitlab::Error::BadRequest
-  puts "=> Remote branch '#{docs_branch}' already exists"
-end
-
-#
-# Remove a remote branch in gitlab-docs
-#
-def remove_remote_branch
-  Gitlab.delete_branch(GITLAB_DOCS_REPO, docs_branch)
-  puts "=> Remote branch '#{docs_branch}' deleted"
-end
-
-#
-# Define suffix in review app URL based on project
-#
-def slug
-  case ENV["CI_PROJECT_PATH"]
-  when 'gitlab-org/gitlab-foss'
-    'ce'
-  when 'gitlab-org/gitlab'
-    'ee'
-  when 'gitlab-org/gitlab-runner'
-    'runner'
-  when 'gitlab-org/omnibus-gitlab'
-    'omnibus'
-  when 'gitlab-org/charts/gitlab'
-    'charts'
-  end
-end
-
-#
-# Overriding vars in https://gitlab.com/gitlab-org/gitlab-docs/blob/master/.gitlab-ci.yml
-#
-def param_name
-  "BRANCH_#{slug.upcase}"
-end
-
-#
-# Trigger a pipeline in gitlab-docs
-#
-def trigger_pipeline
-  # The review app URL
-  app_url = "http://#{docs_branch}.#{ENV["DOCS_REVIEW_APPS_DOMAIN"]}/#{slug}"
-
-  # Create the cross project pipeline using CI_JOB_TOKEN
-  pipeline = Gitlab.run_trigger(GITLAB_DOCS_REPO, ENV["CI_JOB_TOKEN"], docs_branch, { param_name => ENV["CI_COMMIT_REF_NAME"] })
-
-  puts "=> Follow the status of the triggered pipeline:"
-  puts ""
-  puts pipeline.web_url
-  puts ""
-  puts "=> In a few minutes, you will be able to preview your changes under the following URL:"
-  puts ""
-  puts app_url
-  puts ""
-  puts "=> For more information, see the documentation"
-  puts "=> https://docs.gitlab.com/ee/development/documentation/index.html#previewing-the-changes-live"
-  puts ""
-  puts "=> If something doesn't work, drop a line in the #docs chat channel."
-  puts ""
-end
-
-#
-# When the first argument is deploy then create the branch and trigger pipeline
-# When it is 'stop', it deleted the remote branch. That way, we ensure there
-# are no stale remote branches and the Review server doesn't fill.
-#
-case ARGV[0]
-when 'deploy'
-  create_remote_branch
-  trigger_pipeline
-when 'cleanup'
-  remove_remote_branch
-else
-  puts "Please provide a valid option:
-  deploy  - Creates the remote branch and triggers a pipeline
-  cleanup - Deletes the remote branch and stops the Review App"
-end