diff --git a/app/assets/javascripts/environments/components/edit_environment.vue b/app/assets/javascripts/environments/components/edit_environment.vue
index 44d681b71bd8beabcad71dad5611b960fdf02d08..1532a7428f675a27ad128cbd0f9da4410474666b 100644
--- a/app/assets/javascripts/environments/components/edit_environment.vue
+++ b/app/assets/javascripts/environments/components/edit_environment.vue
@@ -2,7 +2,6 @@
 import { GlLoadingIcon } from '@gitlab/ui';
 import { createAlert } from '~/alert';
 import { visitUrl } from '~/lib/utils/url_utility';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
 import getEnvironment from '../graphql/queries/environment.query.graphql';
 import updateEnvironment from '../graphql/mutations/update_environment.mutation.graphql';
 import EnvironmentForm from './environment_form.vue';
@@ -12,11 +11,9 @@ export default {
     GlLoadingIcon,
     EnvironmentForm,
   },
-  mixins: [glFeatureFlagsMixin()],
   inject: ['projectEnvironmentsPath', 'projectPath', 'environmentName'],
   apollo: {
-    // eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
-    environment: {
+    formEnvironment: {
       query: getEnvironment,
       variables() {
         return {
@@ -26,7 +23,7 @@ export default {
       },
       update(data) {
         const result = data?.project?.environment || {};
-        this.formEnvironment = { ...result, clusterAgentId: result?.clusterAgent?.id };
+        return { ...result, clusterAgentId: result?.clusterAgent?.id };
       },
     },
   },
@@ -38,7 +35,7 @@ export default {
   },
   computed: {
     isQueryLoading() {
-      return this.$apollo.queries.environment.loading;
+      return this.$apollo.queries.formEnvironment.loading;
     },
   },
   methods: {
@@ -53,6 +50,7 @@ export default {
           variables: {
             input: {
               id: this.formEnvironment.id,
+              description: this.formEnvironment.description,
               externalUrl: this.formEnvironment.externalUrl,
               clusterAgentId: this.formEnvironment.clusterAgentId,
               kubernetesNamespace: this.formEnvironment.kubernetesNamespace,
diff --git a/app/assets/javascripts/environments/components/environment_form.vue b/app/assets/javascripts/environments/components/environment_form.vue
index f0ea119a986ff4fb0aeeba90beb9adb8661efa70..b3c9fecff72fad5a9f44bda954466a89d395265d 100644
--- a/app/assets/javascripts/environments/components/environment_form.vue
+++ b/app/assets/javascripts/environments/components/environment_form.vue
@@ -16,7 +16,7 @@ import {
   ENVIRONMENT_EDIT_HELP_TEXT,
 } from 'ee_else_ce/environments/constants';
 import csrf from '~/lib/utils/csrf';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
 import { getIdFromGraphQLId } from '~/graphql_shared/utils';
 import getUserAuthorizedAgents from '../graphql/queries/user_authorized_agents.query.graphql';
 import EnvironmentFluxResourceSelector from './environment_flux_resource_selector.vue';
@@ -33,12 +33,13 @@ export default {
     GlSprintf,
     EnvironmentFluxResourceSelector,
     EnvironmentNamespaceSelector,
+    MarkdownEditor,
   },
-  mixins: [glFeatureFlagsMixin()],
   inject: {
     protectedEnvironmentSettingsPath: { default: '' },
     projectPath: { default: '' },
     kasTunnelUrl: { default: '' },
+    markdownPreviewPath: { default: '' },
   },
   props: {
     environment: {
@@ -71,6 +72,11 @@ export default {
     nameFeedback: __('This field is required'),
     nameDisabledHelp: __("You cannot rename an environment after it's created."),
     nameDisabledLinkText: __('How do I rename an environment?'),
+    descriptionLabel: __('Description'),
+    descriptionPlaceholder: s__('Environments|Write a description or drag your files here…'),
+    descriptionHelpText: s__(
+      'Environments|The description is displayed to anyone who can see this environment.',
+    ),
     urlLabel: __('External URL'),
     urlFeedback: __('The URL should start with http:// or https://'),
     agentLabel: s__('Environments|GitLab agent'),
@@ -84,6 +90,8 @@ export default {
   renamingDisabledHelpPagePath: helpPagePath('ci/environments/index.md', {
     anchor: 'rename-an-environment',
   }),
+  markdownDocsPath: helpPagePath('user/markdown'),
+  restrictedToolbarItems: ['full-screen'],
   data() {
     return {
       visited: {
@@ -158,6 +166,13 @@ export default {
         credentials: 'include',
       };
     },
+    descriptionFieldProps() {
+      return {
+        'aria-label': this.$options.i18n.descriptionLabel,
+        placeholder: this.$options.i18n.descriptionPlaceholder,
+        id: 'environment_description',
+      };
+    },
   },
   watch: {
     environment(change) {
@@ -172,6 +187,11 @@ export default {
     visit(field) {
       this.visited[field] = true;
     },
+    updateDescription($event) {
+      if (this.environment.description !== $event) {
+        this.onChange({ ...this.environment, description: $event });
+      }
+    },
     getAgentsList() {
       this.$apollo.addSmartQuery('userAccessAuthorizedAgents', {
         variables() {
@@ -253,6 +273,24 @@ export default {
             @blur="visit('name')"
           />
         </gl-form-group>
+        <gl-form-group
+          :label="$options.i18n.descriptionLabel"
+          :description="$options.i18n.descriptionHelpText"
+          label-for="environment_description"
+          :state="valid.description"
+        >
+          <div class="common-note-form gfm-form">
+            <markdown-editor
+              :value="environment.description"
+              :render-markdown-path="markdownPreviewPath"
+              :form-field-props="descriptionFieldProps"
+              :restricted-tool-bar-items="$options.restrictedToolbarItems"
+              :markdown-docs-path="$options.markdownDocsPath"
+              :disabled="loading"
+              @input="updateDescription"
+            />
+          </div>
+        </gl-form-group>
         <gl-form-group
           :label="$options.i18n.urlLabel"
           :state="valid.url"
@@ -320,6 +358,7 @@ export default {
             variant="confirm"
             name="commit"
             class="js-no-auto-disable"
+            data-testid="save-environment"
             >{{ $options.i18n.save }}</gl-button
           >
           <gl-button :href="cancelPath">{{ $options.i18n.cancel }}</gl-button>
diff --git a/app/assets/javascripts/environments/components/environments_detail_header.vue b/app/assets/javascripts/environments/components/environments_detail_header.vue
index 19e2d42bf4f76cef5ad30db0ad355c683e96c4e2..d8b33d73b0620d18594103753b9ef938434750e9 100644
--- a/app/assets/javascripts/environments/components/environments_detail_header.vue
+++ b/app/assets/javascripts/environments/components/environments_detail_header.vue
@@ -1,9 +1,17 @@
 <script>
-import { GlButton, GlModalDirective, GlTooltipDirective as GlTooltip, GlSprintf } from '@gitlab/ui';
+import {
+  GlButton,
+  GlTruncateText,
+  GlModalDirective,
+  GlTooltipDirective as GlTooltip,
+  GlSprintf,
+} from '@gitlab/ui';
 import csrf from '~/lib/utils/csrf';
 import { __, s__ } from '~/locale';
 import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
 import timeagoMixin from '~/vue_shared/mixins/timeago';
+import { renderGFM } from '~/behaviors/markdown/render_gfm';
+import SafeHtml from '~/vue_shared/directives/safe_html';
 import DeleteEnvironmentModal from './delete_environment_modal.vue';
 import StopEnvironmentModal from './stop_environment_modal.vue';
 import DeployFreezeAlert from './deploy_freeze_alert.vue';
@@ -14,6 +22,7 @@ export default {
   components: {
     GlButton,
     GlSprintf,
+    GlTruncateText,
     TimeAgo,
     DeployFreezeAlert,
     DeleteEnvironmentModal,
@@ -22,6 +31,7 @@ export default {
   directives: {
     GlModalDirective,
     GlTooltip,
+    SafeHtml,
   },
   mixins: [timeagoMixin],
   props: {
@@ -69,6 +79,7 @@ export default {
     externalButtonTitle: s__('Environments|Open live environment'),
     externalButtonText: __('View deployment'),
     cancelAutoStopButtonTitle: __('Prevent environment from auto-stopping'),
+    showMoreText: __('Read more'),
   },
   computed: {
     shouldShowCancelAutoStopButton() {
@@ -84,6 +95,10 @@ export default {
       return this.canAdminEnvironment && this.environment.hasTerminals;
     },
   },
+  mounted() {
+    renderGFM(this.$refs['gfm-content']);
+  },
+  safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
 };
 </script>
 <template>
@@ -159,5 +174,18 @@ export default {
       <delete-environment-modal v-if="canDestroyEnvironment" :environment="environment" />
       <stop-environment-modal v-if="shouldShowStopButton" :environment="environment" />
     </header>
+
+    <gl-truncate-text
+      v-if="environment.descriptionHtml"
+      :show-more-text="$options.i18n.showMoreText"
+      class="gl-relative gl-mb-4"
+    >
+      <div
+        ref="gfm-content"
+        v-safe-html:[$options.safeHtmlConfig]="environment.descriptionHtml"
+        class="md"
+        data-testid="environment-description-content"
+      ></div>
+    </gl-truncate-text>
   </div>
 </template>
diff --git a/app/assets/javascripts/environments/components/new_environment.vue b/app/assets/javascripts/environments/components/new_environment.vue
index 6a4ed34989f60844e429bc8655b291c9c4f356d0..11b63d1e0993153d607e9f61efce249b7ce10248 100644
--- a/app/assets/javascripts/environments/components/new_environment.vue
+++ b/app/assets/javascripts/environments/components/new_environment.vue
@@ -13,6 +13,7 @@ export default {
     return {
       environment: {
         name: '',
+        description: '',
         externalUrl: '',
         clusterAgentId: null,
       },
@@ -31,6 +32,7 @@ export default {
           variables: {
             input: {
               name: this.environment.name,
+              description: this.environment.description,
               externalUrl: this.environment.externalUrl,
               projectPath: this.projectPath,
               clusterAgentId: this.environment.clusterAgentId,
diff --git a/app/assets/javascripts/environments/edit.js b/app/assets/javascripts/environments/edit.js
index 3f22b83e618e4fa29d12748efe8132511b2040c5..c58a0152d62ea8469294071d965c2295c894018f 100644
--- a/app/assets/javascripts/environments/edit.js
+++ b/app/assets/javascripts/environments/edit.js
@@ -15,6 +15,7 @@ export default (el) => {
     projectEnvironmentsPath,
     protectedEnvironmentSettingsPath,
     projectPath,
+    markdownPreviewPath,
     environmentName,
     kasTunnelUrl,
   } = el.dataset;
@@ -26,6 +27,7 @@ export default (el) => {
       projectEnvironmentsPath,
       protectedEnvironmentSettingsPath,
       projectPath,
+      markdownPreviewPath,
       environmentName,
       kasTunnelUrl: removeLastSlashInUrlPath(kasTunnelUrl),
     },
diff --git a/app/assets/javascripts/environments/graphql/queries/environment.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment.query.graphql
index 2d6faed5c88a73dc02e6c4a631633d5ad3403b00..0df0c5aa16062f7cd0f48efeb51bd877ad9cab0c 100644
--- a/app/assets/javascripts/environments/graphql/queries/environment.query.graphql
+++ b/app/assets/javascripts/environments/graphql/queries/environment.query.graphql
@@ -4,6 +4,7 @@ query getEnvironment($projectFullPath: ID!, $environmentName: String) {
     environment(name: $environmentName) {
       id
       name
+      description
       externalUrl
       kubernetesNamespace
       fluxResourcePath
diff --git a/app/assets/javascripts/environments/mount_show.js b/app/assets/javascripts/environments/mount_show.js
index 523c27022bb1bb96941131cfc8e36ef8cf1774bf..38ef597cd3dcc567c986ce7071f2e84032b3c930 100644
--- a/app/assets/javascripts/environments/mount_show.js
+++ b/app/assets/javascripts/environments/mount_show.js
@@ -37,9 +37,10 @@ export const initHeader = () => {
         autoStopAt: dataset.autoStopAt,
         onSingleEnvironmentPage: true,
         // TODO: These two props are snake_case because the environments_mixin file uses
-        // them and the mixin is imported in several files. It would be nice to conver them to camelCase.
+        // them and the mixin is imported in several files. It would be nice to convert them to camelCase.
         stop_path: dataset.environmentStopPath,
         delete_path: dataset.environmentDeletePath,
+        descriptionHtml: dataset.descriptionHtml,
       };
 
       return {
diff --git a/app/assets/javascripts/environments/new.js b/app/assets/javascripts/environments/new.js
index 652085b1f28905b769b8f2c3441707f1b564b591..92de3ae5b2ba0a86a4c791831a6cfbace4512d23 100644
--- a/app/assets/javascripts/environments/new.js
+++ b/app/assets/javascripts/environments/new.js
@@ -11,7 +11,7 @@ export default (el) => {
     return null;
   }
 
-  const { projectEnvironmentsPath, projectPath, kasTunnelUrl } = el.dataset;
+  const { projectEnvironmentsPath, projectPath, markdownPreviewPath, kasTunnelUrl } = el.dataset;
 
   return new Vue({
     el,
@@ -19,6 +19,7 @@ export default (el) => {
     provide: {
       projectEnvironmentsPath,
       projectPath,
+      markdownPreviewPath,
       kasTunnelUrl: removeLastSlashInUrlPath(kasTunnelUrl),
     },
     render(h) {
diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb
index 88007841df1f1f44c4c99740fc7c97df0ebe8f14..e8ab2f72c93ecab60706c82487be4e5b036f6450 100644
--- a/app/helpers/environment_helper.rb
+++ b/app/helpers/environment_helper.rb
@@ -32,6 +32,7 @@ def environments_detail_data(user, project, environment)
       environment_terminal_path: terminal_project_environment_path(project, environment),
       has_terminals: environment.has_terminals?,
       is_environment_available: environment.available?,
+      description_html: markdown_field(environment, :description),
       auto_stop_at: environment.auto_stop_at,
       graphql_etag_key: environment.etag_cache_key
     }
diff --git a/app/views/projects/environments/edit.html.haml b/app/views/projects/environments/edit.html.haml
index 4f9b093a91f0eb66a4ee4610e4cfb5eb8b0ec59c..4664fed282f6006fdb31bc323976b75d5e75b189 100644
--- a/app/views/projects/environments/edit.html.haml
+++ b/app/views/projects/environments/edit.html.haml
@@ -6,6 +6,7 @@
 
 #js-edit-environment{ data: { project_environments_path: project_environments_path(@project),
                               protected_environment_settings_path: (project_settings_ci_cd_path(@project, anchor: 'js-protected-environments-settings') if @project.licensed_feature_available?(:protected_environments)),
+                              markdown_preview_path: preview_markdown_path(@project),
                               project_path: @project.full_path,
                               environment_name: @environment.name,
                               kas_tunnel_url: ::Gitlab::Kas.tunnel_url } }
diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml
index a25dd5fdec6a752a86c3140615e6c264bcad2599..769d21fb86b617d59214dba6ba10693510750653 100644
--- a/app/views/projects/environments/new.html.haml
+++ b/app/views/projects/environments/new.html.haml
@@ -3,4 +3,4 @@
 - page_title s_("Environments|New environment")
 - add_page_specific_style 'page_bundles/environments'
 
-#js-new-environment{ data: { project_environments_path: project_environments_path(@project), project_path: @project.full_path, kas_tunnel_url: ::Gitlab::Kas.tunnel_url } }
+#js-new-environment{ data: { project_environments_path: project_environments_path(@project), project_path: @project.full_path, kas_tunnel_url: ::Gitlab::Kas.tunnel_url, markdown_preview_path: preview_markdown_path(@project) } }
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 438809ccddc10eb832a45ab89cb5606566845b8e..122acaec222f5782d88278e2aa6dbc7b0ef896aa 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -21490,6 +21490,9 @@ msgstr ""
 msgid "Environments|Synced"
 msgstr ""
 
+msgid "Environments|The description is displayed to anyone who can see this environment."
+msgstr ""
+
 msgid "Environments|There are no deployments for this environment yet. %{linkStart}Learn more about setting up deployments.%{linkEnd}"
 msgstr ""
 
@@ -21517,6 +21520,9 @@ msgstr ""
 msgid "Environments|View logs"
 msgstr ""
 
+msgid "Environments|Write a description or drag your files here…"
+msgstr ""
+
 msgid "Environments|by %{avatar}"
 msgstr ""
 
diff --git a/spec/frontend/environments/edit_environment_spec.js b/spec/frontend/environments/edit_environment_spec.js
index 62b62bbc8265c76458a1034c7bf434ffd80457a3..364d821994c849426f912a730f248ddfd32c61e8 100644
--- a/spec/frontend/environments/edit_environment_spec.js
+++ b/spec/frontend/environments/edit_environment_spec.js
@@ -17,6 +17,7 @@ const environment = {
   id: '1',
   name: 'foo',
   externalUrl: 'https://foo.example.com',
+  description: 'this is description',
   clusterAgent: null,
   kubernetesNamespace: null,
   fluxResourcePath: null,
@@ -78,6 +79,7 @@ describe('~/environments/components/edit.vue', () => {
   const findForm = () => wrapper.findByRole('form', { name: 'Edit environment' });
 
   const showsLoading = () => wrapper.findComponent(GlLoadingIcon).exists();
+  const showsFormLoading = () => wrapper.findByTestId('save-environment').props('loading');
 
   describe('default', () => {
     it('performs the environment apollo query', () => {
@@ -127,11 +129,11 @@ describe('~/environments/components/edit.vue', () => {
       });
 
       it('shows loader after form is submitted', async () => {
-        expect(showsLoading()).toBe(false);
+        expect(showsFormLoading()).toBe(false);
 
         await findForm().trigger('submit');
 
-        expect(showsLoading()).toBe(true);
+        expect(showsFormLoading()).toBe(true);
       });
 
       it('submits the updated environment on submit', async () => {
@@ -154,7 +156,7 @@ describe('~/environments/components/edit.vue', () => {
         await waitForPromises();
 
         expect(createAlert).toHaveBeenCalledWith({ message: 'uh oh!' });
-        expect(showsLoading()).toBe(false);
+        expect(showsFormLoading()).toBe(false);
       });
     });
   });
diff --git a/spec/frontend/environments/environment_form_spec.js b/spec/frontend/environments/environment_form_spec.js
index ad00617ce906d44e10f9964355e2ad99d694d050..cb7bbfb3874865fc277b3c6d1468cdd90e709400 100644
--- a/spec/frontend/environments/environment_form_spec.js
+++ b/spec/frontend/environments/environment_form_spec.js
@@ -3,6 +3,7 @@ import Vue, { nextTick } from 'vue';
 import VueApollo from 'vue-apollo';
 import waitForPromises from 'helpers/wait_for_promises';
 import { mountExtended } from 'helpers/vue_test_utils_helper';
+import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
 import EnvironmentForm from '~/environments/components/environment_form.vue';
 import getUserAuthorizedAgents from '~/environments/graphql/queries/user_authorized_agents.query.graphql';
 import EnvironmentFluxResourceSelector from '~/environments/components/environment_flux_resource_selector.vue';
@@ -13,7 +14,7 @@ import { mockKasTunnelUrl } from './mock_data';
 jest.mock('~/lib/utils/csrf');
 
 const DEFAULT_PROPS = {
-  environment: { name: '', externalUrl: '' },
+  environment: { name: '', externalUrl: '', description: '' },
   title: 'environment',
   cancelPath: '/cancel',
 };
@@ -21,6 +22,7 @@ const DEFAULT_PROPS = {
 const PROVIDE = {
   protectedEnvironmentSettingsPath: '/projects/not_real/settings/ci_cd',
   kasTunnelUrl: mockKasTunnelUrl,
+  markdownPreviewPath: '/path/to/markdown/preview',
 };
 const userAccessAuthorizedAgents = [
   { agent: { id: '1', name: 'agent-1' } },
@@ -89,6 +91,7 @@ describe('~/environments/components/form.vue', () => {
   const findAgentSelector = () => wrapper.findByTestId('agent-selector');
   const findNamespaceSelector = () => wrapper.findComponent(EnvironmentNamespaceSelector);
   const findFluxResourceSelector = () => wrapper.findComponent(EnvironmentFluxResourceSelector);
+  const findMarkdownField = () => wrapper.findComponent(MarkdownEditor);
 
   const selectAgent = async () => {
     findAgentSelector().vm.$emit('shown');
@@ -128,7 +131,9 @@ describe('~/environments/components/form.vue', () => {
         await name.setValue('test');
         await name.trigger('blur');
 
-        expect(wrapper.emitted('change')).toEqual([[{ name: 'test', externalUrl: '' }]]);
+        expect(wrapper.emitted('change')).toEqual([
+          [{ name: 'test', externalUrl: '', description: '' }],
+        ]);
       });
 
       it('should validate that the name is required', async () => {
@@ -152,7 +157,7 @@ describe('~/environments/components/form.vue', () => {
         await url.trigger('blur');
 
         expect(wrapper.emitted('change')).toEqual([
-          [{ name: '', externalUrl: 'https://example.com' }],
+          [{ name: '', externalUrl: 'https://example.com', description: '' }],
         ]);
       });
 
@@ -185,6 +190,7 @@ describe('~/environments/components/form.vue', () => {
         environment: {
           name: '',
           externalUrl: '',
+          description: '',
         },
       });
     });
@@ -226,6 +232,7 @@ describe('~/environments/components/form.vue', () => {
           id: 1,
           name: 'test',
           externalUrl: 'https://example.com',
+          description: '',
         },
       });
     });
@@ -255,6 +262,42 @@ describe('~/environments/components/form.vue', () => {
     });
   });
 
+  describe('description', () => {
+    it('renders markdown field', () => {
+      wrapper = createWrapper();
+
+      expect(findMarkdownField().props()).toMatchObject({
+        value: '',
+        renderMarkdownPath: PROVIDE.markdownPreviewPath,
+        markdownDocsPath: '/help/user/markdown',
+        disabled: false,
+      });
+    });
+
+    it('sets the markdown value when provided', () => {
+      wrapper = createWrapper({
+        environment: { name: 'production', externalUrl: '', description: 'some-description' },
+      });
+
+      expect(findMarkdownField().props('value')).toBe('some-description');
+    });
+
+    it('emits changes on user input', async () => {
+      wrapper = createWrapper();
+      await findMarkdownField().vm.$emit('input', 'my new description');
+
+      expect(wrapper.emitted('change').at(-1)).toEqual([
+        { name: '', externalUrl: '', description: 'my new description' },
+      ]);
+    });
+
+    it('disables field on loading', () => {
+      wrapper = createWrapper({ loading: true });
+
+      expect(findMarkdownField().props('disabled')).toBe(true);
+    });
+  });
+
   describe('agent selector', () => {
     beforeEach(() => {
       wrapper = createWrapperWithApollo();
@@ -301,6 +344,7 @@ describe('~/environments/components/form.vue', () => {
           {
             name: '',
             externalUrl: '',
+            description: '',
             clusterAgentId: '2',
             kubernetesNamespace: null,
             fluxResourcePath: null,
@@ -334,7 +378,13 @@ describe('~/environments/components/form.vue', () => {
         await nextTick();
 
         expect(wrapper.emitted('change')[1]).toEqual([
-          { name: '', externalUrl: '', kubernetesNamespace: 'agent', fluxResourcePath: null },
+          {
+            name: '',
+            externalUrl: '',
+            description: '',
+            kubernetesNamespace: 'agent',
+            fluxResourcePath: null,
+          },
         ]);
       });
     });
diff --git a/spec/frontend/environments/environments_detail_header_spec.js b/spec/frontend/environments/environments_detail_header_spec.js
index b59d9f457d67069c47c026013f3fdf915c28cb65..958cbd4bd6f52788f161a68fc3fbcb712d89ca18 100644
--- a/spec/frontend/environments/environments_detail_header_spec.js
+++ b/spec/frontend/environments/environments_detail_header_spec.js
@@ -27,6 +27,7 @@ describe('Environments detail header component', () => {
   const findStopEnvironmentModal = () => wrapper.findComponent(StopEnvironmentModal);
   const findDeleteEnvironmentModal = () => wrapper.findComponent(DeleteEnvironmentModal);
   const findDeployFreezeAlert = () => wrapper.findComponent(DeployFreezeAlert);
+  const findDescription = () => wrapper.findByTestId('environment-description-content');
 
   const buttons = [
     ['Cancel Auto Stop At', findCancelAutoStopAtButton],
@@ -229,4 +230,22 @@ describe('Environments detail header component', () => {
       expect(findDeployFreezeAlert().props('name')).toBe(environment.name);
     });
   });
+
+  describe('environment description', () => {
+    it.each`
+      condition           | descriptionHtml          | renderDescription
+      ${"doesn't render"} | ${''}                    | ${false}
+      ${'renders'}        | ${'this is description'} | ${true}
+    `(
+      '$condition when `descriptionHtml` is "$descriptionHtml"',
+      ({ descriptionHtml, renderDescription }) => {
+        const environment = createEnvironment({ descriptionHtml });
+        createWrapper({
+          props: { environment },
+        });
+
+        expect(findDescription().exists()).toBe(renderDescription);
+      },
+    );
+  });
 });
diff --git a/spec/frontend/environments/new_environment_spec.js b/spec/frontend/environments/new_environment_spec.js
index e95e5d4285220535775266a01d3716eb9937b37d..78cd4282af60691b040d901783838b1d0f5e9a8f 100644
--- a/spec/frontend/environments/new_environment_spec.js
+++ b/spec/frontend/environments/new_environment_spec.js
@@ -1,4 +1,3 @@
-import { GlLoadingIcon } from '@gitlab/ui';
 import Vue from 'vue';
 import VueApollo from 'vue-apollo';
 import { mountExtended } from 'helpers/vue_test_utils_helper';
@@ -49,7 +48,7 @@ describe('~/environments/components/new.vue', () => {
   const findNameInput = () => wrapper.findByLabelText('Name');
   const findExternalUrlInput = () => wrapper.findByLabelText('External URL');
   const findForm = () => wrapper.findByRole('form', { name: 'New environment' });
-  const showsLoading = () => wrapper.findComponent(GlLoadingIcon).exists();
+  const showsLoading = () => wrapper.findByTestId('save-environment').props('loading');
 
   const submitForm = async () => {
     await findNameInput().setValue('test');
diff --git a/spec/helpers/environment_helper_spec.rb b/spec/helpers/environment_helper_spec.rb
index 4e842cfbcdc9c56139652914d31bcc62dbd744db..6c32355b616bd22df1de8613953874330a5accda 100644
--- a/spec/helpers/environment_helper_spec.rb
+++ b/spec/helpers/environment_helper_spec.rb
@@ -9,7 +9,9 @@
     let_it_be(:auto_stop_at) { Time.now.utc }
     let_it_be(:user) { create(:user) }
     let_it_be(:project, reload: true) { create(:project, :repository) }
-    let_it_be(:environment) { create(:environment, project: project, auto_stop_at: auto_stop_at) }
+    let_it_be(:environment) do
+      create(:environment, project: project, auto_stop_at: auto_stop_at, description: '_description_')
+    end
 
     before do
       allow(helper).to receive(:current_user).and_return(user)
@@ -35,6 +37,7 @@
         environment_terminal_path: terminal_project_environment_path(project, environment),
         has_terminals: false,
         is_environment_available: true,
+        description_html: '<p data-sourcepos="1:1-1:13" dir="auto"><em data-sourcepos="1:1-1:13">description</em></p>',
         auto_stop_at: auto_stop_at,
         graphql_etag_key: environment.etag_cache_key
       }.to_json)