diff --git a/app/serializers/base_serializer.rb b/app/serializers/base_serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..de9a181db90f0303e3f70385aa5332dc2b1fae8b
--- /dev/null
+++ b/app/serializers/base_serializer.rb
@@ -0,0 +1,18 @@
+class BaseSerializer
+  def initialize(parameters = {})
+    @request = EntityRequest.new(parameters)
+  end
+
+  def represent(resource, opts = {})
+    self.class.entity_class
+      .represent(resource, opts.merge(request: @request))
+  end
+
+  def self.entity(entity_class)
+    @entity_class ||= entity_class
+  end
+
+  def self.entity_class
+    @entity_class
+  end
+end
diff --git a/app/serializers/build_entity.rb b/app/serializers/build_entity.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3d9ac66de0e5a1d5d96dace4acebea48030e27c9
--- /dev/null
+++ b/app/serializers/build_entity.rb
@@ -0,0 +1,24 @@
+class BuildEntity < Grape::Entity
+  include RequestAwareEntity
+
+  expose :id
+  expose :name
+
+  expose :build_url do |build|
+    url_to(:namespace_project_build, build)
+  end
+
+  expose :retry_url do |build|
+    url_to(:retry_namespace_project_build, build)
+  end
+
+  expose :play_url, if: ->(build, _) { build.manual? } do |build|
+    url_to(:play_namespace_project_build, build)
+  end
+
+  private
+
+  def url_to(route, build)
+    send("#{route}_url", build.project.namespace, build.project, build)
+  end
+end
diff --git a/app/serializers/commit_entity.rb b/app/serializers/commit_entity.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f7eba6fc1e34d8f54a6d1f672340b688c4e66af6
--- /dev/null
+++ b/app/serializers/commit_entity.rb
@@ -0,0 +1,12 @@
+class CommitEntity < API::Entities::RepoCommit
+  include RequestAwareEntity
+
+  expose :author, using: UserEntity
+
+  expose :commit_url do |commit|
+    namespace_project_tree_url(
+      request.project.namespace,
+      request.project,
+      id: commit.id)
+  end
+end
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ad6fc8d665bd571aa8289e431eb30482ff46ed30
--- /dev/null
+++ b/app/serializers/deployment_entity.rb
@@ -0,0 +1,27 @@
+class DeploymentEntity < Grape::Entity
+  include RequestAwareEntity
+
+  expose :id
+  expose :iid
+  expose :sha
+
+  expose :ref do
+    expose :name do |deployment|
+      deployment.ref
+    end
+
+    expose :ref_url do |deployment|
+      namespace_project_tree_url(
+        deployment.project.namespace,
+        deployment.project,
+        id: deployment.ref)
+    end
+  end
+
+  expose :tag
+  expose :last?
+  expose :user, using: UserEntity
+  expose :commit, using: CommitEntity
+  expose :deployable, using: BuildEntity
+  expose :manual_actions, using: BuildEntity
+end
diff --git a/app/serializers/entity_request.rb b/app/serializers/entity_request.rb
new file mode 100644
index 0000000000000000000000000000000000000000..456ba1174c0634486d37a9bb036a252b17c2e674
--- /dev/null
+++ b/app/serializers/entity_request.rb
@@ -0,0 +1,12 @@
+class EntityRequest
+  # We use EntityRequest object to collect parameters and variables
+  # from the controller. Because options that are being passed to the entity
+  # do appear in each entity object  in the chain, we need a way to pass data
+  # that is present in the controller (see  #20045).
+  #
+  def initialize(parameters)
+    parameters.each do |key, value|
+      define_singleton_method(key) { value }
+    end
+  end
+end
diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ee4392cc46d462de2da0cd8c7d64f553e8b8b4b6
--- /dev/null
+++ b/app/serializers/environment_entity.rb
@@ -0,0 +1,20 @@
+class EnvironmentEntity < Grape::Entity
+  include RequestAwareEntity
+
+  expose :id
+  expose :name
+  expose :state
+  expose :external_url
+  expose :environment_type
+  expose :last_deployment, using: DeploymentEntity
+  expose :stoppable?
+
+  expose :environment_url do |environment|
+    namespace_project_environment_url(
+      environment.project.namespace,
+      environment.project,
+      environment)
+  end
+
+  expose :created_at, :updated_at
+end
diff --git a/app/serializers/environment_serializer.rb b/app/serializers/environment_serializer.rb
new file mode 100644
index 0000000000000000000000000000000000000000..91955542f2540bda42e7c0fb58c515a2902ffde5
--- /dev/null
+++ b/app/serializers/environment_serializer.rb
@@ -0,0 +1,3 @@
+class EnvironmentSerializer < BaseSerializer
+  entity EnvironmentEntity
+end
diff --git a/app/serializers/request_aware_entity.rb b/app/serializers/request_aware_entity.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ff8c1142abc114809dc57354197d7b46d602a35c
--- /dev/null
+++ b/app/serializers/request_aware_entity.rb
@@ -0,0 +1,11 @@
+module RequestAwareEntity
+  extend ActiveSupport::Concern
+
+  included do
+    include Gitlab::Routing.url_helpers
+  end
+
+  def request
+    @options.fetch(:request)
+  end
+end
diff --git a/app/serializers/user_entity.rb b/app/serializers/user_entity.rb
new file mode 100644
index 0000000000000000000000000000000000000000..43754ea94f734143b27c58368c95b6a6a11ecc1a
--- /dev/null
+++ b/app/serializers/user_entity.rb
@@ -0,0 +1,2 @@
+class UserEntity < API::Entities::UserBasic
+end
diff --git a/spec/serializers/build_entity_spec.rb b/spec/serializers/build_entity_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2734f5bedca97ab1605cb1752fbdd4f6870db500
--- /dev/null
+++ b/spec/serializers/build_entity_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe BuildEntity do
+  let(:entity) do
+    described_class.new(build, request: double)
+  end
+
+  subject { entity.as_json }
+
+  context 'when build is a regular job' do
+    let(:build) { create(:ci_build) }
+
+    it 'contains url to build page and retry action' do
+      expect(subject).to include(:build_url, :retry_url)
+      expect(subject).not_to include(:play_url)
+    end
+
+    it 'does not contain sensitive information' do
+      expect(subject).not_to include(/token/)
+      expect(subject).not_to include(/variables/)
+    end
+  end
+
+  context 'when build is a manual action' do
+    let(:build) { create(:ci_build, :manual) }
+
+    it 'contains url to play action' do
+      expect(subject).to include(:play_url)
+    end
+  end
+end
diff --git a/spec/serializers/commit_entity_spec.rb b/spec/serializers/commit_entity_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..628e35c9a286a7b889753fadad4f773571d84635
--- /dev/null
+++ b/spec/serializers/commit_entity_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe CommitEntity do
+  let(:entity) do
+    described_class.new(commit, request: request)
+  end
+
+  let(:request) { double('request') }
+  let(:project) { create(:project) }
+  let(:commit) { project.commit }
+
+  subject { entity.as_json }
+
+  before do
+    allow(request).to receive(:project).and_return(project)
+  end
+
+  context 'when commit author is a user' do
+    before do
+      create(:user, email: commit.author_email)
+    end
+
+    it 'contains information about user' do
+      expect(subject.fetch(:author)).not_to be_nil
+    end
+  end
+
+  context 'when commit author is not a user' do
+    it 'does not contain author details' do
+      expect(subject.fetch(:author)).to be_nil
+    end
+  end
+
+  it 'contains commit URL' do
+    expect(subject).to include(:commit_url)
+  end
+
+  it 'needs to receive project in the request' do
+    expect(request).to receive(:project)
+      .and_return(project)
+
+    subject
+  end
+end
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..51b6de915714b5a157874cc58a8be551e7eef46e
--- /dev/null
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe DeploymentEntity do
+  let(:entity) do
+    described_class.new(deployment, request: double)
+  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_url]).not_to be_empty
+  end
+end
diff --git a/spec/serializers/entity_request_spec.rb b/spec/serializers/entity_request_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..86654adfd541c5a658d3cbda672ed69c3e870bf0
--- /dev/null
+++ b/spec/serializers/entity_request_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe EntityRequest do
+  subject do
+    described_class.new(user: 'user', project: 'some project')
+  end
+
+  describe 'methods created' do
+    it 'defines accessible attributes' do
+      expect(subject.user).to eq 'user'
+      expect(subject.project).to eq 'some project'
+    end
+
+    it 'raises error when attribute is not defined' do
+      expect { subject.some_method }.to raise_error NoMethodError
+    end
+  end
+end
diff --git a/spec/serializers/environment_entity_spec.rb b/spec/serializers/environment_entity_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4ca8c299147d3b414e67c773f0ebeff15c7f5563
--- /dev/null
+++ b/spec/serializers/environment_entity_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe EnvironmentEntity do
+  let(:entity) do
+    described_class.new(environment, request: double)
+  end
+
+  let(:environment) { create(:environment) }
+  subject { entity.as_json }
+
+  it 'exposes latest deployment' do
+    expect(subject).to include(:last_deployment)
+  end
+
+  it 'exposes core elements of environment' do
+    expect(subject).to include(:id, :name, :state, :environment_url)
+  end
+end
diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..37bc086826cfc18caa34d5740383b9dc94b6664d
--- /dev/null
+++ b/spec/serializers/environment_serializer_spec.rb
@@ -0,0 +1,60 @@
+require 'spec_helper'
+
+describe EnvironmentSerializer do
+  let(:serializer) do
+    described_class
+      .new(user: user, project: project)
+      .represent(resource)
+  end
+
+  let(:json) { serializer.as_json }
+  let(:user) { create(:user) }
+  let(:project) { create(:project) }
+
+  context 'when there is a single object provided' do
+    before do
+      create(:ci_build, :manual, name: 'manual1',
+                                 pipeline: deployable.pipeline)
+    end
+
+    let(:deployment) do
+      create(:deployment, deployable: deployable,
+                          user: user,
+                          project: project,
+                          sha: project.commit.id)
+    end
+
+    let(:deployable) { create(:ci_build) }
+    let(:resource) { deployment.environment }
+
+    it 'it generates payload for single object' do
+      expect(json).to be_an_instance_of Hash
+    end
+
+    it 'contains important elements of environment' do
+      expect(json)
+        .to include(:name, :external_url, :environment_url, :last_deployment)
+    end
+
+    it 'contains relevant information about last deployment' do
+      last_deployment = json.fetch(:last_deployment)
+
+      expect(last_deployment)
+        .to include(:ref, :user, :commit, :deployable, :manual_actions)
+    end
+  end
+
+  context 'when there is a collection of objects provided' do
+    let(:project) { create(:empty_project) }
+    let(:resource) { create_list(:environment, 2) }
+
+    it 'contains important elements of environment' do
+      expect(json.first)
+        .to include(:last_deployment, :name, :external_url)
+    end
+
+    it 'generates payload for collection' do
+      expect(json).to be_an_instance_of Array
+    end
+  end
+end
diff --git a/spec/serializers/user_entity_spec.rb b/spec/serializers/user_entity_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c5d11cbcf5ee4b2f3842109fb154613901f27182
--- /dev/null
+++ b/spec/serializers/user_entity_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe UserEntity do
+  let(:entity) { described_class.new(user) }
+  let(:user) { create(:user) }
+  subject { entity.as_json }
+
+  it 'exposes user name and login' do
+    expect(subject).to include(:username, :name)
+  end
+
+  it 'does not expose passwords' do
+    expect(subject).not_to include(/password/)
+  end
+
+  it 'does not expose tokens' do
+    expect(subject).not_to include(/token/)
+  end
+
+  it 'does not expose 2FA OTPs' do
+    expect(subject).not_to include(/otp/)
+  end
+end