diff --git a/app/assets/javascripts/environments/components/new_environments_app.vue b/app/assets/javascripts/environments/components/new_environments_app.vue
index a5526f9cd71b6535f13b5423f4ed532a8b4b1659..bfb5689d6234e360e57fed0732a32560daf1e856 100644
--- a/app/assets/javascripts/environments/components/new_environments_app.vue
+++ b/app/assets/javascripts/environments/components/new_environments_app.vue
@@ -1,6 +1,7 @@
 <script>
 import { GlBadge, GlTab, GlTabs } from '@gitlab/ui';
-import environmentAppQuery from '../graphql/queries/environmentApp.query.graphql';
+import environmentAppQuery from '../graphql/queries/environment_app.query.graphql';
+import pollIntervalQuery from '../graphql/queries/poll_interval.query.graphql';
 import EnvironmentFolder from './new_environment_folder.vue';
 
 export default {
@@ -13,7 +14,16 @@ export default {
   apollo: {
     environmentApp: {
       query: environmentAppQuery,
+      pollInterval() {
+        return this.interval;
+      },
     },
+    interval: {
+      query: pollIntervalQuery,
+    },
+  },
+  data() {
+    return { interval: undefined };
   },
   computed: {
     folders() {
diff --git a/app/assets/javascripts/environments/graphql/client.js b/app/assets/javascripts/environments/graphql/client.js
index c734c2fba0cb26520de3bf9ab98574e5b5198eef..c019b4d16f3eb16a2fefd4a8ad7eb2b3134ef560 100644
--- a/app/assets/javascripts/environments/graphql/client.js
+++ b/app/assets/javascripts/environments/graphql/client.js
@@ -1,6 +1,6 @@
 import VueApollo from 'vue-apollo';
 import createDefaultClient from '~/lib/graphql';
-import environmentApp from './queries/environmentApp.query.graphql';
+import environmentApp from './queries/environment_app.query.graphql';
 import { resolvers } from './resolvers';
 import typeDefs from './typedefs.graphql';
 
diff --git a/app/assets/javascripts/environments/graphql/queries/environmentApp.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql
similarity index 100%
rename from app/assets/javascripts/environments/graphql/queries/environmentApp.query.graphql
rename to app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql
diff --git a/app/assets/javascripts/environments/graphql/queries/poll_interval.query.graphql b/app/assets/javascripts/environments/graphql/queries/poll_interval.query.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..28afc30a0dd2f68caf420af4c62da32ca7920ab8
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/queries/poll_interval.query.graphql
@@ -0,0 +1,3 @@
+query pollInterval {
+  interval @client
+}
diff --git a/app/assets/javascripts/environments/graphql/resolvers.js b/app/assets/javascripts/environments/graphql/resolvers.js
index 8322b806370685e8810347f4b3e560fae9b55739..9bb00f92ac46fedc85641dedf0992cacf0cad629 100644
--- a/app/assets/javascripts/environments/graphql/resolvers.js
+++ b/app/assets/javascripts/environments/graphql/resolvers.js
@@ -1,5 +1,12 @@
 import axios from '~/lib/utils/axios_utils';
+import { s__ } from '~/locale';
 import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import pollIntervalQuery from './queries/poll_interval.query.graphql';
+
+const buildErrors = (errors = []) => ({
+  errors,
+  __typename: 'LocalEnvironmentErrors',
+});
 
 const mapNestedEnvironment = (env) => ({
   ...convertObjectPropsToCamelCase(env, { deep: true }),
@@ -12,17 +19,27 @@ const mapEnvironment = (env) => ({
 
 export const resolvers = (endpoint) => ({
   Query: {
-    environmentApp() {
-      return axios.get(endpoint, { params: { nested: true } }).then((res) => ({
-        availableCount: res.data.available_count,
-        environments: res.data.environments.map(mapNestedEnvironment),
-        reviewApp: {
-          ...convertObjectPropsToCamelCase(res.data.review_app),
-          __typename: 'ReviewApp',
-        },
-        stoppedCount: res.data.stopped_count,
-        __typename: 'LocalEnvironmentApp',
-      }));
+    environmentApp(_context, _variables, { cache }) {
+      return axios.get(endpoint, { params: { nested: true } }).then((res) => {
+        const interval = res.headers['poll-interval'];
+
+        if (interval) {
+          cache.writeQuery({ query: pollIntervalQuery, data: { interval } });
+        } else {
+          cache.writeQuery({ query: pollIntervalQuery, data: { interval: undefined } });
+        }
+
+        return {
+          availableCount: res.data.available_count,
+          environments: res.data.environments.map(mapNestedEnvironment),
+          reviewApp: {
+            ...convertObjectPropsToCamelCase(res.data.review_app),
+            __typename: 'ReviewApp',
+          },
+          stoppedCount: res.data.stopped_count,
+          __typename: 'LocalEnvironmentApp',
+        };
+      });
     },
     folder(_, { environment: { folderPath } }) {
       return axios.get(folderPath, { params: { per_page: 3 } }).then((res) => ({
@@ -32,19 +49,51 @@ export const resolvers = (endpoint) => ({
         __typename: 'LocalEnvironmentFolder',
       }));
     },
+    isLastDeployment(_, { environment }) {
+      // eslint-disable-next-line @gitlab/require-i18n-strings
+      return environment?.lastDeployment?.['last?'];
+    },
   },
-  Mutations: {
-    stopEnvironment(_, { environment: { stopPath } }) {
-      return axios.post(stopPath);
+  Mutation: {
+    stopEnvironment(_, { environment }) {
+      return axios
+        .post(environment.stopPath)
+        .then(() => buildErrors())
+        .catch(() => {
+          return buildErrors([
+            s__('Environments|An error occurred while stopping the environment, please try again'),
+          ]);
+        });
     },
     deleteEnvironment(_, { environment: { deletePath } }) {
       return axios.delete(deletePath);
     },
-    rollbackEnvironment(_, { environment: { retryUrl } }) {
-      return axios.post(retryUrl);
+    rollbackEnvironment(_, { environment, isLastDeployment }) {
+      return axios
+        .post(environment?.retryUrl)
+        .then(() => buildErrors())
+        .catch(() => {
+          buildErrors([
+            isLastDeployment
+              ? s__(
+                  'Environments|An error occurred while re-deploying the environment, please try again',
+                )
+              : s__(
+                  'Environments|An error occurred while rolling back the environment, please try again',
+                ),
+          ]);
+        });
     },
     cancelAutoStop(_, { environment: { autoStopPath } }) {
-      return axios.post(autoStopPath);
+      return axios
+        .post(autoStopPath)
+        .then(() => buildErrors())
+        .catch((err) =>
+          buildErrors([
+            err?.response?.data?.message ||
+              s__('Environments|An error occurred while canceling the auto stop, please try again'),
+          ]),
+        );
     },
   },
 });
diff --git a/app/assets/javascripts/environments/graphql/typedefs.graphql b/app/assets/javascripts/environments/graphql/typedefs.graphql
index 49ea719449edcd77c46e199d257836bba4f603d2..f0172765ebed6057e462c18553cda3dce5bb4da7 100644
--- a/app/assets/javascripts/environments/graphql/typedefs.graphql
+++ b/app/assets/javascripts/environments/graphql/typedefs.graphql
@@ -33,3 +33,20 @@ type LocalEnvironmentApp {
   environments: [NestedLocalEnvironment!]!
   reviewApp: ReviewApp!
 }
+
+type LocalErrors {
+  errors: [String!]!
+}
+
+extend type Query {
+  environmentApp: LocalEnvironmentApp
+  folder(environment: NestedLocalEnvironment): LocalEnvironmentFolder
+  isLastDeployment: Boolean
+}
+
+extend type Mutation {
+  stopEnvironment(environment: LocalEnvironment): LocalErrors
+  deleteEnvironment(environment: LocalEnvironment): LocalErrors
+  rollbackEnvironment(environment: LocalEnvironment): LocalErrors
+  cancelAutoStop(environment: LocalEnvironment): LocalErrors
+}
diff --git a/spec/frontend/environments/graphql/resolvers_spec.js b/spec/frontend/environments/graphql/resolvers_spec.js
index 4d2a0818996adc9e94b20bf7d22d19031e206ac6..320e4794de0eae89e9309c79923f085dc105eefc 100644
--- a/spec/frontend/environments/graphql/resolvers_spec.js
+++ b/spec/frontend/environments/graphql/resolvers_spec.js
@@ -1,6 +1,7 @@
 import MockAdapter from 'axios-mock-adapter';
 import axios from '~/lib/utils/axios_utils';
 import { resolvers } from '~/environments/graphql/resolvers';
+import pollIntervalQuery from '~/environments/graphql/queries/poll_interval.query.graphql';
 import { TEST_HOST } from 'helpers/test_constants';
 import { environmentsApp, resolvedEnvironmentsApp, folder, resolvedFolder } from './mock_data';
 
@@ -21,10 +22,27 @@ describe('~/frontend/environments/graphql/resolvers', () => {
 
   describe('environmentApp', () => {
     it('should fetch environments and map them to frontend data', async () => {
-      mock.onGet(ENDPOINT, { params: { nested: true } }).reply(200, environmentsApp);
+      const cache = { writeQuery: jest.fn() };
+      mock.onGet(ENDPOINT, { params: { nested: true } }).reply(200, environmentsApp, {});
 
-      const app = await mockResolvers.Query.environmentApp();
+      const app = await mockResolvers.Query.environmentApp(null, null, { cache });
       expect(app).toEqual(resolvedEnvironmentsApp);
+      expect(cache.writeQuery).toHaveBeenCalledWith({
+        query: pollIntervalQuery,
+        data: { interval: undefined },
+      });
+    });
+    it('should set the poll interval when there is one', async () => {
+      const cache = { writeQuery: jest.fn() };
+      mock
+        .onGet(ENDPOINT, { params: { nested: true } })
+        .reply(200, environmentsApp, { 'poll-interval': 3000 });
+
+      await mockResolvers.Query.environmentApp(null, null, { cache });
+      expect(cache.writeQuery).toHaveBeenCalledWith({
+        query: pollIntervalQuery,
+        data: { interval: 3000 },
+      });
     });
   });
   describe('folder', () => {
@@ -42,7 +60,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
     it('should post to the stop environment path', async () => {
       mock.onPost(ENDPOINT).reply(200);
 
-      await mockResolvers.Mutations.stopEnvironment(null, { environment: { stopPath: ENDPOINT } });
+      await mockResolvers.Mutation.stopEnvironment(null, { environment: { stopPath: ENDPOINT } });
 
       expect(mock.history.post).toContainEqual(
         expect.objectContaining({ url: ENDPOINT, method: 'post' }),
@@ -53,7 +71,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
     it('should post to the retry environment path', async () => {
       mock.onPost(ENDPOINT).reply(200);
 
-      await mockResolvers.Mutations.rollbackEnvironment(null, {
+      await mockResolvers.Mutation.rollbackEnvironment(null, {
         environment: { retryUrl: ENDPOINT },
       });
 
@@ -66,7 +84,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
     it('should DELETE to the delete environment path', async () => {
       mock.onDelete(ENDPOINT).reply(200);
 
-      await mockResolvers.Mutations.deleteEnvironment(null, {
+      await mockResolvers.Mutation.deleteEnvironment(null, {
         environment: { deletePath: ENDPOINT },
       });
 
@@ -79,7 +97,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
     it('should post to the auto stop path', async () => {
       mock.onPost(ENDPOINT).reply(200);
 
-      await mockResolvers.Mutations.cancelAutoStop(null, {
+      await mockResolvers.Mutation.cancelAutoStop(null, {
         environment: { autoStopPath: ENDPOINT },
       });