diff --git a/app/models/environment.rb b/app/models/environment.rb
index 5278efd71d2b008de8d132f118b6e2b8f2a5c395..a7f4156fc2ef53a71eef7ec82ee2e9b415cc1d48 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -19,7 +19,7 @@ class Environment < ActiveRecord::Base
             allow_nil: true,
             addressable_url: true
 
-  delegate :stop_action, to: :last_deployment, allow_nil: true
+  delegate :stop_action, :manual_actions, to: :last_deployment, allow_nil: true
 
   scope :available, -> { with_state(:available) }
   scope :stopped, -> { with_state(:stopped) }
@@ -99,4 +99,12 @@ class Environment < ActiveRecord::Base
     stop
     stop_action.play(current_user)
   end
+
+  def actions_for(environment)
+    return [] unless manual_actions
+
+    manual_actions.select do |action|
+      action.expanded_environment_name == environment
+    end
+  end
 end
diff --git a/changelogs/unreleased/chatops-deploy-command.yml b/changelogs/unreleased/chatops-deploy-command.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1e5a3e8df1568eebc74bfa2668c5cde26693e560
--- /dev/null
+++ b/changelogs/unreleased/chatops-deploy-command.yml
@@ -0,0 +1,4 @@
+---
+title: Add deployment command to ChatOps
+merge_request: 7619
+author: 
diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb
index 5f131703d40ea8c52765f9504e8231d867c6ac3d..0ec358debc7d4df00f5d1733b95e64f990f4a93c 100644
--- a/lib/gitlab/chat_commands/command.rb
+++ b/lib/gitlab/chat_commands/command.rb
@@ -4,6 +4,7 @@ module Gitlab
       COMMANDS = [
         Gitlab::ChatCommands::IssueShow,
         Gitlab::ChatCommands::IssueCreate,
+        Gitlab::ChatCommands::Deploy,
       ].freeze
 
       def execute
diff --git a/lib/gitlab/chat_commands/deploy.rb b/lib/gitlab/chat_commands/deploy.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c0b93cca68c1016b846a17f4dcab2ff8bf1325b2
--- /dev/null
+++ b/lib/gitlab/chat_commands/deploy.rb
@@ -0,0 +1,44 @@
+module Gitlab
+  module ChatCommands
+    class Deploy < BaseCommand
+      def self.match(text)
+        /\Adeploy\s+(?<from>.*)\s+to+\s+(?<to>.*)\z/.match(text)
+      end
+
+      def self.help_message
+        'deploy <environment> to <target-environment>'
+      end
+
+      def self.available?(project)
+        project.builds_enabled?
+      end
+
+      def self.allowed?(project, user)
+        can?(user, :create_deployment, project)
+      end
+
+      def execute(match)
+        from = match[:from]
+        to = match[:to]
+
+        actions = find_actions(from, to)
+        return unless actions.present?
+
+        if actions.one?
+          actions.first.play(current_user)
+        else
+          Result.new(:error, 'Too many actions defined')
+        end
+      end
+
+      private
+
+      def find_actions(from, to)
+        environment = project.environments.find_by(name: from)
+        return unless environment
+
+        environment.actions_for(to).select(&:starts_environment?)
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/chat_commands/result.rb b/lib/gitlab/chat_commands/result.rb
new file mode 100644
index 0000000000000000000000000000000000000000..324d7ef43a3bc30d5165770d326fe7447699b195
--- /dev/null
+++ b/lib/gitlab/chat_commands/result.rb
@@ -0,0 +1,5 @@
+module Gitlab
+  module ChatCommands
+    Result = Struct.new(:type, :message)
+  end
+end
diff --git a/lib/mattermost/presenter.rb b/lib/mattermost/presenter.rb
index bfbb089eb02d4bce220ad9639ca06f453e4f20a7..6b12081575da9b7d4a9e83ac76715018af008130 100644
--- a/lib/mattermost/presenter.rb
+++ b/lib/mattermost/presenter.rb
@@ -24,20 +24,22 @@ module Mattermost
         end
       end
 
