diff --git a/app/services/self_monitoring/project/create_service.rb b/app/services/self_monitoring/project/create_service.rb
deleted file mode 100644
index c925c6a1610c261f3fcd3194ed95c3287f929752..0000000000000000000000000000000000000000
--- a/app/services/self_monitoring/project/create_service.rb
+++ /dev/null
@@ -1,219 +0,0 @@
-# frozen_string_literal: true
-
-module SelfMonitoring
-  module Project
-    class CreateService < ::BaseService
-      include Stepable
-      include Gitlab::Utils::StrongMemoize
-
-      VISIBILITY_LEVEL = Gitlab::VisibilityLevel::INTERNAL
-      PROJECT_NAME = 'GitLab Instance Administration'
-      PROJECT_DESCRIPTION = <<~HEREDOC
-        This project is automatically generated and will be used to help monitor this GitLab instance.
-      HEREDOC
-
-      GROUP_NAME = 'GitLab Instance Administrators'
-      GROUP_PATH = 'gitlab-instance-administrators'
-
-      steps :validate_admins,
-        :create_group,
-        :create_project,
-        :save_project_id,
-        :add_group_members,
-        :add_to_whitelist,
-        :add_prometheus_manual_configuration
-
-      def initialize
-        super(nil)
-      end
-
-      def execute
-        execute_steps
-      end
-
-      private
-
-      def validate_admins
-        unless instance_admins.any?
-          log_error('No active admin user found')
-          return error('No active admin user found')
-        end
-
-        success
-      end
-
-      def create_group
-        if project_created?
-          log_info(_('Instance administrators group already exists'))
-          @group = application_settings.instance_administration_project.owner
-          return success(group: @group)
-        end
-
-        admin_user = group_owner
-        @group = ::Groups::CreateService.new(admin_user, create_group_params).execute
-
-        if @group.persisted?
-          success(group: @group)
-        else
-          error('Could not create group')
-        end
-      end
-
-      def create_project
-        if project_created?
-          log_info(_('Instance administration project already exists'))
-          @project = application_settings.instance_administration_project
-          return success(project: project)
-        end
-
-        admin_user = group_owner
-        @project = ::Projects::CreateService.new(admin_user, create_project_params).execute
-
-        if project.persisted?
-          success(project: project)
-        else
-          log_error(_("Could not create instance administration project. Errors: %{errors}") % { errors: project.errors.full_messages })
-          error(_('Could not create project'))
-        end
-      end
-
-      def save_project_id
-        return success if project_created?
-
-        result = ApplicationSettings::UpdateService.new(
-          application_settings,
-          group_owner,
-          { instance_administration_project_id: @project.id }
-        ).execute
-
-        if result
-          success
-        else
-          log_error(_("Could not save instance administration project ID, errors: %{errors}") % { errors: application_settings.errors.full_messages })
-          error(_('Could not save project ID'))
-        end
-      end
-
-      def add_group_members
-        members = @group.add_users(group_maintainers, Gitlab::Access::MAINTAINER)
-        errors = members.flat_map { |member| member.errors.full_messages }
-
-        if errors.any?
-          log_error("Could not add admins as members to self-monitoring project. Errors: #{errors}")
-          error('Could not add admins as members')
-        else
-          success
-        end
-      end
-
-      def add_to_whitelist
-        return success unless prometheus_enabled?
-        return success unless prometheus_listen_address.present?
-
-        uri = parse_url(internal_prometheus_listen_address_uri)
-        return error(_('Prometheus listen_address is not a valid URI')) unless uri
-
-        result = ApplicationSettings::UpdateService.new(
-          application_settings,
-          group_owner,
-          add_to_outbound_local_requests_whitelist: [uri.normalized_host]
-        ).execute
-
-        if result
-          success
-        else
-          log_error(_("Could not add prometheus URL to whitelist, errors: %{errors}") % { errors: application_settings.errors.full_messages })
-          error(_('Could not add prometheus URL to whitelist'))
-        end
-      end
-
-      def add_prometheus_manual_configuration
-        return success unless prometheus_enabled?
-        return success unless prometheus_listen_address.present?
-
-        service = project.find_or_initialize_service('prometheus')
-
-        unless service.update(prometheus_service_attributes)
-          log_error("Could not save prometheus manual configuration for self-monitoring project. Errors: #{service.errors.full_messages}")
-          return error('Could not save prometheus manual configuration')
-        end
-
-        success
-      end
-
-      def application_settings
-        strong_memoize(:application_settings) do
-          Gitlab::CurrentSettings.expire_current_application_settings
-          Gitlab::CurrentSettings.current_application_settings
-        end
-      end
-
-      def project_created?
-        application_settings.instance_administration_project.present?
-      end
-
-      def parse_url(uri_string)
-        Addressable::URI.parse(uri_string)
-      rescue Addressable::URI::InvalidURIError, TypeError
-      end
-
-      def prometheus_enabled?
-        Gitlab.config.prometheus.enable
-      rescue Settingslogic::MissingSetting
-        false
-      end
-
-      def prometheus_listen_address
-        Gitlab.config.prometheus.listen_address
-      rescue Settingslogic::MissingSetting
-      end
-
-      def instance_admins
-        @instance_admins ||= User.admins.active
-      end
-
-      def group_owner
-        instance_admins.first
-      end
-
-      def group_maintainers
-        # Exclude the first so that the group_owner is not added again as a member.
-        instance_admins - [group_owner]
-      end
-
-      def create_group_params
-        {
-          name: GROUP_NAME,
-          path: "#{GROUP_PATH}-#{SecureRandom.hex(4)}",
-          visibility_level: VISIBILITY_LEVEL
-        }
-      end
-
-      def create_project_params
-        {
-          initialize_with_readme: true,
-          visibility_level: VISIBILITY_LEVEL,
-          name: PROJECT_NAME,
-          description: PROJECT_DESCRIPTION,
-          namespace_id: @group.id
-        }
-      end
-
-      def internal_prometheus_listen_address_uri
-        if prometheus_listen_address.starts_with?('http')
-          prometheus_listen_address
-        else
-          'http://' + prometheus_listen_address
-        end
-      end
-
-      def prometheus_service_attributes
-        {
-          api_url: internal_prometheus_listen_address_uri,
-          manual_configuration: true,
-          active: true
-        }
-      end
-    end
-  end
-end
diff --git a/changelogs/unreleased/56883-migration.yml b/changelogs/unreleased/56883-migration.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d6eb49e62e10ce383068b3162608aedb874c6926
--- /dev/null
+++ b/changelogs/unreleased/56883-migration.yml
@@ -0,0 +1,5 @@
+---
+title: Create a project for self-monitoring the GitLab instance
+merge_request: 31389
+author:
+type: added
diff --git a/db/post_migrate/20190801072937_add_gitlab_instance_administration_project.rb b/db/post_migrate/20190801072937_add_gitlab_instance_administration_project.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8b2cf7b3d76248e0f0d215384bc2a8f3817088c7
--- /dev/null
+++ b/db/post_migrate/20190801072937_add_gitlab_instance_administration_project.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddGitlabInstanceAdministrationProject < ActiveRecord::Migration[5.2]
+  DOWNTIME = false
+
+  def up
+    Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService.new.execute!
+  end
+
+  def down
+    ApplicationSetting.current_without_cache
+      &.instance_administration_project
+      &.owner
+      &.destroy!
+  end
+end
diff --git a/db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb b/db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0c4faebc54827e97b3e924651b17fe1c59a43dd1
--- /dev/null
+++ b/db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+class SetSelfMonitoringProjectAlertingToken < ActiveRecord::Migration[5.2]
+  DOWNTIME = false
+
+  module Migratable
+    module Alerting
+      class ProjectAlertingSetting < ApplicationRecord
+        self.table_name = 'project_alerting_settings'
+
+        belongs_to :project
+
+        validates :token, presence: true
+
+        attr_encrypted :token,
+          mode: :per_attribute_iv,
+          key: Settings.attr_encrypted_db_key_base_truncated,
+          algorithm: 'aes-256-gcm'
+
+        before_validation :ensure_token
+
+        private
+
+        def ensure_token
+          self.token ||= generate_token
+        end
+
+        def generate_token
+          SecureRandom.hex
+        end
+      end
+    end
+
+    class Project < ApplicationRecord
+      has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting'
+    end
+
+    class ApplicationSetting < ApplicationRecord
+      self.table_name = 'application_settings'
+
+      belongs_to :instance_administration_project, class_name: 'Project'
+
+      def self.current_without_cache
+        last
+      end
+    end
+  end
+
+  def setup_alertmanager_token(project)
+    return unless License.feature_available?(:prometheus_alerts)
+
+    project.create_alerting_setting!
+  end
+
+  def up
+    Gitlab.ee do
+      project = Migratable::ApplicationSetting.current_without_cache&.instance_administration_project
+
+      if project
+        setup_alertmanager_token(project)
+      end
+    end
+  end
+
+  def down
+    Gitlab.ee do
+      Migratable::ApplicationSetting.current_without_cache
+        &.instance_administration_project
+        &.alerting_setting
+        &.destroy!
+    end
+  end
+end
diff --git a/lib/gitlab/database_importers/self_monitoring/project/create_service.rb b/lib/gitlab/database_importers/self_monitoring/project/create_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..164854e1e1a3f896a56aa6a39a5aaeeb48b14c87
--- /dev/null
+++ b/lib/gitlab/database_importers/self_monitoring/project/create_service.rb
@@ -0,0 +1,251 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module DatabaseImporters
+    module SelfMonitoring
+      module Project
+        include Stepable
+
+        class CreateService < ::BaseService
+          include Stepable
+
+          STEPS_ALLOWED_TO_FAIL = [
+            :validate_application_settings, :validate_project_created, :validate_admins
+          ].freeze
+
+          VISIBILITY_LEVEL = Gitlab::VisibilityLevel::INTERNAL
+          PROJECT_NAME = 'GitLab Instance Administration'
+
+          steps :validate_application_settings,
+            :validate_project_created,
+            :validate_admins,
+            :create_group,
+            :create_project,
+            :save_project_id,
+            :add_group_members,
+            :add_to_whitelist,
+            :add_prometheus_manual_configuration
+
+          def initialize
+            super(nil)
+          end
+
+          def execute!
+            result = execute_steps
+
+            if result[:status] == :success
+              result
+            elsif STEPS_ALLOWED_TO_FAIL.include?(result[:failed_step])
+              success
+            else
+              raise StandardError, result[:message]
+            end
+          end
+
+          private
+
+          def validate_application_settings
+            return success if application_settings
+
+            log_error(_('No application_settings found'))
+            error(_('No application_settings found'))
+          end
+
+          def validate_project_created
+            return success unless project_created?
+
+            log_error(_('Project already created'))
+            error(_('Project already created'))
+          end
+
+          def validate_admins
+            unless instance_admins.any?
+              log_error(_('No active admin user found'))
+              return error(_('No active admin user found'))
+            end
+
+            success
+          end
+
+          def create_group
+            if project_created?
+              log_info(_('Instance administrators group already exists'))
+              @group = application_settings.instance_administration_project.owner
+              return success(group: @group)
+            end
+
+            @group = ::Groups::CreateService.new(group_owner, create_group_params).execute
+
+            if @group.persisted?
+              success(group: @group)
+            else
+              error(_('Could not create group'))
+            end
+          end
+
+          def create_project
+            if project_created?
+              log_info(_('Instance administration project already exists'))
+              @project = application_settings.instance_administration_project
+              return success(project: project)
+            end
+
+            @project = ::Projects::CreateService.new(group_owner, create_project_params).execute
+
+            if project.persisted?
+              success(project: project)
+            else
+              log_error(_("Could not create instance administration project. Errors: %{errors}") % { errors: project.errors.full_messages })
+              error(_('Could not create project'))
+            end
+          end
+
+          def save_project_id
+            return success if project_created?
+
+            result = application_settings.update(instance_administration_project_id: @project.id)
+
+            if result
+              success
+            else
+              log_error(_("Could not save instance administration project ID, errors: %{errors}") % { errors: application_settings.errors.full_messages })
+              error(_('Could not save project ID'))
+            end
+          end
+
+          def add_group_members
+            members = @group.add_users(members_to_add, Gitlab::Access::MAINTAINER)
+            errors = members.flat_map { |member| member.errors.full_messages }
+
+            if errors.any?
+              log_error(_('Could not add admins as members to self-monitoring project. Errors: %{errors}') % { errors: errors })
+              error(_('Could not add admins as members'))
+            else
+              success
+            end
+          end
+
+          def add_to_whitelist
+            return success unless prometheus_enabled?
+            return success unless prometheus_listen_address.present?
+
+            uri = parse_url(internal_prometheus_listen_address_uri)
+            return error(_('Prometheus listen_address is not a valid URI')) unless uri
+
+            application_settings.add_to_outbound_local_requests_whitelist([uri.normalized_host])
+            result = application_settings.save
+
+            if result
+              # Expire the Gitlab::CurrentSettings cache after updating the whitelist.
+              # This happens automatically in an after_commit hook, but in migrations,
+              # the after_commit hook only runs at the end of the migration.
+              Gitlab::CurrentSettings.expire_current_application_settings
+              success
+            else
+              log_error(_("Could not add prometheus URL to whitelist, errors: %{errors}") % { errors: application_settings.errors.full_messages })
+              error(_('Could not add prometheus URL to whitelist'))
+            end
+          end
+
+          def add_prometheus_manual_configuration
+            return success unless prometheus_enabled?
+            return success unless prometheus_listen_address.present?
+
+            service = project.find_or_initialize_service('prometheus')
+
+            unless service.update(prometheus_service_attributes)
+              log_error(_('Could not save prometheus manual configuration for self-monitoring project. Errors: %{errors}') % { errors: service.errors.full_messages })
+              return error(_('Could not save prometheus manual configuration'))
+            end
+
+            success
+          end
+
+          def application_settings
+            @application_settings ||= ApplicationSetting.current_without_cache
+          end
+
+          def project_created?
+            application_settings.instance_administration_project.present?
+          end
+
+          def parse_url(uri_string)
+            Addressable::URI.parse(uri_string)
+          rescue Addressable::URI::InvalidURIError, TypeError
+          end
+
+          def prometheus_enabled?
+            Gitlab.config.prometheus.enable
+          rescue Settingslogic::MissingSetting
+            log_error(_('prometheus.enable is not present in gitlab.yml'))
+
+            false
+          end
+
+          def prometheus_listen_address
+            Gitlab.config.prometheus.listen_address
+          rescue Settingslogic::MissingSetting
+            log_error(_('prometheus.listen_address is not present in gitlab.yml'))
+
+            nil
+          end
+
+          def instance_admins
+            @instance_admins ||= User.admins.active
+          end
+
+          def group_owner
+            instance_admins.first
+          end
+
+          def members_to_add
+            # Exclude admins who are already members of group because
+            # `@group.add_users(users)` returns an error if the users parameter contains
+            # users who are already members of the group.
+            instance_admins - @group.members.collect(&:user)
+          end
+
+          def create_group_params
+            {
+              name: 'GitLab Instance Administrators',
+              path: "gitlab-instance-administrators-#{SecureRandom.hex(4)}",
+              visibility_level: VISIBILITY_LEVEL
+            }
+          end
+
+          def docs_path
+            Rails.application.routes.url_helpers.help_page_path(
+              'administration/monitoring/gitlab_instance_administration_project/index'
+            )
+          end
+
+          def create_project_params
+            {
+              initialize_with_readme: true,
+              visibility_level: VISIBILITY_LEVEL,
+              name: PROJECT_NAME,
+              description: "This project is automatically generated and will be used to help monitor this GitLab instance. [More information](#{docs_path})",
+              namespace_id: @group.id
+            }
+          end
+
+          def internal_prometheus_listen_address_uri
+            if prometheus_listen_address.starts_with?('http')
+              prometheus_listen_address
+            else
+              'http://' + prometheus_listen_address
+            end
+          end
+
+          def prometheus_service_attributes
+            {
+              api_url: internal_prometheus_listen_address_uri,
+              manual_configuration: true,
+              active: true
+            }
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f8cebc22d934c425fdec869dd58212074b5c50c1..61642fbbd59778ca350271789865e8cfb01dfa20 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3353,6 +3353,12 @@ msgstr ""
 msgid "Copy token to clipboard"
 msgstr ""
 
