diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..c3a8da52404f6b4817c86ace0d8880422ad2f1f0
--- /dev/null
+++ b/app/assets/javascripts/monitoring/constants.js
@@ -0,0 +1,4 @@
+import d3 from 'd3';
+
+export const dateFormat = d3.time.format('%b %d, %Y');
+export const timeFormat = d3.time.format('%H:%M%p');
diff --git a/app/assets/javascripts/monitoring/deployments.js b/app/assets/javascripts/monitoring/deployments.js
new file mode 100644
index 0000000000000000000000000000000000000000..fc92ab61b31d83e0c3a680ff3ef635f194cdf33e
--- /dev/null
+++ b/app/assets/javascripts/monitoring/deployments.js
@@ -0,0 +1,211 @@
+/* global Flash */
+import d3 from 'd3';
+import {
+  dateFormat,
+  timeFormat,
+} from './constants';
+
+export default class Deployments {
+  constructor(width, height) {
+    this.width = width;
+    this.height = height;
+
+    this.endpoint = document.getElementById('js-metrics').dataset.deploymentEndpoint;
+
+    this.createGradientDef();
+  }
+
+  init(chartData) {
+    this.chartData = chartData;
+
+    this.x = d3.time.scale().range([0, this.width]);
+    this.x.domain(d3.extent(this.chartData, d => d.time));
+
+    this.charts = d3.selectAll('.prometheus-graph');
+
+    this.getData();
+  }
+
+  getData() {
+    $.ajax({
+      url: this.endpoint,
+      dataType: 'JSON',
+    })
+    .fail(() => new Flash('Error getting deployment information.'))
+    .done((data) => {
+      this.data = data.deployments.reduce((deploymentDataArray, deployment) => {
+        const time = new Date(deployment.created_at);
+        const xPos = Math.floor(this.x(time));
+
+        time.setSeconds(this.chartData[0].time.getSeconds());
+
+        if (xPos >= 0) {
+          deploymentDataArray.push({
+            id: deployment.id,
+            time,
+            sha: deployment.sha,
+            tag: deployment.tag,
+            ref: deployment.ref.name,
+            xPos,
+          });
+        }
+
+        return deploymentDataArray;
+      }, []);
+
+      this.plotData();
+    });
+  }
+
+  plotData() {
+    this.charts.each((d, i) => {
+      const svg = d3.select(this.charts[0][i]);
+      const chart = svg.select('.graph-container');
+      const key = svg.node().getAttribute('graph-type');
+
+      this.createLine(chart, key);
+      this.createDeployInfoBox(chart, key);
+    });
+  }
+
+  createGradientDef() {
+    const defs = d3.select('body')
+      .append('svg')
+      .attr({
+        height: 0,
+        width: 0,
+      })
+      .append('defs');
+
+    defs.append('linearGradient')
+      .attr({
+        id: 'shadow-gradient',
+      })
+      .append('stop')
+      .attr({
+        offset: '0%',
+        'stop-color': '#000',
+        'stop-opacity': 0.4,
+      })
+      .select(this.selectParentNode)
+      .append('stop')
+      .attr({
+        offset: '100%',
+        'stop-color': '#000',
+        'stop-opacity': 0,
+      });
+  }
+
+  createLine(chart, key) {
+    chart.append('g')
+      .attr({
+        class: 'deploy-info',
+      })
+      .selectAll('.deploy-info')
+      .data(this.data)
+      .enter()
+      .append('g')
+      .attr({
+        class: d => `deploy-info-${d.id}-${key}`,
+        transform: d => `translate(${Math.floor(d.xPos) + 1}, 0)`,
+      })
+      .append('rect')
+      .attr({
+        x: 1,
+        y: 0,
+        height: this.height + 1,
+        width: 3,
+        fill: 'url(#shadow-gradient)',
+      })
+      .select(this.selectParentNode)
+      .append('line')
+      .attr({
+        class: 'deployment-line',
+        x1: 0,
+        x2: 0,
+        y1: 0,
+        y2: this.height + 1,
+      });
+  }
+
+  createDeployInfoBox(chart, key) {
+    chart.selectAll('.deploy-info')
+      .selectAll('.js-deploy-info-box')
+      .data(this.data)
+      .enter()
+      .select(d => document.querySelector(`.deploy-info-${d.id}-${key}`))
+      .append('svg')
+      .attr({
+        class: 'js-deploy-info-box hidden',
+        x: 3,
+        y: 0,
+        width: 92,
+        height: 60,
+      })
+      .append('rect')
+      .attr({
+        class: 'rect-text-metric deploy-info-rect rect-metric',
+        x: 1,
+        y: 1,
+        rx: 2,
+        width: 90,
+        height: 58,
+      })
+      .select(this.selectParentNode)
+      .append('g')
+      .attr({
+        transform: 'translate(5, 2)',
+      })
+      .append('text')
+      .attr({
+        class: 'deploy-info-text text-metric-bold',
+      })
+      .text(Deployments.refText)
+      .select(this.selectParentNode)
+      .append('text')
+      .attr({
+        class: 'deploy-info-text',
+        y: 18,
+      })
+      .text(d => dateFormat(d.time))
+      .select(this.selectParentNode)
+      .append('text')
+      .attr({
+        class: 'deploy-info-text text-metric-bold',
+        y: 38,
+      })
+      .text(d => timeFormat(d.time));
+  }
+
+  static toggleDeployTextbox(deploy, key, showInfoBox) {
+    d3.selectAll(`.deploy-info-${deploy.id}-${key} .js-deploy-info-box`)
+      .classed('hidden', !showInfoBox);
+  }
+
+  mouseOverDeployInfo(mouseXPos, key) {
+    if (!this.data) return false;
+
+    let dataFound = false;
+
+    this.data.forEach((d) => {
+      if (d.xPos >= mouseXPos - 10 && d.xPos <= mouseXPos + 10 && !dataFound) {
+        dataFound = d.xPos + 1;
+
+        Deployments.toggleDeployTextbox(d, key, true);
+      } else {
+        Deployments.toggleDeployTextbox(d, key, false);
+      }
+    });
+
+    return dataFound;
+  }
+
+  /* `this` is bound to the D3 node */
+  selectParentNode() {
+    return this.parentNode;
+  }
+
+  static refText(d) {
+    return d.tag ? d.ref : d.sha.slice(0, 6);
+  }
+}
diff --git a/app/assets/javascripts/monitoring/prometheus_graph.js b/app/assets/javascripts/monitoring/prometheus_graph.js
index 78bb0e6fb4738e7978979b4d6c833f752721701d..6af887691292e1de6e3c013a715417e78c8d101c 100644
--- a/app/assets/javascripts/monitoring/prometheus_graph.js
+++ b/app/assets/javascripts/monitoring/prometheus_graph.js
@@ -3,16 +3,20 @@
 
 import d3 from 'd3';
 import statusCodes from '~/lib/utils/http_status';