-      def present(resource)
-        return not_found unless resource
-
-        if resource.respond_to?(:count)
-          if resource.count > 1
-            return multiple_resources(resource)
-          elsif resource.count == 0
-            return not_found
+      def present(subject)
+        return not_found unless subject
+
+        if subject.is_a?(Gitlab::ChatCommands::Result)
+          show_result(subject)
+        elsif subject.respond_to?(:count)
+          if subject.many?
+            multiple_resources(subject)
+          elsif subject.none?
+            not_found
           else
-            resource = resource.first
+            single_resource(subject)
           end
+        else
+          single_resource(subject)
         end
-
-        single_resource(resource)
       end
 
       def access_denied
@@ -46,6 +48,10 @@ module Mattermost
 
       private
 
+      def show_result(result)
+        ephemeral_response(result.message)
+      end
+
       def not_found
         ephemeral_response("404 not found! GitLab couldn't find what you were looking for! :boom:")
       end
@@ -54,7 +60,7 @@ module Mattermost
         return error(resource) if resource.errors.any? || !resource.persisted?
 
         message = "### #{title(resource)}"
-        message << "\n\n#{resource.description}" if resource.description
+        message << "\n\n#{resource.description}" if resource.try(:description)
 
         in_channel_response(message)
       end
@@ -74,7 +80,10 @@ module Mattermost
       end
 
       def title(resource)
-        "[#{resource.to_reference} #{resource.title}](#{url(resource)})"
+        reference = resource.try(:to_reference) || resource.try(:id)
+        title = resource.try(:title) || resource.try(:name)
+
+        "[#{reference} #{title}](#{url(resource)})"
       end
 
       def header_with_list(header, items)
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 0c93bbdfe26ebfb2a0ca980f86700a93aaff09e5..eb20bd7dd583012a58ab0fcf0b5f82764db52ac8 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -55,6 +55,12 @@ FactoryGirl.define do
       self.when 'manual'
     end
 
+    trait :teardown_environment do
+      options do
+        { environment: { action: 'stop' } }
+      end
+    end
+
     trait :allowed_to_fail do
       allow_failure true
     end
diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb
index 8cedbb0240f2df389114544df82673f79150e269..924b4b7b10130f2a3b03cc581a6bfe80cb258ab0 100644
--- a/spec/lib/gitlab/chat_commands/command_spec.rb
+++ b/spec/lib/gitlab/chat_commands/command_spec.rb
@@ -4,9 +4,9 @@ describe Gitlab::ChatCommands::Command, service: true do
   let(:project) { create(:empty_project) }
   let(:user) { create(:user) }
 
-  subject { described_class.new(project, user, params).execute }
-
   describe '#execute' do
+    subject { described_class.new(project, user, params).execute }
+
     context 'when no command is available' do
       let(:params) { { text: 'issue show 1' } }
       let(:project) { create(:project, has_external_issue_tracker: true) }
@@ -51,5 +51,44 @@ describe Gitlab::ChatCommands::Command, service: true do
         expect(subject[:text]).to match(/\/issues\/\d+/)
       end
     end
+
+    context 'when trying to do deployment' do
+      let(:params) { { text: 'deploy staging to production' } }
+      let!(:build) { create(:ci_build, project: project) }
+      let!(:staging) { create(:environment, name: 'staging', project: project) }
+      let!(:deployment) { create(:deployment, environment: staging, deployable: build) }
+      let!(:manual) do
+        create(:ci_build, :manual, project: project, pipeline: build.pipeline, name: 'first', environment: 'production')
+      end
+
+      context 'and user can not create deployment' do
+        it 'returns action' do
+          expect(subject[:response_type]).to be(:ephemeral)
+          expect(subject[:text]).to start_with('Whoops! That action is not allowed')
+        end
+      end
+
+      context 'and user does have deployment permission' do
+        before do
+          project.team << [user, :developer]
+        end
+
+        it 'returns action' do
+          expect(subject[:text]).to include(manual.name)
+          expect(subject[:response_type]).to be(:in_channel)
+        end
+
+        context 'when duplicate action exists' do
+          let!(:manual2) do
+            create(:ci_build, :manual, project: project, pipeline: build.pipeline, name: 'second', environment: 'production')
+          end
+
+          it 'returns error' do
+            expect(subject[:response_type]).to be(:ephemeral)
+            expect(subject[:text]).to include('Too many actions defined')
+          end
+        end
+      end
+    end
   end
 end