+msgid "Could not add admins as members"
+msgstr ""
+
+msgid "Could not add admins as members to self-monitoring project. Errors: %{errors}"
+msgstr ""
+
 msgid "Could not add prometheus URL to whitelist"
 msgstr ""
 
@@ -3371,6 +3377,9 @@ msgstr ""
 msgid "Could not create Wiki Repository at this time. Please try again later."
 msgstr ""
 
+msgid "Could not create group"
+msgstr ""
+
 msgid "Could not create instance administration project. Errors: %{errors}"
 msgstr ""
 
@@ -3398,6 +3407,12 @@ msgstr ""
 msgid "Could not save project ID"
 msgstr ""
 
+msgid "Could not save prometheus manual configuration"
+msgstr ""
+
+msgid "Could not save prometheus manual configuration for self-monitoring project. Errors: %{errors}"
+msgstr ""
+
 msgid "Coverage"
 msgstr ""
 
@@ -7400,9 +7415,15 @@ msgstr ""
 msgid "No Tag"
 msgstr ""
 
+msgid "No active admin user found"
+msgstr ""
+
 msgid "No activities found"
 msgstr ""
 
+msgid "No application_settings found"
+msgstr ""
+
 msgid "No available namespaces to fork the project."
 msgstr ""
 
@@ -8746,6 +8767,9 @@ msgstr ""
 msgid "Project access must be granted explicitly to each user."
 msgstr ""
 