-import { formatRelevantDigits } from '~/lib/utils/number_utils';
+import Deployments from './deployments';
+import '../lib/utils/common_utils';
+import { formatRelevantDigits } from '../lib/utils/number_utils';
 import '../flash';
+import {
+  dateFormat,
+  timeFormat,
+} from './constants';
 
 const prometheusContainer = '.prometheus-container';
 const prometheusParentGraphContainer = '.prometheus-graphs';
 const prometheusGraphsContainer = '.prometheus-graph';
 const prometheusStatesContainer = '.prometheus-state';
 const metricsEndpoint = 'metrics.json';
-const timeFormat = d3.time.format('%H:%M');
-const dayFormat = d3.time.format('%b %e, %a');
 const bisectDate = d3.bisector(d => d.time).left;
 const extraAddedWidthParent = 100;
 
@@ -36,6 +40,7 @@ class PrometheusGraph {
       this.width = parentContainerWidth - this.margin.left - this.margin.right;
       this.height = this.originalHeight - this.margin.top - this.margin.bottom;
       this.backOffRequestCounter = 0;
+      this.deployments = new Deployments(this.width, this.height);
       this.configureGraph();
       this.init();
     } else {
@@ -74,6 +79,12 @@ class PrometheusGraph {
         $(prometheusParentGraphContainer).show();
         this.transformData(metricsResponse);
         this.createGraph();
+
+        const firstMetricData = this.graphSpecificProperties[
+          Object.keys(this.graphSpecificProperties)[0]
+        ].data;
+
+        this.deployments.init(firstMetricData);
       }
     });
   }