diff --git a/spec/lib/gitlab/chat_commands/deploy_spec.rb b/spec/lib/gitlab/chat_commands/deploy_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..26741367e632fd38d296ec32b4663ed710b5ba8c
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/deploy_spec.rb
@@ -0,0 +1,76 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Deploy, service: true do
+  describe '#execute' do
+    let(:project) { create(:empty_project) }
+    let(:user) { create(:user) }
+    let(:regex_match) { described_class.match('deploy staging to production') }
+
+    before do
+      project.team << [user, :master]
+    end
+
+    subject do
+      described_class.new(project, user).execute(regex_match)
+    end
+
+    context 'if no environment is defined' do
+      it 'returns nil' do
+        expect(subject).to be_nil
+      end
+    end
+
+    context 'with environment' do
+      let!(:staging) { create(:environment, name: 'staging', project: project) }
+      let!(:build) { create(:ci_build, project: project) }
+      let!(:deployment) { create(:deployment, environment: staging, deployable: build) }
+
+      context 'without actions' do
+        it 'returns nil' do
+          expect(subject).to be_nil
+        end
+      end
+
+      context 'with action' do
+        let!(:manual1) do
+          create(:ci_build, :manual, project: project, pipeline: build.pipeline, name: 'first', environment: 'production')
+        end
+
+        it 'returns action' do
+          expect(subject).to eq(manual1)
+        end
+
+        context 'when duplicate action exists' do
+          let!(:manual2) do
+            create(:ci_build, :manual, project: project, pipeline: build.pipeline, name: 'second', environment: 'production')
+          end
+
+          it 'returns error' do
+            expect(subject.message).to eq('Too many actions defined')
+          end
+        end
+
+        context 'when teardown action exists' do
+          let!(:teardown) do
+            create(:ci_build, :manual, :teardown_environment,
+                   project: project, pipeline: build.pipeline,
+                   name: 'teardown', environment: 'production')
+          end
+
+          it 'returns error' do
+            expect(subject).to eq(manual1)
+          end
+        end
+      end
+    end
+  end
+
+  describe 'self.match' do
+    it 'matches the environment' do
+      match = described_class.match('deploy staging to production')
+
+      expect(match[:from]).to eq('staging')
+      expect(match[:to]).to eq('production')
+    end
+  end
+end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 60bbe3fcd728f6b9464884c53c5dc0d7485fb345..d06665197db701869276fb5adf20f7e7157ddd78 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -9,6 +9,7 @@ describe Environment, models: true do
   it { is_expected.to delegate_method(:last_deployment).to(:deployments).as(:last) }
 
   it { is_expected.to delegate_method(:stop_action).to(:last_deployment) }
+  it { is_expected.to delegate_method(:manual_actions).to(:last_deployment) }
 
   it { is_expected.to validate_presence_of(:name) }
   it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) }
@@ -187,4 +188,15 @@ describe Environment, models: true do
       it { is_expected.to be false }
     end
   end
+
+  describe '#actions_for' do
+    let(:deployment) { create(:deployment, environment: environment) }
+    let(:pipeline) { deployment.deployable.pipeline }
+    let!(:review_action) { create(:ci_build, :manual, name: 'review-apps', pipeline: pipeline, environment: 'review/$CI_BUILD_REF_NAME' )}
+    let!(:production_action) { create(:ci_build, :manual, name: 'production', pipeline: pipeline, environment: 'production' )}
+
+    it 'returns a list of actions with matching environment' do
+      expect(environment.actions_for('review/master')).to contain_exactly(review_action)
+    end
+  end
 end