+msgid "Project already created"
+msgstr ""
+
 msgid "Project and wiki repositories"
 msgstr ""
 
@@ -13961,6 +13985,12 @@ msgstr ""
 msgid "project avatar"
 msgstr ""
 
+msgid "prometheus.enable is not present in gitlab.yml"
+msgstr ""
+
+msgid "prometheus.listen_address is not present in gitlab.yml"
+msgstr ""
+
 msgid "quick actions"
 msgstr ""
 
diff --git a/spec/services/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
similarity index 65%
rename from spec/services/self_monitoring/project/create_service_spec.rb
rename to spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
index def20448bd9976e2c0e254bcae001e48643b98af..9bd0d800086d9b4d98977c83d40d8ad838eec846 100644
--- a/spec/services/self_monitoring/project/create_service_spec.rb
+++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
@@ -2,29 +2,48 @@
 
 require 'spec_helper'
 
-describe SelfMonitoring::Project::CreateService do
+describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
   describe '#execute' do
-    let(:result) { subject.execute }
+    let(:result) { subject.execute! }
 
     let(:prometheus_settings) do
-      OpenStruct.new(
+      {
         enable: true,
         listen_address: 'localhost:9090'
-      )
+      }
     end
 
     before do
-      allow(Gitlab.config).to receive(:prometheus).and_return(prometheus_settings)
+      stub_config(prometheus: prometheus_settings)
+    end
+
+    context 'without application_settings' do
+      it 'does not fail' do
+        expect(subject).to receive(:log_error).and_call_original
+        expect(result).to eq(
+          status: :success
+        )
+
+        expect(Project.count).to eq(0)
+        expect(Group.count).to eq(0)
+      end
     end
 
     context 'without admin users' do
