diff --git a/app/assets/javascripts/environments/components/environment_stop.vue b/app/assets/javascripts/environments/components/environment_stop.vue
index 0d4a1e76eb8a6c1159f8b9e62afa5e8ca49e4a67..17a70fd0c34c3b32427960cfdd8cfc76471d53eb 100644
--- a/app/assets/javascripts/environments/components/environment_stop.vue
+++ b/app/assets/javascripts/environments/components/environment_stop.vue
@@ -8,6 +8,8 @@ import { GlTooltipDirective, GlButton, GlModalDirective } from '@gitlab/ui';
 import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
 import { s__ } from '~/locale';
 import eventHub from '../event_hub';
+import setEnvironmentToStopMutation from '../graphql/mutations/set_environment_to_stop.mutation.graphql';
+import isEnvironmentStoppingQuery from '../graphql/queries/is_environment_stopping.query.graphql';
 
 export default {
   components: {
@@ -22,6 +24,19 @@ export default {
       type: Object,
       required: true,
     },
+    graphql: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  apollo: {
+    isEnvironmentStopping: {
+      query: isEnvironmentStoppingQuery,
+      variables() {
+        return { environment: this.environment };
+      },
+    },
   },
   i18n: {
     title: s__('Environments|Stop environment'),
@@ -30,6 +45,7 @@ export default {
   data() {
     return {
       isLoading: false,
+      isEnvironmentStopping: false,
     };
   },
   mounted() {
@@ -41,7 +57,14 @@ export default {
   methods: {
     onClick() {
       this.$root.$emit(BV_HIDE_TOOLTIP, this.$options.stopEnvironmentTooltipId);
-      eventHub.$emit('requestStopEnvironment', this.environment);
+      if (this.graphql) {
+        this.$apollo.mutate({
+          mutation: setEnvironmentToStopMutation,
+          variables: { environment: this.environment },
+        });
+      } else {
+        eventHub.$emit('requestStopEnvironment', this.environment);
+      }
     },
     onStopEnvironment(environment) {
       if (this.environment.id === environment.id) {
@@ -56,7 +79,7 @@ export default {
   <gl-button
     v-gl-tooltip="{ id: $options.stopEnvironmentTooltipId }"
     v-gl-modal-directive="'stop-environment-modal'"
-    :loading="isLoading"
+    :loading="isLoading || isEnvironmentStopping"
     :title="$options.i18n.title"
     :aria-label="$options.i18n.title"
     icon="stop"
diff --git a/app/assets/javascripts/environments/components/new_environments_app.vue b/app/assets/javascripts/environments/components/new_environments_app.vue
index 8d94e7021ca218f750018b77855db1f22ce6fd91..02ccdb534567c58369d2794fa7101f94570ed333 100644
--- a/app/assets/javascripts/environments/components/new_environments_app.vue
+++ b/app/assets/javascripts/environments/components/new_environments_app.vue
@@ -5,8 +5,10 @@ import { updateHistory, setUrlParams, queryToObject } from '~/lib/utils/url_util
 import environmentAppQuery from '../graphql/queries/environment_app.query.graphql';
 import pollIntervalQuery from '../graphql/queries/poll_interval.query.graphql';
 import pageInfoQuery from '../graphql/queries/page_info.query.graphql';
+import environmentToStopQuery from '../graphql/queries/environment_to_stop.query.graphql';
 import EnvironmentFolder from './new_environment_folder.vue';
 import EnableReviewAppModal from './enable_review_app_modal.vue';
+import StopEnvironmentModal from './stop_environment_modal.vue';
 
 export default {
   components: {
@@ -16,6 +18,7 @@ export default {
     GlPagination,
     GlTab,
     GlTabs,
+    StopEnvironmentModal,
   },
   apollo: {
     environmentApp: {
@@ -36,6 +39,9 @@ export default {
     pageInfo: {
       query: pageInfoQuery,
     },
+    environmentToStop: {
+      query: environmentToStopQuery,
+    },
   },
   inject: ['newEnvironmentPath', 'canCreateEnvironment'],
   i18n: {
@@ -57,6 +63,7 @@ export default {
       isReviewAppModalVisible: false,
       page: parseInt(page, 10),
       scope,
+      environmentToStop: {},
     };
   },
   computed: {
@@ -157,6 +164,7 @@ export default {
       :modal-id="$options.modalId"
       data-testid="enable-review-app-modal"
     />
+    <stop-environment-modal :environment="environmentToStop" graphql />
     <gl-tabs
       :action-secondary="addEnvironment"
       :action-primary="openReviewAppModal"
diff --git a/app/assets/javascripts/environments/components/stop_environment_modal.vue b/app/assets/javascripts/environments/components/stop_environment_modal.vue
index 7a9233048a948f23e01e7fe8afc1b0f061b9565f..162ad598c8c2594d81ce19dc6db59eb203b65701 100644
--- a/app/assets/javascripts/environments/components/stop_environment_modal.vue
+++ b/app/assets/javascripts/environments/components/stop_environment_modal.vue
@@ -2,6 +2,7 @@
 import { GlSprintf, GlTooltipDirective, GlModal } from '@gitlab/ui';
 import { __, s__ } from '~/locale';
 import eventHub from '../event_hub';
+import stopEnvironmentMutation from '../graphql/mutations/stop_environment.mutation.graphql';
 
 export default {
   id: 'stop-environment-modal',
@@ -21,6 +22,11 @@ export default {
       type: Object,
       required: true,
     },
+    graphql: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
   },
 
   computed: {
@@ -39,7 +45,14 @@ export default {
 
   methods: {
     onSubmit() {
-      eventHub.$emit('stopEnvironment', this.environment);
+      if (this.graphql) {
+        this.$apollo.mutate({
+          mutation: stopEnvironmentMutation,
+          variables: { environment: this.environment },
+        });
+      } else {
+        eventHub.$emit('stopEnvironment', this.environment);
+      }
     },
   },
 };
diff --git a/app/assets/javascripts/environments/graphql/mutations/set_environment_to_stop.mutation.graphql b/app/assets/javascripts/environments/graphql/mutations/set_environment_to_stop.mutation.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..2891f4c5101a5342a9357f31b65fa4bda7f452c5
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/mutations/set_environment_to_stop.mutation.graphql
@@ -0,0 +1,3 @@
+mutation SetEnvironmentToStop($environment: LocalEnvironmentInput) {
+  setEnvironmentToStop(environment: $environment) @client
+}
diff --git a/app/assets/javascripts/environments/graphql/queries/environment_to_stop.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment_to_stop.query.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..128846145e8f7520adc91b6ceb3c57b496d50361
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/queries/environment_to_stop.query.graphql
@@ -0,0 +1,3 @@
+query environmentToStop {
+  environmentToStop @client
+}
diff --git a/app/assets/javascripts/environments/graphql/queries/is_environment_stopping.query.graphql b/app/assets/javascripts/environments/graphql/queries/is_environment_stopping.query.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..ad05e252e6f9b49d7bc730f9b769fee02a17becb
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/queries/is_environment_stopping.query.graphql
@@ -0,0 +1,3 @@
+query isEnvironmentStopping($environment: LocalEnvironment) {
+  isEnvironmentStopping(environment: $environment) @client
+}
diff --git a/app/assets/javascripts/environments/graphql/resolvers.js b/app/assets/javascripts/environments/graphql/resolvers.js
index 9ebbc0ad1f84f9bd0b29afaf8a291e000b7123d4..a2b3bda05fa28aa4441950b948374284270d1ef0 100644
--- a/app/assets/javascripts/environments/graphql/resolvers.js
+++ b/app/assets/javascripts/environments/graphql/resolvers.js
@@ -8,6 +8,7 @@ import {
 
 import pollIntervalQuery from './queries/poll_interval.query.graphql';
 import environmentToRollbackQuery from './queries/environment_to_rollback.query.graphql';
+import environmentToStopQuery from './queries/environment_to_stop.query.graphql';
 import environmentToDeleteQuery from './queries/environment_to_delete.query.graphql';
 import pageInfoQuery from './queries/page_info.query.graphql';
 
@@ -108,6 +109,12 @@ export const resolvers = (endpoint) => ({
           ]);
         });
     },
+    setEnvironmentToStop(_, { environment }, { client }) {
+      client.writeQuery({
+        query: environmentToStopQuery,
+        data: { environmentToStop: environment },
+      });
+    },
     setEnvironmentToDelete(_, { environment }, { client }) {
       client.writeQuery({
         query: environmentToDeleteQuery,
diff --git a/app/assets/javascripts/environments/graphql/typedefs.graphql b/app/assets/javascripts/environments/graphql/typedefs.graphql
index 4a3abb0e89f84d5daf0d68a19fd0c4a8c99d4502..64cab480c98a4a188cac29c869e553abe7708b37 100644
--- a/app/assets/javascripts/environments/graphql/typedefs.graphql
+++ b/app/assets/javascripts/environments/graphql/typedefs.graphql
@@ -68,6 +68,8 @@ extend type Query {
   environmentToDelete: LocalEnvironment
   pageInfo: LocalPageInfo
   environmentToRollback: LocalEnvironment
+  environmentToStop: LocalEnvironment
+  isEnvironmentStopping(environment: LocalEnvironmentInput): Boolean
   isLastDeployment: Boolean
 }
 
@@ -78,4 +80,5 @@ extend type Mutation {
   cancelAutoStop(environment: LocalEnvironmentInput): LocalErrors
   setEnvironmentToDelete(environment: LocalEnvironmentInput): LocalErrors
   setEnvironmentToRollback(environment: LocalEnvironmentInput): LocalErrors
+  setEnvironmentToStop(environment: LocalEnvironmentInput): LocalErrors
 }
diff --git a/spec/frontend/environments/environment_stop_spec.js b/spec/frontend/environments/environment_stop_spec.js
index dff444b79f3c16d981b313c0c821196a68ff599e..358abca2f7769deda659df439a273400839058f9 100644
--- a/spec/frontend/environments/environment_stop_spec.js
+++ b/spec/frontend/environments/environment_stop_spec.js
@@ -1,38 +1,80 @@
 import { GlButton } from '@gitlab/ui';
 import { shallowMount } from '@vue/test-utils';
-import $ from 'jquery';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import setEnvironmentToStopMutation from '~/environments/graphql/mutations/set_environment_to_stop.mutation.graphql';
+import isEnvironmentStoppingQuery from '~/environments/graphql/queries/is_environment_stopping.query.graphql';
 import StopComponent from '~/environments/components/environment_stop.vue';
 import eventHub from '~/environments/event_hub';
-
-$.fn.tooltip = () => {};
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { resolvedEnvironment } from './graphql/mock_data';
 
 describe('Stop Component', () => {
   let wrapper;
 
-  const createWrapper = () => {
+  const createWrapper = (props = {}, options = {}) => {
     wrapper = shallowMount(StopComponent, {
       propsData: {
         environment: {},
+        ...props,
       },
+      ...options,
     });
   };
 
   const findButton = () => wrapper.find(GlButton);
 
-  beforeEach(() => {
-    jest.spyOn(window, 'confirm');
+  describe('eventHub', () => {
+    beforeEach(() => {
+      createWrapper();
+    });
 
-    createWrapper();
-  });
+    it('should render a button to stop the environment', () => {
+      expect(findButton().exists()).toBe(true);
+      expect(wrapper.attributes('title')).toEqual('Stop environment');
+    });
 
-  it('should render a button to stop the environment', () => {
-    expect(findButton().exists()).toBe(true);
-    expect(wrapper.attributes('title')).toEqual('Stop environment');
+    it('emits requestStopEnvironment in the event hub when button is clicked', () => {
+      jest.spyOn(eventHub, '$emit');
+      findButton().vm.$emit('click');
+      expect(eventHub.$emit).toHaveBeenCalledWith('requestStopEnvironment', wrapper.vm.environment);
+    });
   });
 
-  it('emits requestStopEnvironment in the event hub when button is clicked', () => {
-    jest.spyOn(eventHub, '$emit');
-    findButton().vm.$emit('click');
-    expect(eventHub.$emit).toHaveBeenCalledWith('requestStopEnvironment', wrapper.vm.environment);
+  describe('graphql', () => {
+    Vue.use(VueApollo);
+    let mockApollo;
+
+    beforeEach(() => {
+      mockApollo = createMockApollo();
+      mockApollo.clients.defaultClient.writeQuery({
+        query: isEnvironmentStoppingQuery,
+        variables: { environment: resolvedEnvironment },
+        data: { isEnvironmentStopping: true },
+      });
+
+      createWrapper(
+        { graphql: true, environment: resolvedEnvironment },
+        { apolloProvider: mockApollo },
+      );
+    });
+
+    it('should render a button to stop the environment', () => {
+      expect(findButton().exists()).toBe(true);
+      expect(wrapper.attributes('title')).toEqual('Stop environment');
+    });
+
+    it('sets the environment to stop on click', () => {
+      jest.spyOn(mockApollo.defaultClient, 'mutate');
+      findButton().vm.$emit('click');
+      expect(mockApollo.defaultClient.mutate).toHaveBeenCalledWith({
+        mutation: setEnvironmentToStopMutation,
+        variables: { environment: resolvedEnvironment },
+      });
+    });
+
+    it('should show a loading icon if the environment is currently stopping', async () => {
+      expect(findButton().props('loading')).toBe(true);
+    });
   });
 });
diff --git a/spec/frontend/environments/graphql/resolvers_spec.js b/spec/frontend/environments/graphql/resolvers_spec.js
index d8d26b7450469dbefbcfbed1a3861db6ddfc20c7..9238f92dc9506202d5bb4d19511a4bd862066fd7 100644
--- a/spec/frontend/environments/graphql/resolvers_spec.js
+++ b/spec/frontend/environments/graphql/resolvers_spec.js
@@ -3,6 +3,7 @@ import axios from '~/lib/utils/axios_utils';
 import { resolvers } from '~/environments/graphql/resolvers';
 import environmentToRollback from '~/environments/graphql/queries/environment_to_rollback.query.graphql';
 import environmentToDelete from '~/environments/graphql/queries/environment_to_delete.query.graphql';
+import environmentToStopQuery from '~/environments/graphql/queries/environment_to_stop.query.graphql';
 import createMockApollo from 'helpers/mock_apollo_helper';
 import pollIntervalQuery from '~/environments/graphql/queries/poll_interval.query.graphql';
 import pageInfoQuery from '~/environments/graphql/queries/page_info.query.graphql';
@@ -210,4 +211,19 @@ describe('~/frontend/environments/graphql/resolvers', () => {
       });
     });
   });
+  describe('setEnvironmentToStop', () => {
+    it('should write the given environment to the cache', () => {
+      localState.client.writeQuery = jest.fn();
+      mockResolvers.Mutation.setEnvironmentToStop(
+        null,
+        { environment: resolvedEnvironment },
+        localState,
+      );
+
+      expect(localState.client.writeQuery).toHaveBeenCalledWith({
+        query: environmentToStopQuery,
+        data: { environmentToStop: resolvedEnvironment },
+      });
+    });
+  });
 });
diff --git a/spec/frontend/environments/new_environments_app_spec.js b/spec/frontend/environments/new_environments_app_spec.js
index 1e9bd4d64c92348a33625836e2b911f0d47f1940..368645b8046c4d02c496d936b8a6bf5224965d03 100644
--- a/spec/frontend/environments/new_environments_app_spec.js
+++ b/spec/frontend/environments/new_environments_app_spec.js
@@ -8,7 +8,8 @@ import setWindowLocation from 'helpers/set_window_location_helper';
 import { sprintf, __, s__ } from '~/locale';
 import EnvironmentsApp from '~/environments/components/new_environments_app.vue';
 import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue';
-import { resolvedEnvironmentsApp, resolvedFolder } from './graphql/mock_data';
+import StopEnvironmentModal from '~/environments/components/stop_environment_modal.vue';
+import { resolvedEnvironmentsApp, resolvedFolder, resolvedEnvironment } from './graphql/mock_data';
 
 Vue.use(VueApollo);
 
@@ -17,6 +18,7 @@ describe('~/environments/components/new_environments_app.vue', () => {
   let environmentAppMock;
   let environmentFolderMock;
   let paginationMock;
+  let environmentToStopMock;
 
   const createApolloProvider = () => {
     const mockResolvers = {
@@ -24,6 +26,7 @@ describe('~/environments/components/new_environments_app.vue', () => {
         environmentApp: environmentAppMock,
         folder: environmentFolderMock,
         pageInfo: paginationMock,
+        environmentToStop: environmentToStopMock,
       },
     };
 
@@ -45,6 +48,7 @@ describe('~/environments/components/new_environments_app.vue', () => {
     provide = {},
     environmentsApp,
     folder,
+    environmentToStop = {},
     pageInfo = {
       total: 20,
       perPage: 5,
@@ -58,6 +62,7 @@ describe('~/environments/components/new_environments_app.vue', () => {
     environmentAppMock.mockReturnValue(environmentsApp);
     environmentFolderMock.mockReturnValue(folder);
     paginationMock.mockReturnValue(pageInfo);
+    environmentToStopMock.mockReturnValue(environmentToStop);
     const apolloProvider = createApolloProvider();
     wrapper = createWrapper({ apolloProvider, provide });
 
@@ -68,6 +73,7 @@ describe('~/environments/components/new_environments_app.vue', () => {
   beforeEach(() => {
     environmentAppMock = jest.fn();
     environmentFolderMock = jest.fn();
+    environmentToStopMock = jest.fn();
     paginationMock = jest.fn();
   });
 
@@ -175,6 +181,20 @@ describe('~/environments/components/new_environments_app.vue', () => {
     });
   });
 
+  describe('modals', () => {
+    it('should pass the environment to stop to the stop environment modal', async () => {
+      await createWrapperWithMocked({
+        environmentsApp: resolvedEnvironmentsApp,
+        folder: resolvedFolder,
+        environmentToStop: resolvedEnvironment,
+      });
+
+      const modal = wrapper.findComponent(StopEnvironmentModal);
+
+      expect(modal.props('environment')).toMatchObject(resolvedEnvironment);
+    });
+  });
+
   describe('pagination', () => {
     it('should sync page from query params on load', async () => {
       await createWrapperWithMocked({