diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 3561839846841c7d36c298dcb3b5da2995f9d9a7..1325a2682143fca2400f914eca455e1d11f63401 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -99,12 +99,6 @@
           </p>
         `;
       },
-      gitlabRunnerDescription() {
-        return _.escape(s__(
-          `ClusterIntegration|GitLab Runner is the open source project that is used to run your jobs
-          and send the results back to GitLab.`,
-        ));
-      },
       prometheusDescription() {
         return sprintf(
           _.escape(s__(
@@ -256,6 +250,22 @@
           >
           </div>
         </application-row>
+        <application-row
+          id="runner"
+          :title="applications.runner.title"
+          title-link="https://docs.gitlab.com/runner/"
+          :status="applications.runner.status"
+          :status-reason="applications.runner.statusReason"
+          :request-status="applications.runner.requestStatus"
+          :request-reason="applications.runner.requestReason"
+        >
+          <div slot="description">
+            {{ s__(`ClusterIntegration|GitLab Runner connects to this
+              project's repository and executes CI/CD jobs,
+              pushing results back and deploying,
+              applications to production.`) }}
+          </div>
+        </application-row>
         <!--
           NOTE: Don't forget to update `clusters.scss`
           min-height for this block and uncomment `application_spec` tests
diff --git a/app/models/clusters/applications/helm.rb b/app/models/clusters/applications/helm.rb
index 193bb48e54d3b9dbb6a22eec7518e742bbc91ff3..58de344857763facb4cacb8e6bf9592ae1961658 100644
--- a/app/models/clusters/applications/helm.rb
+++ b/app/models/clusters/applications/helm.rb
@@ -15,7 +15,7 @@ module Clusters
       end
 
       def install_command
-        Gitlab::Kubernetes::Helm::InstallCommand.new(name, install_helm: true)
+        Gitlab::Kubernetes::Helm::InitCommand.new(name)
       end
     end
   end
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index 9f583342c199a7665118fdc3e9d6cbe94fafbaf0..27fc3b8546575cd063518d5913cb7c5d1773a1eb 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -5,6 +5,7 @@ module Clusters
 
       include ::Clusters::Concerns::ApplicationCore
       include ::Clusters::Concerns::ApplicationStatus
+      include ::Clusters::Concerns::ApplicationData
       include AfterCommitQueue
 
       default_value_for :ingress_type, :nginx
@@ -29,12 +30,12 @@ module Clusters
         'stable/nginx-ingress'
       end
 
-      def chart_values_file
-        "#{Rails.root}/vendor/#{name}/values.yaml"
-      end
-
       def install_command
-        Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file)
+        Gitlab::Kubernetes::Helm::InstallCommand.new(
+          name,
+          chart: chart,
+          values: values
+        )
       end
 
       def schedule_status_update
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index aa22e9d5d58158f40b8d3e1e7a1114f0de2560b6..89ebd63e605140fc27a058d5e407f674984d5af7 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -7,6 +7,7 @@ module Clusters
 
       include ::Clusters::Concerns::ApplicationCore
       include ::Clusters::Concerns::ApplicationStatus
+      include ::Clusters::Concerns::ApplicationData
 
       default_value_for :version, VERSION
 
@@ -30,12 +31,12 @@ module Clusters
         80
       end
 
-      def chart_values_file
-        "#{Rails.root}/vendor/#{name}/values.yaml"
-      end
-
       def install_command
-        Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file)
+        Gitlab::Kubernetes::Helm::InstallCommand.new(
+          name,
+          chart: chart,
+          values: values
+        )
       end
 
       def proxy_client
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7adf1663c352db091fb65ee9a5883466a0467b1c
--- /dev/null
+++ b/app/models/clusters/applications/runner.rb
@@ -0,0 +1,68 @@
+module Clusters
+  module Applications
+    class Runner < ActiveRecord::Base
+      VERSION = '0.1.13'.freeze
+
+      self.table_name = 'clusters_applications_runners'
+
+      include ::Clusters::Concerns::ApplicationCore
+      include ::Clusters::Concerns::ApplicationStatus
+      include ::Clusters::Concerns::ApplicationData
+
+      belongs_to :runner, class_name: 'Ci::Runner', foreign_key: :runner_id
+      delegate :project, to: :cluster
+
+      default_value_for :version, VERSION
+
+      def chart
+        "#{name}/gitlab-runner"
+      end
+
+      def repository
+        'https://charts.gitlab.io'
+      end
+
+      def values
+        content_values.to_yaml
+      end
+
+      def install_command
+        Gitlab::Kubernetes::Helm::InstallCommand.new(
+          name,
+          chart: chart,
+          values: values,
+          repository: repository
+        )
+      end
+
+      private
+
+      def ensure_runner
+        runner || create_and_assign_runner
+      end
+
+      def create_and_assign_runner
+        transaction do
+          project.runners.create!(name: 'kubernetes-cluster', tag_list: %w(kubernetes cluster)).tap do |runner|
+            update!(runner_id: runner.id)
+          end
+        end
+      end
+
+      def gitlab_url
+        Gitlab::Routing.url_helpers.root_url(only_path: false)
+      end
+
+      def specification
+        {
+          "gitlabUrl" => gitlab_url,
+          "runnerToken" => ensure_runner.token
+        }
+      end
+
+      def content_values
+        specification.merge(YAML.load_file(chart_values_file))
+      end
+    end
+  end
+end
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 8678f70f78ca15dc23e008f411a11546d8887dae..1c0046107d78aa6f0f2b24eb2dd019866bb6e9e4 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -7,7 +7,8 @@ module Clusters
     APPLICATIONS = {
       Applications::Helm.application_name => Applications::Helm,
       Applications::Ingress.application_name => Applications::Ingress,
-      Applications::Prometheus.application_name => Applications::Prometheus
+      Applications::Prometheus.application_name => Applications::Prometheus,
+      Applications::Runner.application_name => Applications::Runner
     }.freeze
 
     belongs_to :user
@@ -23,6 +24,7 @@ module Clusters
     has_one :application_helm, class_name: 'Clusters::Applications::Helm'
     has_one :application_ingress, class_name: 'Clusters::Applications::Ingress'
     has_one :application_prometheus, class_name: 'Clusters::Applications::Prometheus'
+    has_one :application_runner, class_name: 'Clusters::Applications::Runner'
 
     accepts_nested_attributes_for :provider_gcp, update_only: true
     accepts_nested_attributes_for :platform_kubernetes, update_only: true
@@ -68,7 +70,8 @@ module Clusters
       [
         application_helm || build_application_helm,
         application_ingress || build_application_ingress,
-        application_prometheus || build_application_prometheus
+        application_prometheus || build_application_prometheus,
+        application_runner || build_application_runner
       ]
     end
 
diff --git a/app/models/clusters/concerns/application_data.rb b/app/models/clusters/concerns/application_data.rb
new file mode 100644
index 0000000000000000000000000000000000000000..96ac757e99e6b296bad5be54efcf99abbd9605ef
--- /dev/null
+++ b/app/models/clusters/concerns/application_data.rb
@@ -0,0 +1,23 @@
+module Clusters
+  module Concerns
+    module ApplicationData
+      extend ActiveSupport::Concern
+
+      included do
+        def repository
+          nil
+        end
+
+        def values
+          File.read(chart_values_file)
+        end
+
+        private
+
+        def chart_values_file
+          "#{Rails.root}/vendor/#{name}/values.yaml"
+        end
+      end
+    end
+  end
+end
diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml
index 179c45a9867b9e7fdffeb3da6a2be17eabb14851..2ee0eafcf1af341df963ac2ce9360881d331a798 100644
--- a/app/views/projects/clusters/show.html.haml
+++ b/app/views/projects/clusters/show.html.haml
@@ -10,6 +10,7 @@
   install_helm_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :helm),
   install_ingress_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :ingress),
   install_prometheus_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :prometheus),
+  install_runner_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :runner),
   toggle_status: @cluster.enabled? ? 'true': 'false',
   cluster_status: @cluster.status_name,
   cluster_status_reason: @cluster.status_reason,
diff --git a/changelogs/unreleased/32831-single-deploy-of-runner-in-k8s-cluster.yml b/changelogs/unreleased/32831-single-deploy-of-runner-in-k8s-cluster.yml
new file mode 100644
index 0000000000000000000000000000000000000000..74675992105e75ba6bbba9aebc785b1f889f2be6
--- /dev/null
+++ b/changelogs/unreleased/32831-single-deploy-of-runner-in-k8s-cluster.yml
@@ -0,0 +1,5 @@
+---
+title: Allow installation of GitLab Runner with a single click
+merge_request: 17134
+author:
+type: added
diff --git a/db/migrate/20180214155405_create_clusters_applications_runners.rb b/db/migrate/20180214155405_create_clusters_applications_runners.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fc4c088133896f9ec4e705993b4c8f72ab8544c0
--- /dev/null
+++ b/db/migrate/20180214155405_create_clusters_applications_runners.rb
@@ -0,0 +1,32 @@
+class CreateClustersApplicationsRunners < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    create_table :clusters_applications_runners do |t|
+      t.references :cluster, null: false, foreign_key: { on_delete: :cascade }
+      t.references :runner, references: :ci_runners
+      t.index :runner_id
+      t.index :cluster_id, unique: true
+      t.integer :status, null: false
+      t.timestamps_with_timezone null: false
+      t.string :version, null: false
+      t.text :status_reason
+    end
+
+    add_concurrent_foreign_key :clusters_applications_runners, :ci_runners,
+      column: :runner_id,
+      on_delete: :nullify
+  end
+
+  def down
+    if foreign_keys_for(:clusters_applications_runners, :runner_id).any?
+      remove_foreign_key :clusters_applications_runners, column: :runner_id
+    end
+
+    drop_table :clusters_applications_runners
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 773cf8b4d3fab04f4a95cf3f00eaac0f66e33512..db8bafe967795f5ded08c8687cfb85f82735e88b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -582,6 +582,19 @@ ActiveRecord::Schema.define(version: 20180301084653) do
     t.datetime_with_timezone "updated_at", null: false
   end
 
+  create_table "clusters_applications_runners", force: :cascade do |t|
+    t.integer "cluster_id", null: false
+    t.integer "runner_id"
+    t.integer "status", null: false
+    t.datetime_with_timezone "created_at", null: false
+    t.datetime_with_timezone "updated_at", null: false
+    t.string "version", null: false
+    t.text "status_reason"
+  end
+
+  add_index "clusters_applications_runners", ["cluster_id"], name: "index_clusters_applications_runners_on_cluster_id", unique: true, using: :btree
+  add_index "clusters_applications_runners", ["runner_id"], name: "index_clusters_applications_runners_on_runner_id", using: :btree
+
   create_table "container_repositories", force: :cascade do |t|
     t.integer "project_id", null: false
     t.string "name", null: false
@@ -1988,6 +2001,8 @@ ActiveRecord::Schema.define(version: 20180301084653) do
   add_foreign_key "cluster_providers_gcp", "clusters", on_delete: :cascade
   add_foreign_key "clusters", "users", on_delete: :nullify
   add_foreign_key "clusters_applications_helm", "clusters", on_delete: :cascade
+  add_foreign_key "clusters_applications_runners", "ci_runners", column: "runner_id", name: "fk_02de2ded36", on_delete: :nullify
+  add_foreign_key "clusters_applications_runners", "clusters", on_delete: :cascade
   add_foreign_key "container_repositories", "projects"
   add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade
   add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index bbe25c2d911e43e6f4250bf8936624b0cfbb1907..4ac54f96aa21e8a3adfbcf7c83eae2b84987372f 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -120,6 +120,7 @@ added directly to your configured cluster. Those applications are needed for
 | [Helm Tiller](https://docs.helm.sh/) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It will be automatically installed as a dependency when you try to install a different app. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. |
 | [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. |
 | [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications |
+| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. |
 
 ## Getting the external IP address
 
diff --git a/lib/gitlab/kubernetes/config_map.rb b/lib/gitlab/kubernetes/config_map.rb
new file mode 100644
index 0000000000000000000000000000000000000000..95e1054919df7fec1ae1de1f2d7b38b791ee7440
--- /dev/null
+++ b/lib/gitlab/kubernetes/config_map.rb
@@ -0,0 +1,37 @@
+module Gitlab
+  module Kubernetes
+    class ConfigMap
+      def initialize(name, values)
+        @name = name
+        @values = values
+      end
+
+      def generate
+        resource = ::Kubeclient::Resource.new
+        resource.metadata = metadata
+        resource.data = { values: values }
+        resource
+      end
+
+      private
+
+      attr_reader :name, :values
+
+      def metadata
+        {
+          name: config_map_name,
+          namespace: namespace,
+          labels: { name: config_map_name }
+        }
+      end
+
+      def config_map_name
+        "values-content-configuration-#{name}"
+      end
+
+      def namespace
+        Gitlab::Kubernetes::Helm::NAMESPACE
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/kubernetes/helm/api.rb b/lib/gitlab/kubernetes/helm/api.rb
index 737081ddc5b42ed85331a744d26839a0c44316c3..2edd34109ba077d12aefe209bf3538629a0cd7e3 100644
--- a/lib/gitlab/kubernetes/helm/api.rb
+++ b/lib/gitlab/kubernetes/helm/api.rb
@@ -9,7 +9,8 @@ module Gitlab
 
         def install(command)
           @namespace.ensure_exists!
-          @kubeclient.create_pod(pod_resource(command))
+          create_config_map(command) if command.config_map?
+          @kubeclient.create_pod(command.pod_resource)
         end
 
         ##
@@ -33,8 +34,10 @@ module Gitlab
 
         private
 
-        def pod_resource(command)
-          Gitlab::Kubernetes::Helm::Pod.new(command, @namespace.name, @kubeclient).generate
+        def create_config_map(command)
+          command.config_map_resource.tap do |config_map_resource|
+            @kubeclient.create_config_map(config_map_resource)
+          end
         end
       end
     end
diff --git a/lib/gitlab/kubernetes/helm/base_command.rb b/lib/gitlab/kubernetes/helm/base_command.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6e4df05aa7efb1ee7f036a217814e6437fe0d0b7
--- /dev/null
+++ b/lib/gitlab/kubernetes/helm/base_command.rb
@@ -0,0 +1,40 @@
+module Gitlab
+  module Kubernetes
+    module Helm
+      class BaseCommand
+        attr_reader :name
+
+        def initialize(name)
+          @name = name
+        end
+
+        def pod_resource
+          Gitlab::Kubernetes::Helm::Pod.new(self, namespace).generate
+        end
+
+        def generate_script
+          <<~HEREDOC
+            set -eo pipefail
+            apk add -U ca-certificates openssl >/dev/null
+            wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v#{Gitlab::Kubernetes::Helm::HELM_VERSION}-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
+            mv /tmp/linux-amd64/helm /usr/bin/
+          HEREDOC
+        end
+
+        def config_map?
+          false
+        end
+
+        def pod_name
+          "install-#{name}"
+        end
+
+        private
+
+        def namespace
+          Gitlab::Kubernetes::Helm::NAMESPACE
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/kubernetes/helm/init_command.rb b/lib/gitlab/kubernetes/helm/init_command.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a02e64561f62bb147da57bb374edae4532504e61
--- /dev/null
+++ b/lib/gitlab/kubernetes/helm/init_command.rb
@@ -0,0 +1,19 @@
+module Gitlab
+  module Kubernetes
+    module Helm
+      class InitCommand < BaseCommand
+        def generate_script
+          super + [
+            init_helm_command
+          ].join("\n")
+        end
+
+        private
+
+        def init_helm_command
+          "helm init >/dev/null"
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/kubernetes/helm/install_command.rb b/lib/gitlab/kubernetes/helm/install_command.rb
index bf6981035f45990178b0ad5d362a5bffb2b3d975..30af3e97b4ac8421ea235be49508b6efba837423 100644
--- a/lib/gitlab/kubernetes/helm/install_command.rb
+++ b/lib/gitlab/kubernetes/helm/install_command.rb
@@ -1,54 +1,45 @@
 module Gitlab
   module Kubernetes
     module Helm
-      class InstallCommand
-        attr_reader :name, :install_helm, :chart, :chart_values_file
+      class InstallCommand < BaseCommand
+        attr_reader :name, :chart, :repository, :values
 
-        def initialize(name, install_helm: false, chart: false, chart_values_file: false)
+        def initialize(name, chart:, values:, repository: nil)
           @name = name
-          @install_helm = install_helm
           @chart = chart
-          @chart_values_file = chart_values_file
+          @values = values
+          @repository = repository
         end
 
-        def pod_name
-          "install-#{name}"
+        def generate_script
+          super + [
+            init_command,
+            repository_command,
+            script_command
+          ].compact.join("\n")
         end
 
-        def generate_script(namespace_name)
-          [
-            install_dps_command,
-            init_command,
-            complete_command(namespace_name)
-          ].join("\n")
+        def config_map?
+          true
+        end
+
+        def config_map_resource
+          Gitlab::Kubernetes::ConfigMap.new(name, values).generate
         end
 
         private
 
         def init_command
-          if install_helm
-            'helm init >/dev/null'
-          else
-            'helm init --client-only >/dev/null'
-          end
+          'helm init --client-only >/dev/null'
         end
 
-        def complete_command(namespace_name)
-          return unless chart
-
-          if chart_values_file
-            "helm install #{chart} --name #{name} --namespace #{namespace_name} -f /data/helm/#{name}/config/values.yaml >/dev/null"
-          else
-            "helm install #{chart} --name #{name} --namespace #{namespace_name} >/dev/null"
-          end
+        def repository_command
+          "helm repo add #{name} #{repository}" if repository
         end
 
-        def install_dps_command
+        def script_command
           <<~HEREDOC
-            set -eo pipefail
-            apk add -U ca-certificates openssl >/dev/null
-            wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v#{Gitlab::Kubernetes::Helm::HELM_VERSION}-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
-            mv /tmp/linux-amd64/helm /usr/bin/
+          helm install #{chart} --name #{name} --namespace #{Gitlab::Kubernetes::Helm::NAMESPACE} -f /data/helm/#{name}/config/values.yaml >/dev/null
           HEREDOC
         end
       end
diff --git a/lib/gitlab/kubernetes/helm/pod.rb b/lib/gitlab/kubernetes/helm/pod.rb
index ca5e06009fa318b6fc2ea50c51827c455758e8b8..1e12299eefdceec9000b790400c5899732b00546 100644
--- a/lib/gitlab/kubernetes/helm/pod.rb
+++ b/lib/gitlab/kubernetes/helm/pod.rb
@@ -2,18 +2,17 @@ module Gitlab
   module Kubernetes
     module Helm
       class Pod
-        def initialize(command, namespace_name, kubeclient)
+        def initialize(command, namespace_name)
           @command = command
           @namespace_name = namespace_name
-          @kubeclient = kubeclient
         end
 
         def generate
           spec = { containers: [container_specification], restartPolicy: 'Never' }
 
-          if command.chart_values_file
-            create_config_map
+          if command.config_map?
             spec[:volumes] = volumes_specification
+            spec[:containers][0][:volumeMounts] = volume_mounts_specification
           end
 
           ::Kubeclient::Resource.new(metadata: metadata, spec: spec)
@@ -21,18 +20,16 @@ module Gitlab
 
         private
 
-        attr_reader :command, :namespace_name, :kubeclient
+        attr_reader :command, :namespace_name, :kubeclient, :config_map
 
         def container_specification
-          container = {
+          {
             name: 'helm',
             image: 'alpine:3.6',
             env: generate_pod_env(command),
             command: %w(/bin/sh),
             args: %w(-c $(COMMAND_SCRIPT))
           }
-          container[:volumeMounts] = volume_mounts_specification if command.chart_values_file
-          container
         end
 
         def labels
@@ -50,13 +47,12 @@ module Gitlab
           }
         end
 
-        def volume_mounts_specification
-          [
-            {
-              name: 'configuration-volume',
-              mountPath: "/data/helm/#{command.name}/config"
-            }
-          ]
+        def generate_pod_env(command)
+          {
+            HELM_VERSION: Gitlab::Kubernetes::Helm::HELM_VERSION,
+            TILLER_NAMESPACE: namespace_name,
+            COMMAND_SCRIPT: command.generate_script
+          }.map { |key, value| { name: key, value: value } }
         end
 
         def volumes_specification
@@ -71,23 +67,13 @@ module Gitlab
           ]
         end
 
-        def generate_pod_env(command)
-          {
-            HELM_VERSION: Gitlab::Kubernetes::Helm::HELM_VERSION,
-            TILLER_NAMESPACE: namespace_name,
-            COMMAND_SCRIPT: command.generate_script(namespace_name)
-          }.map { |key, value| { name: key, value: value } }
-        end
-
-        def create_config_map
-          resource = ::Kubeclient::Resource.new
-          resource.metadata = {
-            name: "values-content-configuration-#{command.name}",
-            namespace: namespace_name,
-            labels: { name: "values-content-configuration-#{command.name}" }
-          }
-          resource.data = { values: File.read(command.chart_values_file) }
-          kubeclient.create_config_map(resource)
+        def volume_mounts_specification
+          [
+            {
+              name: 'configuration-volume',
+              mountPath: "/data/helm/#{command.name}/config"
+            }
+          ]
         end
       end
     end
diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb
index 775fbb3d27b93688a03e34c3972d88e2528ad4ca..3deca103578e9a693fd4710b51eaf0f06c6442e7 100644
--- a/spec/factories/clusters/applications/helm.rb
+++ b/spec/factories/clusters/applications/helm.rb
@@ -34,5 +34,6 @@ FactoryBot.define do
 
     factory :clusters_applications_ingress, class: Clusters::Applications::Ingress
     factory :clusters_applications_prometheus, class: Clusters::Applications::Prometheus
+    factory :clusters_applications_runner, class: Clusters::Applications::Runner
   end
 end
diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js
index dfb4cc1b9b1775f93351b14914d30de1c7dda51c..d546543d2735daaa939718c52887a4921b95b094 100644
--- a/spec/javascripts/clusters/components/applications_spec.js
+++ b/spec/javascripts/clusters/components/applications_spec.js
@@ -38,11 +38,9 @@ describe('Applications', () => {
       expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).toBeDefined();
     });
 
-    /* * /
     it('renders a row for GitLab Runner', () => {
       expect(vm.$el.querySelector('.js-cluster-application-row-runner')).toBeDefined();
     });
-    /* */
   });
 
   describe('Ingress application', () => {
diff --git a/spec/lib/gitlab/kubernetes/config_map_spec.rb b/spec/lib/gitlab/kubernetes/config_map_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..33dfa461202d2c40bf945d18326ee32169041f59
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/config_map_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::ConfigMap do
+  let(:kubeclient) { double('kubernetes client') }
+  let(:application) { create(:clusters_applications_prometheus) }
+  let(:config_map) { described_class.new(application.name, application.values) }
+  let(:namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
+
+  let(:metadata) do
+    {
+      name: "values-content-configuration-#{application.name}",
+      namespace: namespace,
+      labels: { name: "values-content-configuration-#{application.name}" }
+    }
+  end
+
+  describe '#generate' do
+    let(:resource) { ::Kubeclient::Resource.new(metadata: metadata, data: { values: application.values }) }
+    subject { config_map.generate }
+
+    it 'should build a Kubeclient Resource' do
+      is_expected.to eq(resource)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/kubernetes/helm/api_spec.rb b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
index 69112fe90b1adb10398615709d5f5a6c49c4e887..740466ea5cb6e4567a4b2f732da5802ec9b74d9f 100644
--- a/spec/lib/gitlab/kubernetes/helm/api_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
@@ -5,14 +5,21 @@ describe Gitlab::Kubernetes::Helm::Api do
   let(:helm) { described_class.new(client) }
   let(:gitlab_namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
   let(:namespace) { Gitlab::Kubernetes::Namespace.new(gitlab_namespace, client) }
-  let(:install_helm) { true }
-  let(:chart) { 'stable/a_chart' }
-  let(:application_name) { 'app_name' }
-  let(:command) { Gitlab::Kubernetes::Helm::InstallCommand.new(application_name, install_helm: install_helm, chart: chart) }
+  let(:application) { create(:clusters_applications_prometheus) }
+
+  let(:command) do
+    Gitlab::Kubernetes::Helm::InstallCommand.new(
+      application.name,
+      chart: application.chart,
+      values: application.values
+    )
+  end
+
   subject { helm }
 
   before do
     allow(Gitlab::Kubernetes::Namespace).to receive(:new).with(gitlab_namespace, client).and_return(namespace)
+    allow(client).to receive(:create_config_map)
   end
 
   describe '#initialize' do
@@ -26,6 +33,7 @@ describe Gitlab::Kubernetes::Helm::Api do
   describe '#install' do
     before do
       allow(client).to receive(:create_pod).and_return(nil)
+      allow(client).to receive(:create_config_map).and_return(nil)
       allow(namespace).to receive(:ensure_exists!).once
     end
 
@@ -35,6 +43,16 @@ describe Gitlab::Kubernetes::Helm::Api do
 
       subject.install(command)
     end
+
+    context 'with a ConfigMap' do
+      let(:resource) { Gitlab::Kubernetes::ConfigMap.new(application.name, application.values).generate }
+
+      it 'creates a ConfigMap on kubeclient' do
+        expect(client).to receive(:create_config_map).with(resource).once
+
+        subject.install(command)
+      end
+    end
   end
 
   describe '#installation_status' do
diff --git a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3cfdae794f611e139987e48edf961841192eee2d
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::Helm::BaseCommand do
+  let(:application) { create(:clusters_applications_helm) }
+  let(:base_command) { described_class.new(application.name) }
+
+  describe '#generate_script' do
+    let(:helm_version) { Gitlab::Kubernetes::Helm::HELM_VERSION }
+    let(:command) do
+      <<~HEREDOC
+         set -eo pipefail
+         apk add -U ca-certificates openssl >/dev/null
+         wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v#{helm_version}-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
+         mv /tmp/linux-amd64/helm /usr/bin/
+      HEREDOC
+    end
+
+    subject { base_command.generate_script }
+
+    it 'should return a command that prepares the environment for helm-cli' do
+      expect(subject).to eq(command)
+    end
+  end
+
+  describe '#pod_resource' do
+    subject { base_command.pod_resource }
+
+    it 'should returns a kubeclient resoure with pod content for application' do
+      is_expected.to be_an_instance_of ::Kubeclient::Resource
+    end
+  end
+
+  describe '#config_map?' do
+    subject { base_command.config_map? }
+
+    it { is_expected.to be_falsy }
+  end
+
+  describe '#pod_name' do
+    subject { base_command.pod_name }
+
+    it { is_expected.to eq('install-helm') }
+  end
+end
diff --git a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e6920b0a76fba76b50e553167b9aaa7a7d0f96f1
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::Helm::InitCommand do
+  let(:application) { create(:clusters_applications_helm) }
+  let(:init_command) { described_class.new(application.name) }
+
+  describe '#generate_script' do
+    let(:command) do
+      <<~MSG.chomp
+        set -eo pipefail
+        apk add -U ca-certificates openssl >/dev/null
+        wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
+        mv /tmp/linux-amd64/helm /usr/bin/
+        helm init >/dev/null
+      MSG
+    end
+
+    subject { init_command.generate_script }
+
+    it 'should return the appropriate command' do
+      is_expected.to eq(command)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
index 63997a40d52c705ddbd82647e6aaab88c5890fc9..137b8f718de292f823132d7264d846d8f11a5a64 100644
--- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
@@ -1,79 +1,56 @@
 require 'rails_helper'
 
 describe Gitlab::Kubernetes::Helm::InstallCommand do
-  let(:prometheus) { create(:clusters_applications_prometheus) }
-
-  describe "#initialize" do
-    context "With all the params" do
-      subject { described_class.new(prometheus.name, install_helm: true, chart: prometheus.chart, chart_values_file: prometheus.chart_values_file) }
-
-      it 'should assign all parameters' do
-        expect(subject.name).to eq(prometheus.name)
-        expect(subject.install_helm).to be_truthy
-        expect(subject.chart).to eq(prometheus.chart)
-        expect(subject.chart_values_file).to eq("#{Rails.root}/vendor/prometheus/values.yaml")
-      end
-    end
-
-    context 'when install_helm is not set' do
-      subject { described_class.new(prometheus.name, chart: prometheus.chart, chart_values_file: true) }
-
-      it 'should set install_helm as false' do
-        expect(subject.install_helm).to be_falsy
-      end
-    end
-
-    context 'when chart is not set' do
-      subject { described_class.new(prometheus.name, install_helm: true) }
+  let(:application) { create(:clusters_applications_prometheus) }
+  let(:namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
+
+  let(:install_command) do
+    described_class.new(
+      application.name,
+      chart: application.chart,
+      values: application.values
+    )
+  end
 
-      it 'should set chart as nil' do
-        expect(subject.chart).to be_falsy
-      end
+  describe '#generate_script' do
+    let(:command) do
+      <<~MSG
+      set -eo pipefail
+      apk add -U ca-certificates openssl >/dev/null
+      wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
+      mv /tmp/linux-amd64/helm /usr/bin/
+      helm init --client-only >/dev/null
+      helm install #{application.chart} --name #{application.name} --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null
+      MSG
     end
 
-    context 'when chart_values_file is not set' do
-      subject { described_class.new(prometheus.name, install_helm: true, chart: prometheus.chart) }
+    subject { install_command.generate_script }
 
-      it 'should set chart_values_file as nil' do
-        expect(subject.chart_values_file).to be_falsy
-      end
+    it 'should return appropriate command' do
+      is_expected.to eq(command)
     end
-  end
-
-  describe "#generate_script" do
-    let(:install_command) { described_class.new(prometheus.name, install_helm: install_helm) }
-    let(:client) { double('kubernetes client') }
-    let(:namespace) { Gitlab::Kubernetes::Namespace.new(Gitlab::Kubernetes::Helm::NAMESPACE, client) }
-    subject { install_command.send(:generate_script, namespace.name) }
 
-    context 'when install helm is true' do
-      let(:install_helm) { true }
-      let(:command) do
-        <<~MSG
-        set -eo pipefail
-        apk add -U ca-certificates openssl >/dev/null
-        wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
-        mv /tmp/linux-amd64/helm /usr/bin/
-
-        helm init >/dev/null
-        MSG
+    context 'with an application with a repository' do
+      let(:ci_runner) { create(:ci_runner) }
+      let(:application) { create(:clusters_applications_runner, runner: ci_runner) }
+      let(:install_command) do
+        described_class.new(
+          application.name,
+          chart: application.chart,
+          values: application.values,
+          repository: application.repository
+        )
       end
 
-      it 'should return appropriate command' do
-        is_expected.to eq(command)
-      end
-    end
-
-    context 'when install helm is false' do
-      let(:install_helm) { false }
       let(:command) do
         <<~MSG
         set -eo pipefail
         apk add -U ca-certificates openssl >/dev/null
         wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
         mv /tmp/linux-amd64/helm /usr/bin/
-
         helm init --client-only >/dev/null
+        helm repo add #{application.name} #{application.repository}
+        helm install #{application.chart} --name #{application.name} --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null
         MSG
       end
 
@@ -81,50 +58,29 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
         is_expected.to eq(command)
       end
     end
+  end
 
-    context 'when chart is present' do
-      let(:install_command) { described_class.new(prometheus.name, chart: prometheus.chart) }
-      let(:command) do
-        <<~MSG.chomp
-        set -eo pipefail
-        apk add -U ca-certificates openssl >/dev/null
-        wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
-        mv /tmp/linux-amd64/helm /usr/bin/
+  describe '#config_map?' do
+    subject { install_command.config_map? }
 
-        helm init --client-only >/dev/null
-        helm install #{prometheus.chart} --name #{prometheus.name} --namespace #{namespace.name} >/dev/null
-        MSG
-      end
+    it { is_expected.to be_truthy }
+  end
 
-      it 'should return appropriate command' do
-        is_expected.to eq(command)
-      end
+  describe '#config_map_resource' do
+    let(:metadata) do
+      {
+        name: "values-content-configuration-#{application.name}",
+        namespace: namespace,
+        labels: { name: "values-content-configuration-#{application.name}" }
+      }
     end
 
-    context 'when chart values file is present' do
-      let(:install_command) { described_class.new(prometheus.name, chart: prometheus.chart, chart_values_file: prometheus.chart_values_file) }
-      let(:command) do
-        <<~MSG.chomp
-        set -eo pipefail
-        apk add -U ca-certificates openssl >/dev/null
-        wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
-        mv /tmp/linux-amd64/helm /usr/bin/
+    let(:resource) { ::Kubeclient::Resource.new(metadata: metadata, data: { values: application.values }) }
 
-        helm init --client-only >/dev/null
-        helm install #{prometheus.chart} --name #{prometheus.name} --namespace #{namespace.name} -f /data/helm/#{prometheus.name}/config/values.yaml >/dev/null
-        MSG
-      end
+    subject { install_command.config_map_resource }
 
-      it 'should return appropriate command' do
-        is_expected.to eq(command)
-      end
+    it 'returns a KubeClient resource with config map content for the application' do
+      is_expected.to eq(resource)
     end
   end
-
-  describe "#pod_name" do
-    let(:install_command) { described_class.new(prometheus.name, install_helm: true, chart: prometheus.chart, chart_values_file: true) }
-    subject { install_command.send(:pod_name) }
-
-    it { is_expected.to eq('install-prometheus') }
-  end
 end
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index ebb6033f71ef013436d13fd39416cde4a26512f5..43adc80d576ff69dc1963e8f8d4c3236583c1297 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -5,13 +5,9 @@ describe Gitlab::Kubernetes::Helm::Pod do
     let(:cluster) { create(:cluster) }
     let(:app) {  create(:clusters_applications_prometheus, cluster: cluster) }
     let(:command) {  app.install_command }
-    let(:client) { double('kubernetes client') }
-    let(:namespace) { Gitlab::Kubernetes::Namespace.new(Gitlab::Kubernetes::Helm::NAMESPACE, client) }
-    subject { described_class.new(command, namespace.name, client) }
+    let(:namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
 
-    before do
-      allow(client).to receive(:create_config_map).and_return(nil)
-    end
+    subject { described_class.new(command, namespace) }
 
     shared_examples 'helm pod' do
       it 'should generate a Kubeclient::Resource' do
@@ -47,7 +43,7 @@ describe Gitlab::Kubernetes::Helm::Pod do
       end
     end
 
-    context 'with a configuration file' do
+    context 'with a install command' do
       it_behaves_like 'helm pod'
 
       it 'should include volumes for the container' do
@@ -62,14 +58,14 @@ describe Gitlab::Kubernetes::Helm::Pod do
       end
 
       it 'should mount configMap specification in the volume' do
-        spec = subject.generate.spec
-        expect(spec.volumes.first.configMap['name']).to eq("values-content-configuration-#{app.name}")
-        expect(spec.volumes.first.configMap['items'].first['key']).to eq('values')
-        expect(spec.volumes.first.configMap['items'].first['path']).to eq('values.yaml')
+        volume = subject.generate.spec.volumes.first
+        expect(volume.configMap['name']).to eq("values-content-configuration-#{app.name}")
+        expect(volume.configMap['items'].first['key']).to eq('values')
+        expect(volume.configMap['items'].first['path']).to eq('values.yaml')
       end
     end
 
-    context 'without a configuration file' do
+    context 'with a init command' do
       let(:app) { create(:clusters_applications_helm, cluster: cluster) }
 
       it_behaves_like 'helm pod'
diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb
index eb57abaf6efd69a2ecb0402c96b4b5b30eaf32af..ba7bad617b44215bd24abbf8ae4dff8a9ab1e8dd 100644
--- a/spec/models/clusters/applications/helm_spec.rb
+++ b/spec/models/clusters/applications/helm_spec.rb
@@ -1,102 +1,17 @@
 require 'rails_helper'
 
 describe Clusters::Applications::Helm do
-  it { is_expected.to belong_to(:cluster) }
-  it { is_expected.to validate_presence_of(:cluster) }
-
-  describe '#name' do
-    it 'is .application_name' do
-      expect(subject.name).to eq(described_class.application_name)
-    end
-
-    it 'is recorded in Clusters::Cluster::APPLICATIONS' do
-      expect(Clusters::Cluster::APPLICATIONS[subject.name]).to eq(described_class)
-    end
-  end
-
-  describe '#version' do
-    it 'defaults to Gitlab::Kubernetes::Helm::HELM_VERSION' do
-      expect(subject.version).to eq(Gitlab::Kubernetes::Helm::HELM_VERSION)
-    end
-  end
-
-  describe '#status' do
-    let(:cluster) { create(:cluster) }
-
-    subject { described_class.new(cluster: cluster) }
-
-    it 'defaults to :not_installable' do
-      expect(subject.status_name).to be(:not_installable)
-    end
-
-    context 'when platform kubernetes is defined' do
-      let(:cluster) { create(:cluster, :provided_by_gcp) }
-
-      it 'defaults to :installable' do
-        expect(subject.status_name).to be(:installable)
-      end
-    end
-  end
+  include_examples 'cluster application core specs', :clusters_applications_helm
 
   describe '#install_command' do
-    it 'has all the needed information' do
-      expect(subject.install_command).to have_attributes(name: subject.name, install_helm: true)
-    end
-  end
-
-  describe 'status state machine' do
-    describe '#make_installing' do
-      subject { create(:clusters_applications_helm, :scheduled) }
-
-      it 'is installing' do
-        subject.make_installing!
-
-        expect(subject).to be_installing
-      end
-    end
-
-    describe '#make_installed' do
-      subject { create(:clusters_applications_helm, :installing) }
-
-      it 'is installed' do
-        subject.make_installed
-
-        expect(subject).to be_installed
-      end
-    end
-
-    describe '#make_errored' do
-      subject { create(:clusters_applications_helm, :installing) }
-      let(:reason) { 'some errors' }
-
-      it 'is errored' do
-        subject.make_errored(reason)
-
-        expect(subject).to be_errored
-        expect(subject.status_reason).to eq(reason)
-      end
-    end
-
-    describe '#make_scheduled' do
-      subject { create(:clusters_applications_helm, :installable) }
-
-      it 'is scheduled' do
-        subject.make_scheduled
-
-        expect(subject).to be_scheduled
-      end
-
-      describe 'when was errored' do
-        subject { create(:clusters_applications_helm, :errored) }
+    let(:helm) { create(:clusters_applications_helm) }
 
-        it 'clears #status_reason' do
-          expect(subject.status_reason).not_to be_nil
+    subject { helm.install_command }
 
-          subject.make_scheduled!
+    it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InitCommand) }
 
-          expect(subject.status_reason).to be_nil
-        end
-      end
+    it 'should be initialized with 1 arguments' do
+      expect(subject.name).to eq('helm')
     end
   end
 end
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index a34f4ff2b4869551d70dc8d3a237ef90768869af..03f5b88a5250e7bc9a1923bd6f3cbc7b2adc531f 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -1,16 +1,16 @@
 require 'rails_helper'
 
 describe Clusters::Applications::Ingress do
-  it { is_expected.to belong_to(:cluster) }
-  it { is_expected.to validate_presence_of(:cluster) }
+  let(:ingress) { create(:clusters_applications_ingress) }
+
+  include_examples 'cluster application core specs', :clusters_applications_ingress
+  include_examples 'cluster application status specs', :cluster_application_ingress
 
   before do
     allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
     allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
   end
 
-  include_examples 'cluster application specs', described_class
-
   describe '#make_installed!' do
     before do
       application.make_installed!
@@ -52,4 +52,27 @@ describe Clusters::Applications::Ingress do
       end
     end
   end
+
+  describe '#install_command' do
+    subject { ingress.install_command }
+
+    it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
+
+    it 'should be initialized with ingress arguments' do
+      expect(subject.name).to eq('ingress')
+      expect(subject.chart).to eq('stable/nginx-ingress')
+      expect(subject.values).to eq(ingress.values)
+    end
+  end
+
+  describe '#values' do
+    subject { ingress.values }
+
+    it 'should include ingress valid keys' do
+      is_expected.to include('image')
+      is_expected.to include('repository')
+      is_expected.to include('stats')
+      is_expected.to include('podAnnotations')
+    end
+  end
 end
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index 01037919530acc3745471644b29c9da2a668539b..df8a508e02195fee7483446134d79f8e3f48f8d4 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -1,10 +1,8 @@
 require 'rails_helper'
 
 describe Clusters::Applications::Prometheus do
-  it { is_expected.to belong_to(:cluster) }
-  it { is_expected.to validate_presence_of(:cluster) }
-
-  include_examples 'cluster application specs', described_class
+  include_examples 'cluster application core specs', :clusters_applications_prometheus
+  include_examples 'cluster application status specs', :cluster_application_prometheus
 
   describe 'transition to installed' do
     let(:project) { create(:project) }
@@ -24,14 +22,6 @@ describe Clusters::Applications::Prometheus do
     end
   end
 
-  describe "#chart_values_file" do
-    subject { create(:clusters_applications_prometheus).chart_values_file }
-
-    it 'should return chart values file path' do
-      expect(subject).to eq("#{Rails.root}/vendor/prometheus/values.yaml")
-    end
-  end
-
   describe '#proxy_client' do
     context 'cluster is nil' do
       it 'returns nil' do
@@ -85,4 +75,33 @@ describe Clusters::Applications::Prometheus do
       end
     end
   end
+
+  describe '#install_command' do
+    let(:kubeclient) { double('kubernetes client') }
+    let(:prometheus) { create(:clusters_applications_prometheus) }
+
+    subject { prometheus.install_command }
+
+    it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
+
+    it 'should be initialized with 3 arguments' do
+      expect(subject.name).to eq('prometheus')
+      expect(subject.chart).to eq('stable/prometheus')
+      expect(subject.values).to eq(prometheus.values)
+    end
+  end
+
+  describe '#values' do
+    let(:prometheus) { create(:clusters_applications_prometheus) }
+
+    subject { prometheus.values }
+
+    it 'should include prometheus valid values' do
+      is_expected.to include('alertmanager')
+      is_expected.to include('kubeStateMetrics')
+      is_expected.to include('nodeExporter')
+      is_expected.to include('pushgateway')
+      is_expected.to include('serverFiles')
+    end
+  end
 end
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..612a3c8e413307862cec4a3ef3546db36f3fbf81
--- /dev/null
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -0,0 +1,69 @@
+require 'rails_helper'
+
+describe Clusters::Applications::Runner do
+  let(:ci_runner) { create(:ci_runner) }
+
+  include_examples 'cluster application core specs', :clusters_applications_runner
+  include_examples 'cluster application status specs', :cluster_application_runner
+
+  it { is_expected.to belong_to(:runner) }
+
+  describe '#install_command' do
+    let(:kubeclient) { double('kubernetes client') }
+    let(:gitlab_runner) { create(:clusters_applications_runner, runner: ci_runner) }
+
+    subject { gitlab_runner.install_command }
+
+    it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
+
+    it 'should be initialized with 4 arguments' do
+      expect(subject.name).to eq('runner')
+      expect(subject.chart).to eq('runner/gitlab-runner')
+      expect(subject.repository).to eq('https://charts.gitlab.io')
+      expect(subject.values).to eq(gitlab_runner.values)
+    end
+  end
+
+  describe '#values' do
+    let(:gitlab_runner) { create(:clusters_applications_runner, runner: ci_runner) }
+
+    subject { gitlab_runner.values }
+
+    it 'should include runner valid values' do
+      is_expected.to include('concurrent')
+      is_expected.to include('checkInterval')
+      is_expected.to include('rbac')
+      is_expected.to include('runners')
+      is_expected.to include('resources')
+      is_expected.to include("runnerToken: #{ci_runner.token}")
+      is_expected.to include("gitlabUrl: #{Gitlab::Routing.url_helpers.root_url}")
+    end
+
+    context 'without a runner' do
+      let(:project) { create(:project) }
+      let(:cluster) { create(:cluster) }
+      let(:gitlab_runner) { create(:clusters_applications_runner, cluster: cluster) }
+
+      before do
+        cluster.projects << project
+      end
+
+      it 'creates a runner' do
+        expect do
+          subject
+        end.to change { Ci::Runner.count }.by(1)
+      end
+
+      it 'uses the new runner token' do
+        expect(subject).to include("runnerToken: #{gitlab_runner.reload.runner.token}")
+      end
+
+      it 'assigns the new runner to runner' do
+        subject
+        gitlab_runner.reload
+
+        expect(gitlab_runner.runner).not_to be_nil
+      end
+    end
+  end
+end
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 799d7ced1164cc13e10d95c3dbe1cbf664c397af..8f12a0e308553ed09a7f24492e5bd998e5b02267 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -8,6 +8,7 @@ describe Clusters::Cluster do
   it { is_expected.to have_one(:application_helm) }
   it { is_expected.to have_one(:application_ingress) }
   it { is_expected.to have_one(:application_prometheus) }
+  it { is_expected.to have_one(:application_runner) }
   it { is_expected.to delegate_method(:status).to(:provider) }
   it { is_expected.to delegate_method(:status_reason).to(:provider) }
   it { is_expected.to delegate_method(:status_name).to(:provider) }
@@ -196,9 +197,10 @@ describe Clusters::Cluster do
       let!(:helm) { create(:clusters_applications_helm, cluster: cluster) }
       let!(:ingress) { create(:clusters_applications_ingress, cluster: cluster) }
       let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) }
+      let!(:runner) { create(:clusters_applications_runner, cluster: cluster) }
 
       it 'returns a list of created applications' do
-        is_expected.to contain_exactly(helm, ingress, prometheus)
+        is_expected.to contain_exactly(helm, ingress, prometheus, runner)
       end
     end
   end
diff --git a/spec/support/cluster_application_spec.rb b/spec/support/cluster_application_spec.rb
deleted file mode 100644
index ab77910a0505cf1c53ad0d20957e638e143f87e7..0000000000000000000000000000000000000000
--- a/spec/support/cluster_application_spec.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-shared_examples 'cluster application specs' do
-  let(:factory_name) { described_class.to_s.downcase.gsub("::", "_") }
-
-  describe '#name' do
-    it 'is .application_name' do
-      expect(subject.name).to eq(described_class.application_name)
-    end
-
-    it 'is recorded in Clusters::Cluster::APPLICATIONS' do
-      expect(Clusters::Cluster::APPLICATIONS[subject.name]).to eq(described_class)
-    end
-  end
-
-  describe '#status' do
-    let(:cluster) { create(:cluster, :provided_by_gcp) }
-
-    subject { described_class.new(cluster: cluster) }
-
-    it 'defaults to :not_installable' do
-      expect(subject.status_name).to be(:not_installable)
-    end
-
-    context 'when application helm is scheduled' do
-      before do
-        create(factory_name, :scheduled, cluster: cluster)
-      end
-
-      it 'defaults to :not_installable' do
-        expect(subject.status_name).to be(:not_installable)
-      end
-    end
-
-    context 'when application helm is installed' do
-      before do
-        create(:clusters_applications_helm, :installed, cluster: cluster)
-      end
-
-      it 'defaults to :installable' do
-        expect(subject.status_name).to be(:installable)
-      end
-    end
-  end
-
-  describe '#install_command' do
-    it 'has all the needed information' do
-      expect(subject.install_command).to have_attributes(name: subject.name, install_helm: false)
-    end
-  end
-
-  describe 'status state machine' do
-    describe '#make_installing' do
-      subject { create(factory_name, :scheduled) }
-
-      it 'is installing' do
-        subject.make_installing!
-
-        expect(subject).to be_installing
-      end
-    end
-
-    describe '#make_installed' do
-      subject { create(factory_name, :installing) }
-
-      it 'is installed' do
-        subject.make_installed
-
-        expect(subject).to be_installed
-      end
-    end
-
-    describe '#make_errored' do
-      subject { create(factory_name, :installing) }
-      let(:reason) { 'some errors' }
-
-      it 'is errored' do
-        subject.make_errored(reason)
-
-        expect(subject).to be_errored
-        expect(subject.status_reason).to eq(reason)
-      end
-    end
-
-    describe '#make_scheduled' do
-      subject { create(factory_name, :installable) }
-
-      it 'is scheduled' do
-        subject.make_scheduled
-
-        expect(subject).to be_scheduled
-      end
-
-      describe 'when was errored' do
-        subject { create(factory_name, :errored) }
-
-        it 'clears #status_reason' do
-          expect(subject.status_reason).not_to be_nil
-
-          subject.make_scheduled!
-
-          expect(subject.status_reason).to be_nil
-        end
-      end
-    end
-  end
-end
diff --git a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..87d12a784ba1719b7d2272a596419710fc763b02
--- /dev/null
+++ b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
@@ -0,0 +1,70 @@
+shared_examples 'cluster application core specs' do |application_name|
+  it { is_expected.to belong_to(:cluster) }
+  it { is_expected.to validate_presence_of(:cluster) }
+
+  describe '#name' do
+    it 'is .application_name' do
+      expect(subject.name).to eq(described_class.application_name)
+    end
+
+    it 'is recorded in Clusters::Cluster::APPLICATIONS' do
+      expect(Clusters::Cluster::APPLICATIONS[subject.name]).to eq(described_class)
+    end
+  end
+
+  describe 'status state machine' do
+    describe '#make_installing' do
+      subject { create(application_name, :scheduled) }
+
+      it 'is installing' do
+        subject.make_installing!
+
+        expect(subject).to be_installing
+      end
+    end
+
+    describe '#make_installed' do
+      subject { create(application_name, :installing) }
+
+      it 'is installed' do
+        subject.make_installed
+
+        expect(subject).to be_installed
+      end
+    end
+
+    describe '#make_errored' do
+      subject { create(application_name, :installing) }
+      let(:reason) { 'some errors' }
+
+      it 'is errored' do
+        subject.make_errored(reason)
+
+        expect(subject).to be_errored
+        expect(subject.status_reason).to eq(reason)
+      end
+    end
+
+    describe '#make_scheduled' do
+      subject { create(application_name, :installable) }
+
+      it 'is scheduled' do
+        subject.make_scheduled
+
+        expect(subject).to be_scheduled
+      end
+
+      describe 'when was errored' do
+        subject { create(application_name, :errored) }
+
+        it 'clears #status_reason' do
+          expect(subject.status_reason).not_to be_nil
+
+          subject.make_scheduled!
+
+          expect(subject.status_reason).to be_nil
+        end
+      end
+    end
+  end
+end
diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..765dd32f4babdd0726d3a960dd1f38981c74be1d
--- /dev/null
+++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
@@ -0,0 +1,31 @@
+shared_examples 'cluster application status specs' do |application_name|
+  describe '#status' do
+    let(:cluster) { create(:cluster, :provided_by_gcp) }
+
+    subject { described_class.new(cluster: cluster) }
+
+    it 'sets a default status' do
+      expect(subject.status_name).to be(:not_installable)
+    end
+
+    context 'when application helm is scheduled' do
+      before do
+        create(:clusters_applications_helm, :scheduled, cluster: cluster)
+      end
+
+      it 'defaults to :not_installable' do
+        expect(subject.status_name).to be(:not_installable)
+      end
+    end
+
+    context 'when application is scheduled' do
+      before do
+        create(:clusters_applications_helm, :installed, cluster: cluster)
+      end
+
+      it 'sets a default status' do
+        expect(subject.status_name).to be(:installable)
+      end
+    end
+  end
+end
diff --git a/vendor/runner/values.yaml b/vendor/runner/values.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..b7e2e24acafe0270d5987b50f25a53dd0167022f
--- /dev/null
+++ b/vendor/runner/values.yaml
@@ -0,0 +1,25 @@
+## Configure the maximum number of concurrent jobs
+## - Documentation: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section
+## - Default value: 10
+## - Currently don't support auto-scaling.
+concurrent: 4
+
+## Defines in seconds how often to check GitLab for a new builds
+## - Documentation: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section
+## - Default value: 3
+checkInterval: 3
+
+## For RBAC support
+rbac:
+  create: false
+  clusterWideAccess: false
+
+## Configuration for the Pods that that the runner launches for each new job
+##
+runners:
+  image: ubuntu:16.04
+  privileged: false
+  builds: {}
+  services: {}
+  helpers: {}
+resources: {}