-      it 'returns error' do
+      let(:application_setting) { Gitlab::CurrentSettings.current_application_settings }
+
+      before do
+        allow(ApplicationSetting).to receive(:current_without_cache) { application_setting }
+      end
+
+      it 'does not fail' do
         expect(subject).to receive(:log_error).and_call_original
         expect(result).to eq(
-          status: :error,
-          message: 'No active admin user found',
-          failed_step: :validate_admins
+          status: :success
         )
+
+        expect(Project.count).to eq(0)
+        expect(Group.count).to eq(0)
       end
     end
 
@@ -36,6 +55,7 @@ describe SelfMonitoring::Project::CreateService do
       let!(:user) { create(:user, :admin) }
 
       before do
+        allow(ApplicationSetting).to receive(:current_without_cache) { application_setting }
         application_setting.allow_local_requests_from_web_hooks_and_services = true
       end
 
@@ -56,8 +76,8 @@ describe SelfMonitoring::Project::CreateService do
       it 'creates group' do
         expect(result[:status]).to eq(:success)
         expect(group).to be_persisted
-        expect(group.name).to eq(described_class::GROUP_NAME)
-        expect(group.path).to start_with(described_class::GROUP_PATH)
+        expect(group.name).to eq('GitLab Instance Administrators')
+        expect(group.path).to start_with('gitlab-instance-administrators')
         expect(group.path.split('-').last.length).to eq(8)
         expect(group.visibility_level).to eq(described_class::VISIBILITY_LEVEL)
       end