@@ -96,6 +107,7 @@ class PrometheusGraph {
       .attr('width', this.width + this.margin.left + this.margin.right)
       .attr('height', this.height + this.margin.bottom + this.margin.top)
       .append('g')
+      .attr('class', 'graph-container')
         .attr('transform', `translate(${this.margin.left},${this.margin.top})`);
 
     const axisLabelContainer = d3.select(prometheusGraphContainer)
@@ -116,6 +128,7 @@ class PrometheusGraph {
       .scale(y)
       .ticks(this.commonGraphProperties.axis_no_ticks)
       .tickSize(-this.width)
+      .outerTickSize(0)
       .orient('left');
 
     this.createAxisLabelContainers(axisLabelContainer, key);
@@ -248,7 +261,8 @@ class PrometheusGraph {
       const d1 = currentGraphProps.data[overlayIndex];
       const evalTime = timeValueOverlay - d0.time > d1.time - timeValueOverlay;
       const currentData = evalTime ? d1 : d0;
-      const currentTimeCoordinate = currentGraphProps.xScale(currentData.time);
+      const currentTimeCoordinate = Math.floor(currentGraphProps.xScale(currentData.time));
+      const currentDeployXPos = this.deployments.mouseOverDeployInfo(currentXCoordinate, key);
       const currentPrometheusGraphContainer = `${prometheusGraphsContainer}[graph-type=${key}]`;
       const maxValueFromData = d3.max(currentGraphProps.data.map(metricValue => metricValue.value));
       const maxMetricValue = currentGraphProps.yScale(maxValueFromData);
@@ -256,13 +270,12 @@ class PrometheusGraph {
       // Clear up all the pieces of the flag
       d3.selectAll(`${currentPrometheusGraphContainer} .selected-metric-line`).remove();
       d3.selectAll(`${currentPrometheusGraphContainer} .circle-metric`).remove();
-      d3.selectAll(`${currentPrometheusGraphContainer} .rect-text-metric`).remove();
-      d3.selectAll(`${currentPrometheusGraphContainer} .text-metric`).remove();
+      d3.selectAll(`${currentPrometheusGraphContainer} .rect-text-metric:not(.deploy-info-rect)`).remove();
 
       const currentChart = d3.select(currentPrometheusGraphContainer).select('g');
       currentChart.append('line')
-      .attr('class', 'selected-metric-line')
       .attr({
+        class: `${currentDeployXPos ? 'hidden' : ''} selected-metric-line`,
         x1: currentTimeCoordinate,
         y1: currentGraphProps.yScale(0),
         x2: currentTimeCoordinate,
@@ -272,33 +285,45 @@ class PrometheusGraph {
       currentChart.append('circle')
         .attr('class', 'circle-metric')
         .attr('fill', currentGraphProps.line_color)
-        .attr('cx', currentTimeCoordinate)
+        .attr('cx', currentDeployXPos || currentTimeCoordinate)
         .attr('cy', currentGraphProps.yScale(currentData.value))
         .attr('r', this.commonGraphProperties.circle_radius_metric);
 
+      if (currentDeployXPos) return;
+
       // The little box with text
-      const rectTextMetric = currentChart.append('g')
-        .attr('class', 'rect-text-metric')
-        .attr('translate', `(${currentTimeCoordinate}, ${currentGraphProps.yScale(currentData.value)})`);
+      const rectTextMetric = currentChart.append('svg')
+        .attr({
+          class: 'rect-text-metric',
+          x: currentTimeCoordinate,
+          y: 0,
+        });
 
       rectTextMetric.append('rect')
-        .attr('class', 'rect-metric')
-        .attr('x', currentTimeCoordinate + 10)
-        .attr('y', maxMetricValue)
-        .attr('width', this.commonGraphProperties.rect_text_width)
-        .attr('height', this.commonGraphProperties.rect_text_height);
+        .attr({
+          class: 'rect-metric',
+          x: 4,
+          y: 1,
+          rx: 2,
+          width: this.commonGraphProperties.rect_text_width,
+          height: this.commonGraphProperties.rect_text_height,
+        });
 
       rectTextMetric.append('text')
-        .attr('class', 'text-metric')
-        .attr('x', currentTimeCoordinate + 35)
-        .attr('y', maxMetricValue + 35)
+        .attr({
+          class: 'text-metric text-metric-bold',
+          x: 8,
+          y: 35,
+        })
         .text(timeFormat(currentData.time));
 
       rectTextMetric.append('text')
-        .attr('class', 'text-metric-date')
-        .attr('x', currentTimeCoordinate + 15)
-        .attr('y', maxMetricValue + 15)
-        .text(dayFormat(currentData.time));
+        .attr({
+          class: 'text-metric-date',
+          x: 8,
+          y: 15,
+        })
+        .text(dateFormat(currentData.time));
 
       let currentMetricValue = formatRelevantDigits(currentData.value);
       if (key === 'cpu_values') {
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 72e7d42858dbb6a3e417bc6f81146bf9f2f6d91a..026d35295d79a2d40ce4ab04191056c26509f833 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -157,7 +157,8 @@
 
 .prometheus-graph {
   text {
-    fill: $stat-graph-axis-fill;
+    fill: $gl-text-color;
+    stroke-width: 0;
   }
 
   .label-axis-text,
@@ -210,27 +211,33 @@
 .rect-text-metric {
   fill: $white-light;
   stroke-width: 1;
-  stroke: $black;
+  stroke: $gray-darkest;
 }
 
 .rect-axis-text {
   fill: $white-light;
 }
 
-.text-metric,
-.text-median-metric,
-.text-metric-usage,
-.text-metric-date {
-  fill: $black;
+.text-metric {
+  font-weight: 600;
 }
 
-.text-metric-date {
-  font-weight: 200;
+.selected-metric-line {
+  stroke: $gl-gray-dark;
+  stroke-width: 1;
 }
 
-.selected-metric-line {
+.deployment-line {
   stroke: $black;
-  stroke-width: 1;
+  stroke-width: 2;
+}
+
+.deploy-info-text {
+  dominant-baseline: text-before-edge;
+}
+
+.text-metric-bold {
+  font-weight: 600;
 }
 
 .prometheus-state {
diff --git a/app/controllers/projects/deployments_controller.rb b/app/controllers/projects/deployments_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c319671456d0a24644fa2b4807a60251a51f19e5
--- /dev/null
+++ b/app/controllers/projects/deployments_controller.rb
@@ -0,0 +1,18 @@
+class Projects::DeploymentsController < Projects::ApplicationController
+  before_action :authorize_read_environment!
+  before_action :authorize_read_deployment!
+
+  def index
+    deployments = environment.deployments.reorder(created_at: :desc)
+    deployments = deployments.where('created_at > ?', params[:after].to_time) if params[:after]&.to_time
+
+    render json: { deployments: DeploymentSerializer.new(user: @current_user, project: project)
+                                  .represent_concise(deployments) }
+  end
+
+  private
+
+  def environment
+    @environment ||= project.environments.find(params[:environment_id])
+  end
+end
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
index d610fbe0c8ac51d26edd9e042119cebd9298b951..8b3de1bed0f2f6039ac125571794f509ad90db2f 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -18,8 +18,10 @@ class DeploymentEntity < Grape::Entity
     end
   end
 
+  expose :created_at
   expose :tag
   expose :last?
+
   expose :user, using: UserEntity
   expose :commit, using: CommitEntity
   expose :deployable, using: BuildEntity
diff --git a/app/serializers/deployment_serializer.rb b/app/serializers/deployment_serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cba5c3f311f77f9ad3e5a84bedb7381639b86fcc
--- /dev/null
+++ b/app/serializers/deployment_serializer.rb
@@ -0,0 +1,8 @@
+class DeploymentSerializer < BaseSerializer
+  entity DeploymentEntity
+
+  def represent_concise(resource, opts = {})
+    opts[:only] = [:iid, :id, :sha, :created_at, :tag, :last?, :id, ref: [:name]]
+    represent(resource, opts)
+  end
+end
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
index 766f119116fe7f64a1d6368082b6f504122d1c12..e8f8fbbcf092594ecb4b8a672538bb3abe54aee2 100644
--- a/app/views/projects/environments/metrics.html.haml
+++ b/app/views/projects/environments/metrics.html.haml
@@ -5,7 +5,7 @@
   = page_specific_javascript_bundle_tag('monitoring')
 = render "projects/pipelines/head"
 
-.prometheus-container{ class: container_class, 'data-has-metrics': "#{@environment.has_metrics?}" }
+#js-metrics.prometheus-container{ class: container_class, data: { has_metrics: "#{@environment.has_metrics?}", deployment_endpoint: namespace_project_environment_deployments_path(@project.namespace, @project, @environment, format: :json) } }
   .top-area
     .row
       .col-sm-6
diff --git a/config/routes/project.rb b/config/routes/project.rb
index aaad503eba4049272ae5d62c670c5b70b4aa247b..894faeb6188b400371b2181c724cc7284f1987ec 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -138,6 +138,8 @@ constraints(ProjectUrlConstrainer.new) do
         collection do
           get :folder, path: 'folders/*id', constraints: { format: /(html|json)/ }
         end
+
+        resources :deployments, only: [:index]
       end
 
       resource :cycle_analytics, only: [:show]
diff --git a/db/migrate/20170327091750_add_created_at_index_to_deployments.rb b/db/migrate/20170327091750_add_created_at_index_to_deployments.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fd6ed499b80a186db9f1f1de715020072aee5ad4
--- /dev/null
+++ b/db/migrate/20170327091750_add_created_at_index_to_deployments.rb
@@ -0,0 +1,15 @@
+class AddCreatedAtIndexToDeployments < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_index :deployments, :created_at
+  end
+
+  def down
+    remove_concurrent_index :deployments, :created_at
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b938657a186557b164103d34ecc6dc8407b4da68..be6684f3a6b4ef29b802b3fe3c70cedc679c309a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -386,6 +386,7 @@ ActiveRecord::Schema.define(version: 20170426181740) do
     t.string "on_stop"
   end
 
+  add_index "deployments", ["created_at"], name: "index_deployments_on_created_at", using: :btree
   add_index "deployments", ["project_id", "environment_id", "iid"], name: "index_deployments_on_project_id_and_environment_id_and_iid", using: :btree
   add_index "deployments", ["project_id", "iid"], name: "index_deployments_on_project_id_and_iid", unique: true, using: :btree
 
diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..89692b601b2e3135f31ab8243fa7f35e1e103f37
--- /dev/null
+++ b/spec/controllers/projects/deployments_controller_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe Projects::DeploymentsController do
+  include ApiHelpers
+
+  let(:user) { create(:user) }
+  let(:project) { create(:empty_project) }
+  let(:environment) { create(:environment, name: 'production', project: project) }
+
+  before do
+    project.add_master(user)
+
+    sign_in(user)
+  end
+
+  describe 'GET #index' do
+    it 'returns list of deployments from last 8 hours' do
+      create(:deployment, environment: environment, created_at: 9.hours.ago)
+      create(:deployment, environment: environment, created_at: 7.hours.ago)
+      create(:deployment, environment: environment)
+
+      get :index, environment_params(after: 8.hours.ago)
+
+      expect(response).to be_ok
+
+      expect(json_response['deployments'].count).to eq(2)
+    end
+
+    it 'returns a list with deployments information' do
+      create(:deployment, environment: environment)
+
+      get :index, environment_params
+
+      expect(response).to be_ok
+      expect(response).to match_response_schema('deployments')
+    end
+  end
+
+  def environment_params(opts = {})
+    opts.reverse_merge(namespace_id: project.namespace, project_id: project, environment_id: environment.id)
+  end
+end
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index a1a36931824d87b7738a0b46f90f34f0f6435885..26879a77c487e1c925f42441f0b029f1189108bb 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -466,6 +466,21 @@ describe "Internal Project Access", feature: true  do
     it { is_expected.to be_denied_for(:visitor) }
   end
 
+  describe "GET /:project_path/environments/:id/deployments" do
+    let(:environment) { create(:environment, project: project) }
+    subject { namespace_project_environment_deployments_path(project.namespace, project, environment) }
+
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
+  end
+
   describe "GET /:project_path/environments/new" do
     subject { new_namespace_project_environment_path(project.namespace, project) }
 
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index 5d58494a22aa6a33da0283d99c12bae8a4b060e1..699ca4f724c4faa1f2b7a3feed3e30faa8a81577 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -449,6 +449,21 @@ describe "Private Project Access", feature: true  do
     it { is_expected.to be_denied_for(:visitor) }
   end
 
+  describe "GET /:project_path/environments/:id/deployments" do
+    let(:environment) { create(:environment, project: project) }
+    subject { namespace_project_environment_deployments_path(project.namespace, project, environment) }
+
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
+  end
+
   describe "GET /:project_path/environments/new" do
     subject { new_namespace_project_environment_path(project.namespace, project) }
 
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 5df5b710dc4083e403735339c689af1462e6d2ee..624f0d0f485908cdaab963dfb6d45b8dba5bd73b 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -286,6 +286,21 @@ describe "Public Project Access", feature: true  do
     it { is_expected.to be_denied_for(:visitor) }
   end
 
+  describe "GET /:project_path/environments/:id/deployments" do
+    let(:environment) { create(:environment, project: project) }
+    subject { namespace_project_environment_deployments_path(project.namespace, project, environment) }
+
+    it { is_expected.to be_allowed_for(:admin) }
+    it { is_expected.to be_allowed_for(:owner).of(project) }
+    it { is_expected.to be_allowed_for(:master).of(project) }
+    it { is_expected.to be_allowed_for(:developer).of(project) }
+    it { is_expected.to be_allowed_for(:reporter).of(project) }
+    it { is_expected.to be_denied_for(:guest).of(project) }
+    it { is_expected.to be_denied_for(:user) }
+    it { is_expected.to be_denied_for(:external) }
+    it { is_expected.to be_denied_for(:visitor) }
+  end
+
   describe "GET /:project_path/environments/new" do
     subject { new_namespace_project_environment_path(project.namespace, project) }
 
diff --git a/spec/fixtures/api/schemas/deployments.json b/spec/fixtures/api/schemas/deployments.json
new file mode 100644
index 0000000000000000000000000000000000000000..1112f23aab25e1ab120f0ab91794432d2ebe82f3
--- /dev/null
+++ b/spec/fixtures/api/schemas/deployments.json
@@ -0,0 +1,58 @@
+{
+  "additionalProperties": false,
+  "properties": {
+    "deployments": {
+      "items": {
+        "additionalProperties": false,
+        "properties": {
+          "created_at": {
+            "type": "string"
+          },
+          "id": {
+            "type": "integer"
+          },
+          "iid": {
+            "type": "integer"
+          },
+          "last?": {
+            "type": "boolean"
+          },
+          "ref": {
+            "additionalProperties": false,
+            "properties": {
+              "name": {
+                "type": "string"
+              }
+            },
+            "required": [
+              "name"
+            ],
+            "type": "object"
+          },
+          "sha": {
+            "type": "string"
+          },
+          "tag": {
+            "type": "boolean"
+          }
+        },
+        "required": [
+          "sha",
+          "created_at",
+          "iid",
+          "tag",
+          "last?",
+          "ref",
+          "id"
+        ],
+        "type": "object"
+      },
+      "minItems": 1,
+      "type": "array"
+    }
+  },
+  "required": [
+    "deployments"
+  ],
+  "type": "object"
+}
diff --git a/spec/javascripts/fixtures/environments.rb b/spec/javascripts/fixtures/environments.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3474f4696ef304114440ef5aef9d716b786835e2
--- /dev/null
+++ b/spec/javascripts/fixtures/environments.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Projects::EnvironmentsController, '(JavaScript fixtures)', type: :controller do
+  include JavaScriptFixturesHelpers
+
+  let(:admin) { create(:admin) }
+  let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+  let(:project) { create(:project_empty_repo, namespace: namespace, path: 'environments-project') }
+  let(:environment) { create(:environment, name: 'production', project: project) }
+
+  render_views
+
+  before(:all) do
+    clean_frontend_fixtures('environments/metrics')
+  end
+
+  before(:each) do
+    sign_in(admin)
+  end
+
+  it 'environments/metrics/metrics.html.raw' do |example|
+    get :metrics,
+      namespace_id: project.namespace,
+      project_id: project,
+      id: environment.id
+
+    expect(response).to be_success
+    store_frontend_fixture(response, example.description)
+  end
+end
diff --git a/spec/javascripts/fixtures/environments/metrics.html.haml b/spec/javascripts/fixtures/environments/metrics.html.haml
deleted file mode 100644
index e2dd95198984a7c1c9ad31a7b3ba6778f51f4227..0000000000000000000000000000000000000000
--- a/spec/javascripts/fixtures/environments/metrics.html.haml
+++ /dev/null
@@ -1,62 +0,0 @@
-.prometheus-container{ 'data-has-metrics': "false", 'data-doc-link': '/help/administration/monitoring/prometheus/index.md', 'data-prometheus-integration': '/root/hello-prometheus/services/prometheus/edit' }
-  .top-area
-    .row
-      .col-sm-6
-        %h3.page-title
-          Metrics for environment
-  .prometheus-state
-    .js-getting-started.hidden
-      .row
-        .col-md-4.col-md-offset-4.state-svg
-          %svg
-      .row
-        .col-md-6.col-md-offset-3
-          %h4.text-center.state-title
-            Get started with performance monitoring
-      .row
-        .col-md-6.col-md-offset-3
-          .description-text.text-center.state-description
-            Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments. Learn more about performance monitoring
-      .row.state-button-section
-        .col-md-4.col-md-offset-4.text-center.state-button
-          %a.btn.btn-success
-            Configure Prometheus
-    .js-loading.hidden
-      .row
-        .col-md-4.col-md-offset-4.state-svg
-          %svg
-      .row
-        .col-md-6.col-md-offset-3
-          %h4.text-center.state-title
-            Waiting for performance data
-      .row
-        .col-md-6.col-md-offset-3
-          .description-text.text-center.state-description
-            Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available.
-      .row.state-button-section
-        .col-md-4.col-md-offset-4.text-center.state-button
-          %a.btn.btn-success
-            View documentation
-    .js-unable-to-connect.hidden
-      .row
-        .col-md-4.col-md-offset-4.state-svg
-          %svg
-      .row
-        .col-md-6.col-md-offset-3
-          %h4.text-center.state-title
-            Unable to connect to Prometheus server
-      .row
-        .col-md-6.col-md-offset-3
-          .description-text.text-center.state-description
-            Ensure connectivity is available from the GitLab server to the Prometheus server
-      .row.state-button-section
-        .col-md-4.col-md-offset-4.text-center.state-button
-          %a.btn.btn-success
-            View documentation
-  .prometheus-graphs
-    .row
-      .col-sm-12
-        %svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
-    .row
-      .col-sm-12
-        %svg.prometheus-graph{ 'graph-type' => 'memory_values' }
diff --git a/spec/javascripts/monitoring/deployments_spec.js b/spec/javascripts/monitoring/deployments_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..19bc11d0f2489f1b36f5bbadf29721a9a91806c9
--- /dev/null
+++ b/spec/javascripts/monitoring/deployments_spec.js
@@ -0,0 +1,133 @@
+import d3 from 'd3';
+import PrometheusGraph from '~/monitoring/prometheus_graph';
+import Deployments from '~/monitoring/deployments';
+import { prometheusMockData } from './prometheus_mock_data';
+
+describe('Metrics deployments', () => {
+  const fixtureName = 'environments/metrics/metrics.html.raw';
+  let deployment;
+  let prometheusGraph;
+
+  const graphElement = () => document.querySelector('.prometheus-graph');
+
+  preloadFixtures(fixtureName);
+
+  beforeEach((done) => {
+    // Setup the view
+    loadFixtures(fixtureName);
+
+    d3.selectAll('.prometheus-graph')
+      .append('g')
+      .attr('class', 'graph-container');
+
+    prometheusGraph = new PrometheusGraph();
+    deployment = new Deployments(1000, 500);
+
+    spyOn(prometheusGraph, 'init');
+    spyOn($, 'ajax').and.callFake(() => {
+      const d = $.Deferred();
+      d.resolve({
+        deployments: [{
+          id: 1,
+          created_at: deployment.chartData[10].time,
+          sha: 'testing',
+          tag: false,
+          ref: {
+            name: 'testing',
+          },
+        }, {
+          id: 2,
+          created_at: deployment.chartData[15].time,
+          sha: '',
+          tag: true,
+          ref: {
+            name: 'tag',
+          },
+        }],
+      });
+
+      setTimeout(done);
+
+      return d.promise();
+    });
+
+    prometheusGraph.configureGraph();
+    prometheusGraph.transformData(prometheusMockData.metrics);
+
+    deployment.init(prometheusGraph.graphSpecificProperties.memory_values.data);
+  });
+
+  it('creates line on graph for deploment', () => {
+    expect(
+      graphElement().querySelectorAll('.deployment-line').length,
+    ).toBe(2);
+  });
+
+  it('creates hidden deploy boxes', () => {
+    expect(
+      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box').length,
+    ).toBe(2);
+  });
+
+  it('hides the info boxes by default', () => {
+    expect(
+      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
+    ).toBe(2);
+  });
+
+  it('shows sha short code when tag is false', () => {
+    expect(
+      graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box').textContent.trim(),
+    ).toContain('testin');
+  });
+
+  it('shows ref name when tag is true', () => {
+    expect(
+      graphElement().querySelector('.deploy-info-2-cpu_values .js-deploy-info-box').textContent.trim(),
+    ).toContain('tag');
+  });
+
+  it('shows info box when moving mouse over line', () => {
+    deployment.mouseOverDeployInfo(deployment.data[0].xPos, 'cpu_values');
+
+    expect(
+      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
+    ).toBe(1);
+
+    expect(
+      graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box.hidden'),
+    ).toBeNull();
+  });
+
+  it('hides previously visible info box when moving mouse away', () => {
+    deployment.mouseOverDeployInfo(500, 'cpu_values');
+
+    expect(
+      graphElement().querySelectorAll('.prometheus-graph .js-deploy-info-box.hidden').length,
+    ).toBe(2);
+
+    expect(
+      graphElement().querySelector('.deploy-info-1-cpu_values .js-deploy-info-box.hidden'),
+    ).not.toBeNull();
+  });
+
+  describe('refText', () => {
+    it('returns shortened SHA', () => {
+      expect(
+        Deployments.refText({
+          tag: false,
+          sha: '123456789',
+        }),
+      ).toBe('123456');
+    });
+
+    it('returns tag name', () => {
+      expect(
+        Deployments.refText({
+          tag: true,
+          ref: 'v1.0',
+        }),
+      ).toBe('v1.0');
+    });
+  });
+});
diff --git a/spec/javascripts/monitoring/prometheus_graph_spec.js b/spec/javascripts/monitoring/prometheus_graph_spec.js
index 4b904fc2960ab4fd1d020ba2ba50bb59c42434bd..25578bf1c6e37abf2cf232ec1273179516a79097 100644
--- a/spec/javascripts/monitoring/prometheus_graph_spec.js
+++ b/spec/javascripts/monitoring/prometheus_graph_spec.js
@@ -3,7 +3,7 @@ import PrometheusGraph from '~/monitoring/prometheus_graph';
 import { prometheusMockData } from './prometheus_mock_data';
 
 describe('PrometheusGraph', () => {
-  const fixtureName = 'static/environments/metrics.html.raw';
+  const fixtureName = 'environments/metrics/metrics.html.raw';
   const prometheusGraphContainer = '.prometheus-graph';
   const prometheusGraphContents = `${prometheusGraphContainer}[graph-type=cpu_values]`;
 
@@ -77,7 +77,7 @@ describe('PrometheusGraph', () => {
 });
 
 describe('PrometheusGraphs UX states', () => {
-  const fixtureName = 'static/environments/metrics.html.raw';
+  const fixtureName = 'environments/metrics/metrics.html.raw';
   preloadFixtures(fixtureName);
 
   beforeEach(() => {
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
index 95eca5463eb618266c219fc10d1d323ef83a531c..69355bcde42d1ef2d40702927643142924015062 100644
--- a/spec/serializers/deployment_entity_spec.rb
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -3,25 +3,23 @@ require 'spec_helper'
 describe DeploymentEntity do
   let(:user) { create(:user) }
   let(:request) { double('request') }
+  let(:deployment) { create(:deployment) }
+  let(:entity) { described_class.new(deployment, request: request) }
+  subject { entity.as_json }
 
   before do
     allow(request).to receive(:user).and_return(user)
   end
 
-  let(:entity) do
-    described_class.new(deployment, request: request)
-  end
-
-  let(:deployment) { create(:deployment) }
-
-  subject { entity.as_json }
-
   it 'exposes internal deployment id'  do
     expect(subject).to include(:iid)
   end
 
   it 'exposes nested information about branch' do
     expect(subject[:ref][:name]).to eq 'master'
-    expect(subject[:ref][:ref_path]).not_to be_empty
+  end
+
+  it 'exposes creation date' do
+    expect(subject).to include(:created_at)
   end
 end