@@ -77,9 +97,16 @@ describe SelfMonitoring::Project::CreateService do
       end
 
       it 'creates project with correct name and description' do
+        path = 'administration/monitoring/gitlab_instance_administration_project/index'
+        docs_path = Rails.application.routes.url_helpers.help_page_path(path)
+
         expect(result[:status]).to eq(:success)
         expect(project.name).to eq(described_class::PROJECT_NAME)
-        expect(project.description).to eq(described_class::PROJECT_DESCRIPTION)
+        expect(project.description).to eq(
+          'This project is automatically generated and will be used to help monitor this GitLab instance. ' \
+          "[More information](#{docs_path})"
+        )
+        expect(File).to exist("doc/#{path}.md")
       end
 
       it 'adds all admins as maintainers' do
@@ -105,19 +132,30 @@ describe SelfMonitoring::Project::CreateService do
       it 'returns error when saving project ID fails' do
         allow(application_setting).to receive(:update) { false }
 
-        expect(result[:status]).to eq(:error)
-        expect(result[:failed_step]).to eq(:save_project_id)
-        expect(result[:message]).to eq('Could not save project ID')
+        expect { result }.to raise_error(StandardError, 'Could not save project ID')
       end
 
-      it 'does not fail when a project already exists' do
-        expect(result[:status]).to eq(:success)
+      context 'when project already exists' do
+        let(:existing_group) { create(:group) }
+        let(:existing_project) { create(:project, namespace: existing_group) }
 
-        second_result = subject.execute
+        before do
+          admin1 = create(:user, :admin)
+          admin2 = create(:user, :admin)
+
+          existing_group.add_owner(user)
+          existing_group.add_users([admin1, admin2], Gitlab::Access::MAINTAINER)
+
+          application_setting.instance_administration_project_id = existing_project.id
+        end
+
+        it 'does not fail' do
+          expect(subject).to receive(:log_error).and_call_original
+          expect(result[:status]).to eq(:success)
 
-        expect(second_result[:status]).to eq(:success)
-        expect(second_result[:project]).to eq(project)
-        expect(second_result[:group]).to eq(group)
+          expect(Project.count).to eq(1)
+          expect(Group.count).to eq(1)
+        end
       end
 
       context 'when local requests from hooks and services are not allowed' do
@@ -138,8 +176,11 @@ describe SelfMonitoring::Project::CreateService do
       end
 
       context 'with non default prometheus address' do
-        before do
-          prometheus_settings.listen_address = 'https://localhost:9090'
+        let(:prometheus_settings) do
+          {
+            enable: true,
+            listen_address: 'https://localhost:9090'
+          }
         end
 
         it_behaves_like 'has prometheus service', 'https://localhost:9090'
@@ -157,8 +198,11 @@ describe SelfMonitoring::Project::CreateService do
       end
 
       context 'when prometheus setting is disabled in gitlab.yml' do
-        before do
-          prometheus_settings.enable = false
+        let(:prometheus_settings) do
+          {
+            enable: false,
+            listen_address: 'http://localhost:9090'
+          }
         end
 
         it 'does not configure prometheus' do
@@ -168,9 +212,7 @@ describe SelfMonitoring::Project::CreateService do
       end
 
       context 'when prometheus listen address is blank in gitlab.yml' do
-        before do
-          prometheus_settings.listen_address = ''
-        end
+        let(:prometheus_settings) { { enable: true, listen_address: '' } }
 
         it 'does not configure prometheus' do
           expect(result).to include(status: :success)
@@ -192,11 +234,7 @@ describe SelfMonitoring::Project::CreateService do
 
         it 'returns error' do
           expect(subject).to receive(:log_error).and_call_original
-          expect(result).to eq({
-            status: :error,
-            message: 'Could not create project',
-            failed_step: :create_project
-          })
+          expect { result }.to raise_error(StandardError, 'Could not create project')
         end
       end
 
@@ -207,26 +245,21 @@ describe SelfMonitoring::Project::CreateService do
 
         it 'returns error' do
           expect(subject).to receive(:log_error).and_call_original
-          expect(result).to eq({
-            status: :error,
-            message: 'Could not add admins as members',
-            failed_step: :add_group_members
-          })
+          expect { result }.to raise_error(StandardError, 'Could not add admins as members')
         end
       end
 
       context 'when prometheus manual configuration cannot be saved' do
-        before do
-          prometheus_settings.listen_address = 'httpinvalid://localhost:9090'
+        let(:prometheus_settings) do
+          {
+            enable: true,
+            listen_address: 'httpinvalid://localhost:9090'
+          }
         end
 
         it 'returns error' do
           expect(subject).to receive(:log_error).and_call_original
-          expect(result).to eq(
-            status: :error,
-            message: 'Could not save prometheus manual configuration',
-            failed_step: :add_prometheus_manual_configuration
-          )
+          expect { result }.to raise_error(StandardError, 'Could not save prometheus manual configuration')
         end
       end
     end
diff --git a/spec/migrations/add_gitlab_instance_administration_project_spec.rb b/spec/migrations/add_gitlab_instance_administration_project_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..08e20a4e8ff100d07d42305a4d827393552fce42
--- /dev/null
+++ b/spec/migrations/add_gitlab_instance_administration_project_spec.rb
@@ -0,0 +1,252 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190801072937_add_gitlab_instance_administration_project.rb')
+
+describe AddGitlabInstanceAdministrationProject, :migration do
+  let(:application_settings) { table(:application_settings) }
+  let(:users)                { table(:users) }
+  let(:projects)             { table(:projects) }
+  let(:namespaces)           { table(:namespaces) }
+  let(:members)              { table(:members) }
+
+  let(:service_class) do
+    Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService
+  end
+
+  let(:prometheus_settings) do
+    {
+      enable: true,
+      listen_address: 'localhost:9090'
+    }
+  end
+
+  before do
+    stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+
+    stub_config(prometheus: prometheus_settings)
+  end
+
+  describe 'down' do
+    let!(:application_setting) { application_settings.create! }
+    let!(:user) { users.create!(admin: true, email: 'admin1@example.com', projects_limit: 10, state: :active) }
+
+    it 'deletes group and project' do
+      migrate!
+
+      expect(Project.count).to eq(1)
+      expect(Group.count).to eq(1)
+
+      schema_migrate_down!
+
+      expect(Project.count).to eq(0)
+      expect(Group.count).to eq(0)
+    end
+  end
+
+  describe 'up' do
+    context 'without application_settings' do
+      it 'does not fail' do
+        migrate!
+
+        expect(Project.count).to eq(0)
+      end
+    end
+
+    context 'without admin users' do
+      let!(:application_setting) { application_settings.create! }
+
+      it 'does not fail' do
+        migrate!
+
+        expect(Project.count).to eq(0)
+      end
+    end
+
+    context 'with admin users' do
+      let(:project) { Project.last }
+      let(:group) { Group.last }
+      let!(:application_setting) { application_settings.create! }
+      let!(:user) { users.create!(admin: true, email: 'admin1@example.com', projects_limit: 10, state: :active) }
+
+      before do
+        stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+      end
+
+      shared_examples 'has prometheus service' do |listen_address|
+        it do
+          migrate!
+
+          prometheus = project.prometheus_service
+          expect(prometheus).to be_persisted
+          expect(prometheus).not_to eq(nil)
+          expect(prometheus.api_url).to eq(listen_address)
+          expect(prometheus.active).to eq(true)
+          expect(prometheus.manual_configuration).to eq(true)
+        end
+      end
+
+      it_behaves_like 'has prometheus service', 'http://localhost:9090'
+
+      it 'creates GitLab Instance Administrator group' do
+        migrate!
+
+        expect(group).to be_persisted
+        expect(group.name).to eq('GitLab Instance Administrators')
+        expect(group.path).to start_with('gitlab-instance-administrators')
+        expect(group.path.split('-').last.length).to eq(8)
+        expect(group.visibility_level).to eq(service_class::VISIBILITY_LEVEL)
+      end
+
+      it 'creates project with internal visibility' do
+        migrate!
+
+        expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+        expect(project).to be_persisted
+      end
+
+      it 'creates project with correct name and description' do
+        migrate!
+
+        path = 'administration/monitoring/gitlab_instance_administration_project/index'
+        docs_path = Rails.application.routes.url_helpers.help_page_path(path)
+
+        expect(project.name).to eq(service_class::PROJECT_NAME)
+        expect(project.description).to eq(
+          'This project is automatically generated and will be used to help monitor this GitLab instance. ' \
+          "[More information](#{docs_path})"
+        )
+        expect(File).to exist("doc/#{path}.md")
+      end
+
+      it 'adds all admins as maintainers' do
+        admin1 = users.create!(admin: true, email: 'admin2@example.com', projects_limit: 10, state: :active)
+        admin2 = users.create!(admin: true, email: 'admin3@example.com', projects_limit: 10, state: :active)
+        users.create!(email: 'nonadmin1@example.com', projects_limit: 10, state: :active)
+
+        migrate!
+
+        expect(project.owner).to eq(group)
+        expect(group.members.collect(&:user).collect(&:id)).to contain_exactly(user.id, admin1.id, admin2.id)
+        expect(group.members.collect(&:access_level)).to contain_exactly(
+          Gitlab::Access::OWNER,
+          Gitlab::Access::MAINTAINER,
+          Gitlab::Access::MAINTAINER
+        )
+      end
+
+      it 'saves the project id' do
+        migrate!
+
+        application_setting.reload
+        expect(application_setting.instance_administration_project_id).to eq(project.id)
+      end
+
+      it 'does not fail when a project already exists' do
+        group = namespaces.create!(
+          path: 'gitlab-instance-administrators',
+          name: 'GitLab Instance Administrators',
+          type: 'Group'
+        )
+        project = projects.create!(
+          namespace_id: group.id,
+          name: 'GitLab Instance Administration'
+        )
+
+        admin1 = users.create!(admin: true, email: 'admin4@example.com', projects_limit: 10, state: :active)
+        admin2 = users.create!(admin: true, email: 'admin5@example.com', projects_limit: 10, state: :active)
+
+        members.create!(
+          user_id: admin1.id,
+          source_id: group.id,
+          source_type: 'Namespace',
+          type: 'GroupMember',
+          access_level: GroupMember::MAINTAINER,
+          notification_level: NotificationSetting.levels[:global]
+        )
+        members.create!(
+          user_id: admin2.id,
+          source_id: group.id,
+          source_type: 'Namespace',
+          type: 'GroupMember',
+          access_level: GroupMember::MAINTAINER,
+          notification_level: NotificationSetting.levels[:global]
+        )
+
+        stub_application_setting(instance_administration_project: project)
+
+        migrate!
+
+        expect(Project.last.id).to eq(project.id)
+        expect(Group.last.id).to eq(group.id)
+      end
+
+      context 'when local requests from hooks and services are not allowed' do
+        before do
+          stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
+        end
+
+        it_behaves_like 'has prometheus service', 'http://localhost:9090'
+
+        it 'does not overwrite the existing whitelist' do
+          application_setting.update!(outbound_local_requests_whitelist: ['example.com'])
+
+          migrate!
+
+          application_setting.reload
+          expect(application_setting.outbound_local_requests_whitelist).to contain_exactly(
+            'example.com', 'localhost'
+          )
+        end
+      end
+
+      context 'with non default prometheus address' do
+        let(:prometheus_settings) do
+          {
+            enable: true,
+            listen_address: 'https://localhost:9090'
+          }
+        end
+
+        it_behaves_like 'has prometheus service', 'https://localhost:9090'
+      end
+
+      context 'when prometheus setting is not present in gitlab.yml' do
+        before do
+          allow(Gitlab.config).to receive(:prometheus).and_raise(Settingslogic::MissingSetting)
+        end
+
+        it 'does not fail' do
+          migrate!
+
+          expect(project.prometheus_service).to be_nil
+        end
+      end
+
+      context 'when prometheus setting is disabled in gitlab.yml' do
+        let(:prometheus_settings) do
+          {
+            enable: false,
+            listen_address: 'localhost:9090'
+          }
+        end
+
+        it 'does not configure prometheus' do
+          migrate!
+
+          expect(project.prometheus_service).to be_nil
+        end
+      end
+
+      context 'when prometheus listen address is blank in gitlab.yml' do
+        let(:prometheus_settings) { { enable: true, listen_address: '' } }
+
+        it 'does not configure prometheus' do
+          migrate!
+
+          expect(project.prometheus_service).to be_nil
+        end
+      end
+    end
+  end
+end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index c8b2bf040e6911ee087e1fd22b6b58411b6ae9ba..dec7898d8d28ec2af7f97eaa03848cf7d803ee36 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -30,6 +30,10 @@ module StubConfiguration
     allow(Gitlab.config.gitlab).to receive_messages(to_settings(messages))
   end
 
+  def stub_config(messages)
+    allow(Gitlab.config).to receive_messages(to_settings(messages))
+  end
+
   def stub_default_url_options(host: "localhost", protocol: "http")
     url_options = { host: host, protocol: protocol }
     allow(Rails.application.routes).to receive(:default_url_options).and_return(url_options)