diff --git a/app/assets/javascripts/google_cloud/components/cloudsql/create_instance_form.vue b/app/assets/javascripts/google_cloud/components/cloudsql/create_instance_form.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0ac561b6132c397d21747b0e68c61a2005046146
--- /dev/null
+++ b/app/assets/javascripts/google_cloud/components/cloudsql/create_instance_form.vue
@@ -0,0 +1,132 @@
+<script>
+import { GlButton, GlFormCheckbox, GlFormGroup, GlFormSelect } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+const i18n = {
+  gcpProjectLabel: s__('CloudSeed|Google Cloud project'),
+  gcpProjectDescription: s__(
+    'CloudSeed|Database instance is generated within the selected Google Cloud project',
+  ),
+  refsLabel: s__('CloudSeed|Refs'),
+  refsDescription: s__(
+    'CloudSeed|Generated database instance is linked to the selected branch or tag',
+  ),
+  databaseVersionLabel: s__('CloudSeed|Database version'),
+  tierLabel: s__('CloudSeed|Machine type'),
+  tierDescription: s__('CloudSeed|Determines memory and virtual cores available to your instance'),
+  checkboxLabel: s__(
+    'CloudSeed|I accept Google Cloud pricing and responsibilities involved with managing database instances',
+  ),
+  cancelLabel: s__('CloudSeed|Cancel'),
+  submitLabel: s__('CloudSeed|Create instance'),
+  all: s__('CloudSeed|All'),
+};
+
+export default {
+  ALL_REFS: '*',
+  components: {
+    GlButton,
+    GlFormCheckbox,
+    GlFormGroup,
+    GlFormSelect,
+  },
+  props: {
+    cancelPath: { required: true, type: String },
+    gcpProjects: { required: true, type: Array },
+    refs: { required: true, type: Array },
+    formTitle: { required: true, type: String },
+    formDescription: { required: true, type: String },
+    databaseVersions: { required: true, type: Array },
+    tiers: { required: true, type: Array },
+  },
+  i18n,
+};
+</script>
+<template>
+  <div>
+    <header class="gl-my-5 gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid">
+      <h2 class="gl-font-size-h1">{{ formTitle }}</h2>
+      <p>{{ formDescription }}</p>
+    </header>
+
+    <gl-form-group
+      data-testid="form_group_gcp_project"
+      label-for="gcp_project"
+      :label="$options.i18n.gcpProjectLabel"
+      :description="$options.i18n.gcpProjectDescription"
+    >
+      <gl-form-select id="gcp_project" data-testid="select_gcp_project" name="gcp_project" required>
+        <option
+          v-for="gcpProject in gcpProjects"
+          :key="gcpProject.project_id"
+          :value="gcpProject.project_id"
+        >
+          {{ gcpProject.name }}
+        </option>
+      </gl-form-select>
+    </gl-form-group>
+
+    <gl-form-group
+      data-testid="form_group_environments"
+      label-for="ref"
+      :label="$options.i18n.refsLabel"
+      :description="$options.i18n.refsDescription"
+    >
+      <gl-form-select id="ref" data-testid="select_environments" name="ref" required>
+        <option :value="$options.ALL_REFS">{{ $options.i18n.all }}</option>
+        <option v-for="ref in refs" :key="ref" :value="ref">
+          {{ ref }}
+        </option>
+      </gl-form-select>
+    </gl-form-group>
+
+    <gl-form-group
+      data-testid="form_group_tier"
+      label-for="tier"
+      :label="$options.i18n.tierLabel"
+      :description="$options.i18n.tierDescription"
+    >
+      <gl-form-select id="tier" data-testid="select_tier" name="tier" required>
+        <option v-for="tier in tiers" :key="tier.value" :value="tier.value">
+          {{ tier.label }}
+        </option>
+      </gl-form-select>
+    </gl-form-group>
+
+    <gl-form-group
+      data-testid="form_group_database_version"
+      label-for="database-version"
+      :label="$options.i18n.databaseVersionLabel"
+    >
+      <gl-form-select
+        id="database-version"
+        data-testid="select_database_version"
+        name="database_version"
+        required
+      >
+        <option
+          v-for="databaseVersion in databaseVersions"
+          :key="databaseVersion.value"
+          :value="databaseVersion.value"
+        >
+          {{ databaseVersion.label }}
+        </option>
+      </gl-form-select>
+    </gl-form-group>
+
+    <gl-form-group>
+      <gl-form-checkbox name="confirmation" required>
+        {{ $options.i18n.checkboxLabel }}
+      </gl-form-checkbox>
+    </gl-form-group>
+
+    <div class="form-actions row">
+      <gl-button type="submit" category="primary" variant="confirm" data-testid="submit-button">
+        {{ $options.i18n.submitLabel }}
+      </gl-button>
+      <gl-button class="gl-ml-1" :href="cancelPath" data-testid="cancel-button">{{
+        $options.i18n.cancelLabel
+      }}</gl-button>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/google_cloud/components/cloudsql/instance_table.vue b/app/assets/javascripts/google_cloud/components/cloudsql/instance_table.vue
new file mode 100644
index 0000000000000000000000000000000000000000..823895214dfa217943abb41e82d4261966a96f06
--- /dev/null
+++ b/app/assets/javascripts/google_cloud/components/cloudsql/instance_table.vue
@@ -0,0 +1,75 @@
+<script>
+import { GlEmptyState, GlLink, GlTable } from '@gitlab/ui';
+import { encodeSaferUrl, setUrlParams } from '~/lib/utils/url_utility';
+import { s__ } from '~/locale';
+
+const i18n = {
+  noInstancesTitle: s__('CloudSeed|No instances'),
+  noInstancesDescription: s__('CloudSeed|There are no instances to display.'),
+  title: s__('CloudSeed|Instances'),
+  description: s__('CloudSeed|Database instances associated with this project'),
+};
+
+export default {
+  components: { GlEmptyState, GlLink, GlTable },
+  props: {
+    cloudsqlInstances: {
+      type: Array,
+      required: true,
+    },
+    emptyIllustrationUrl: {
+      type: String,
+      required: true,
+    },
+  },
+  computed: {
+    tableData() {
+      return this.cloudsqlInstances.filter((instance) => instance.instance_name);
+    },
+  },
+  methods: {
+    gcpProjectUrl(id) {
+      return setUrlParams({ project: id }, 'https://console.cloud.google.com/sql/instances');
+    },
+    instanceUrl(name, id) {
+      const saferName = encodeSaferUrl(name);
+
+      return setUrlParams(
+        { project: id },
+        `https://console.cloud.google.com/sql/instances/${saferName}/overview`,
+      );
+    },
+  },
+  fields: [
+    { key: 'ref', label: s__('CloudSeed|Environment') },
+    { key: 'gcp_project', label: s__('CloudSeed|Google Cloud Project') },
+    { key: 'instance_name', label: s__('CloudSeed|CloudSQL Instance') },
+    { key: 'version', label: s__('CloudSeed|Version') },
+  ],
+  i18n,
+};
+</script>
+
+<template>
+  <div class="gl-mx-3">
+    <gl-empty-state
+      v-if="tableData.length === 0"
+      :title="$options.i18n.noInstancesTitle"
+      :description="$options.i18n.noInstancesDescription"
+      :svg-path="emptyIllustrationUrl"
+    />
+
+    <div v-else>
+      <h2 class="gl-font-size-h2">{{ $options.i18n.title }}</h2>
+      <p>{{ $options.i18n.description }}</p>
+      <gl-table :fields="$options.fields" :items="tableData">
+        <template #cell(gcp_project)="{ value }">
+          <gl-link :href="gcpProjectUrl(value)">{{ value }}</gl-link>
+        </template>
+        <template #cell(instance_name)="{ item: { instance_name, gcp_project } }">
+          <a :href="instanceUrl(instance_name, gcp_project)">{{ instance_name }}</a>
+        </template>
+      </gl-table>
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/google_cloud/components/databases/service_table.vue b/app/assets/javascripts/google_cloud/components/databases/service_table.vue
new file mode 100644
index 0000000000000000000000000000000000000000..80bd6ef28fb28ed437b5e8d4b27c8de9faf5fe47
--- /dev/null
+++ b/app/assets/javascripts/google_cloud/components/databases/service_table.vue
@@ -0,0 +1,221 @@
+<script>
+import { GlAlert, GlButton, GlLink, GlSprintf, GlTable } from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { s__ } from '~/locale';
+
+const KEY_CLOUDSQL_POSTGRES = 'cloudsql-postgres';
+const KEY_CLOUDSQL_MYSQL = 'cloudsql-mysql';
+const KEY_CLOUDSQL_SQLSERVER = 'cloudsql-sqlserver';
+const KEY_ALLOYDB_POSTGRES = 'alloydb-postgres';
+const KEY_MEMORYSTORE_REDIS = 'memorystore-redis';
+const KEY_FIRESTORE = 'firestore';
+
+const i18n = {
+  columnService: s__('CloudSeed|Service'),
+  columnDescription: s__('CloudSeed|Description'),
+  cloudsqlPostgresTitle: s__('CloudSeed|Cloud SQL for Postgres'),
+  cloudsqlPostgresDescription: s__(
+    'CloudSeed|Fully managed relational database service for PostgreSQL',
+  ),
+  cloudsqlMysqlTitle: s__('CloudSeed|Cloud SQL for MySQL'),
+  cloudsqlMysqlDescription: s__('CloudSeed|Fully managed relational database service for MySQL'),
+  cloudsqlSqlserverTitle: s__('CloudSeed|Cloud SQL for SQL Server'),
+  cloudsqlSqlserverDescription: s__(
+    'CloudSeed|Fully managed relational database service for SQL Server',
+  ),
+  alloydbPostgresTitle: s__('CloudSeed|AlloyDB for Postgres'),
+  alloydbPostgresDescription: s__(
+    'CloudSeed|Fully managed PostgreSQL-compatible service for high-demand workloads',
+  ),
+  memorystoreRedisTitle: s__('CloudSeed|Memorystore for Redis'),
+  memorystoreRedisDescription: s__(
+    'CloudSeed|Scalable, secure, and highly available in-memory service for Redis',
+  ),
+  firestoreTitle: s__('CloudSeed|Cloud Firestore'),
+  firestoreDescription: s__(
+    'CloudSeed|Flexible, scalable NoSQL cloud database for client- and server-side development',
+  ),
+  createInstance: s__('CloudSeed|Create instance'),
+  createCluster: s__('CloudSeed|Create cluster'),
+  createDatabase: s__('CloudSeed|Create database'),
+  title: s__('CloudSeed|Services'),
+  description: s__('CloudSeed|Available database services through which instances may be created'),
+  pricingAlert: s__(
+    'CloudSeed|Learn more about pricing for %{cloudsqlPricingStart}Cloud SQL%{cloudsqlPricingEnd}, %{alloydbPricingStart}Alloy DB%{alloydbPricingEnd}, %{memorystorePricingStart}Memorystore%{memorystorePricingEnd} and %{firestorePricingStart}Firestore%{firestorePricingEnd}.',
+  ),
+  secretManagersDescription: s__(
+    'CloudSeed|Enhance security by storing database variables in secret managers - learn more about %{docLinkStart}secret management with GitLab%{docLinkEnd}',
+  ),
+};
+
+const helpUrlSecrets = helpPagePath('ee/ci/secrets');
+
+export default {
+  components: { GlAlert, GlButton, GlLink, GlSprintf, GlTable },
+  props: {
+    cloudsqlPostgresUrl: {
+      type: String,
+      required: true,
+    },
+    cloudsqlMysqlUrl: {
+      type: String,
+      required: true,
+    },
+    cloudsqlSqlserverUrl: {
+      type: String,
+      required: true,
+    },
+    alloydbPostgresUrl: {
+      type: String,
+      required: true,
+    },
+    memorystoreRedisUrl: {
+      type: String,
+      required: true,
+    },
+    firestoreUrl: {
+      type: String,
+      required: true,
+    },
+  },
+  methods: {
+    actionUrl(key) {
+      switch (key) {
+        case KEY_CLOUDSQL_POSTGRES:
+          return this.cloudsqlPostgresUrl;
+        case KEY_CLOUDSQL_MYSQL:
+          return this.cloudsqlMysqlUrl;
+        case KEY_CLOUDSQL_SQLSERVER:
+          return this.cloudsqlSqlserverUrl;
+        case KEY_ALLOYDB_POSTGRES:
+          return this.alloydbPostgresUrl;
+        case KEY_MEMORYSTORE_REDIS:
+          return this.memorystoreRedisUrl;
+        case KEY_FIRESTORE:
+          return this.firestoreUrl;
+        default:
+          return '#';
+      }
+    },
+  },
+  fields: [
+    { key: 'title', label: i18n.columnService },
+    { key: 'description', label: i18n.columnDescription },
+    { key: 'action', label: '' },
+  ],
+  items: [
+    {
+      title: i18n.cloudsqlPostgresTitle,
+      description: i18n.cloudsqlPostgresDescription,
+      action: {
+        key: KEY_CLOUDSQL_POSTGRES,
+        title: i18n.createInstance,
+        testId: 'button-cloudsql-postgres',
+      },
+    },
+    {
+      title: i18n.cloudsqlMysqlTitle,
+      description: i18n.cloudsqlMysqlDescription,
+      action: {
+        disabled: false,
+        key: KEY_CLOUDSQL_MYSQL,
+        title: i18n.createInstance,
+        testId: 'button-cloudsql-mysql',
+      },
+    },
+    {
+      title: i18n.cloudsqlSqlserverTitle,
+      description: i18n.cloudsqlSqlserverDescription,
+      action: {
+        disabled: false,
+        key: KEY_CLOUDSQL_SQLSERVER,
+        title: i18n.createInstance,
+        testId: 'button-cloudsql-sqlserver',
+      },
+    },
+    {
+      title: i18n.alloydbPostgresTitle,
+      description: i18n.alloydbPostgresDescription,
+      action: {
+        disabled: true,
+        key: KEY_ALLOYDB_POSTGRES,
+        title: i18n.createCluster,
+        testId: 'button-alloydb-postgres',
+      },
+    },
+    {
+      title: i18n.memorystoreRedisTitle,
+      description: i18n.memorystoreRedisDescription,
+      action: {
+        disabled: true,
+        key: KEY_MEMORYSTORE_REDIS,
+        title: i18n.createInstance,
+        testId: 'button-memorystore-redis',
+      },
+    },
+    {
+      title: i18n.firestoreTitle,
+      description: i18n.firestoreDescription,
+      action: {
+        disabled: true,
+        key: KEY_FIRESTORE,
+        title: i18n.createDatabase,
+        testId: 'button-firestore',
+      },
+    },
+  ],
+  helpUrlSecrets,
+  i18n,
+};
+</script>
+
+<template>
+  <div class="gl-mx-3">
+    <h2 class="gl-font-size-h2">{{ $options.i18n.title }}</h2>
+    <p>{{ $options.i18n.description }}</p>
+
+    <gl-table :fields="$options.fields" :items="$options.items">
+      <template #cell(action)="{ value }">
+        <gl-button
+          block
+          :disabled="value.disabled"
+          :href="actionUrl(value.key)"
+          :data-testid="value.testId"
+          category="secondary"
+          variant="confirm"
+        >
+          {{ value.title }}
+        </gl-button>
+      </template>
+    </gl-table>
+
+    <gl-alert class="gl-mt-5" :dismissible="false" variant="tip">
+      <gl-sprintf :message="$options.i18n.pricingAlert">
+        <template #cloudsqlPricing="{ content }">
+          <gl-link href="https://cloud.google.com/sql/pricing">{{ content }}</gl-link>
+        </template>
+        <template #alloydbPricing="{ content }">
+          <gl-link href="https://cloud.google.com/alloydb/pricing">{{ content }}</gl-link>
+        </template>
+        <template #memorystorePricing="{ content }">
+          <gl-link href="https://cloud.google.com/memorystore/docs/redis/pricing">{{
+            content
+          }}</gl-link>
+        </template>
+        <template #firestorePricing="{ content }">
+          <gl-link href="https://cloud.google.com/firestore/pricing">{{ content }}</gl-link>
+        </template>
+      </gl-sprintf>
+    </gl-alert>
+
+    <gl-alert class="gl-mt-5" :dismissible="false" variant="tip">
+      <gl-sprintf :message="$options.i18n.secretManagersDescription">
+        <template #docLink="{ content }">
+          <gl-link :href="$options.helpUrlSecrets">
+            {{ content }}
+          </gl-link>
+        </template>
+      </gl-sprintf>
+    </gl-alert>
+  </div>
+</template>
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index e02822d4617727bf7a09333ccc46a469d48fb015..eda8c83fe0836ec30d3e013141dc21dfd24410b4 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8255,6 +8255,123 @@ msgstr ""
 msgid "Cloud Storage"
 msgstr ""
 
+msgid "CloudSeed|All"
+msgstr ""
+
+msgid "CloudSeed|AlloyDB for Postgres"
+msgstr ""
+
+msgid "CloudSeed|Available database services through which instances may be created"
+msgstr ""
+
+msgid "CloudSeed|Cancel"
+msgstr ""
+
+msgid "CloudSeed|Cloud Firestore"
+msgstr ""
+
+msgid "CloudSeed|Cloud SQL for MySQL"
+msgstr ""
+
+msgid "CloudSeed|Cloud SQL for Postgres"
+msgstr ""
+
+msgid "CloudSeed|Cloud SQL for SQL Server"
+msgstr ""
+
+msgid "CloudSeed|CloudSQL Instance"
+msgstr ""
+
+msgid "CloudSeed|Create cluster"
+msgstr ""
+
+msgid "CloudSeed|Create database"
+msgstr ""
+
+msgid "CloudSeed|Create instance"
+msgstr ""
+
+msgid "CloudSeed|Database instance is generated within the selected Google Cloud project"
+msgstr ""
+
+msgid "CloudSeed|Database instances associated with this project"
+msgstr ""
+
+msgid "CloudSeed|Database version"
+msgstr ""
+
+msgid "CloudSeed|Description"
+msgstr ""
+
+msgid "CloudSeed|Determines memory and virtual cores available to your instance"
+msgstr ""
+
+msgid "CloudSeed|Enhance security by storing database variables in secret managers - learn more about %{docLinkStart}secret management with GitLab%{docLinkEnd}"
+msgstr ""
+
+msgid "CloudSeed|Environment"
+msgstr ""
+
+msgid "CloudSeed|Flexible, scalable NoSQL cloud database for client- and server-side development"
+msgstr ""
+
+msgid "CloudSeed|Fully managed PostgreSQL-compatible service for high-demand workloads"
+msgstr ""
+
+msgid "CloudSeed|Fully managed relational database service for MySQL"
+msgstr ""
+
+msgid "CloudSeed|Fully managed relational database service for PostgreSQL"
+msgstr ""
+
+msgid "CloudSeed|Fully managed relational database service for SQL Server"
+msgstr ""
+
+msgid "CloudSeed|Generated database instance is linked to the selected branch or tag"
+msgstr ""
+
+msgid "CloudSeed|Google Cloud Project"
+msgstr ""
+
+msgid "CloudSeed|Google Cloud project"
+msgstr ""
+
+msgid "CloudSeed|I accept Google Cloud pricing and responsibilities involved with managing database instances"
+msgstr ""
+
+msgid "CloudSeed|Instances"
+msgstr ""
+
+msgid "CloudSeed|Learn more about pricing for %{cloudsqlPricingStart}Cloud SQL%{cloudsqlPricingEnd}, %{alloydbPricingStart}Alloy DB%{alloydbPricingEnd}, %{memorystorePricingStart}Memorystore%{memorystorePricingEnd} and %{firestorePricingStart}Firestore%{firestorePricingEnd}."
+msgstr ""
+
+msgid "CloudSeed|Machine type"
+msgstr ""
+
+msgid "CloudSeed|Memorystore for Redis"
+msgstr ""
+
+msgid "CloudSeed|No instances"
+msgstr ""
+
+msgid "CloudSeed|Refs"
+msgstr ""
+
+msgid "CloudSeed|Scalable, secure, and highly available in-memory service for Redis"
+msgstr ""
+
+msgid "CloudSeed|Service"
+msgstr ""
+
+msgid "CloudSeed|Services"
+msgstr ""
+
+msgid "CloudSeed|There are no instances to display."
+msgstr ""
+
+msgid "CloudSeed|Version"
+msgstr ""
+
 msgid "Cluster"
 msgstr ""
 
diff --git a/spec/frontend/google_cloud/components/cloudsql/create_instance_form_spec.js b/spec/frontend/google_cloud/components/cloudsql/create_instance_form_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..de644a33b503afa61dc56acfa3f1009839558437
--- /dev/null
+++ b/spec/frontend/google_cloud/components/cloudsql/create_instance_form_spec.js
@@ -0,0 +1,103 @@
+import { GlFormCheckbox } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import InstanceForm from '~/google_cloud/components/cloudsql/create_instance_form.vue';
+
+describe('google_cloud::cloudsql::create_instance_form component', () => {
+  let wrapper;
+
+  const findByTestId = (id) => wrapper.findByTestId(id);
+  const findCancelButton = () => findByTestId('cancel-button');
+  const findCheckbox = () => wrapper.findComponent(GlFormCheckbox);
+  const findHeader = () => wrapper.find('header');
+  const findSubmitButton = () => findByTestId('submit-button');
+
+  const propsData = {
+    gcpProjects: [],
+    refs: [],
+    cancelPath: '#cancel-url',
+    formTitle: 'mock form title',
+    formDescription: 'mock form description',
+    databaseVersions: [],
+    tiers: [],
+  };
+
+  beforeEach(() => {
+    wrapper = shallowMountExtended(InstanceForm, { propsData, stubs: { GlFormCheckbox } });
+  });
+
+  afterEach(() => {
+    wrapper.destroy();
+  });
+
+  it('contains header', () => {
+    expect(findHeader().exists()).toBe(true);
+  });
+
+  it('contains GCP project form group', () => {
+    const formGroup = findByTestId('form_group_gcp_project');
+    expect(formGroup.exists()).toBe(true);
+    expect(formGroup.attributes('label')).toBe(InstanceForm.i18n.gcpProjectLabel);
+    expect(formGroup.attributes('description')).toBe(InstanceForm.i18n.gcpProjectDescription);
+  });
+
+  it('contains GCP project dropdown', () => {
+    const select = findByTestId('select_gcp_project');
+    expect(select.exists()).toBe(true);
+  });
+
+  it('contains Environments form group', () => {
+    const formGroup = findByTestId('form_group_environments');
+    expect(formGroup.exists()).toBe(true);
+    expect(formGroup.attributes('label')).toBe(InstanceForm.i18n.refsLabel);
+    expect(formGroup.attributes('description')).toBe(InstanceForm.i18n.refsDescription);
+  });
+
+  it('contains Environments dropdown', () => {
+    const select = findByTestId('select_environments');
+    expect(select.exists()).toBe(true);
+  });
+
+  it('contains Tier form group', () => {
+    const formGroup = findByTestId('form_group_tier');
+    expect(formGroup.exists()).toBe(true);
+    expect(formGroup.attributes('label')).toBe(InstanceForm.i18n.tierLabel);
+    expect(formGroup.attributes('description')).toBe(InstanceForm.i18n.tierDescription);
+  });
+
+  it('contains Tier dropdown', () => {
+    const select = findByTestId('select_tier');
+    expect(select.exists()).toBe(true);
+  });
+
+  it('contains Database Version form group', () => {
+    const formGroup = findByTestId('form_group_database_version');
+    expect(formGroup.exists()).toBe(true);
+    expect(formGroup.attributes('label')).toBe(InstanceForm.i18n.databaseVersionLabel);
+  });
+
+  it('contains Database Version dropdown', () => {
+    const select = findByTestId('select_database_version');
+    expect(select.exists()).toBe(true);
+  });
+
+  it('contains Submit button', () => {
+    expect(findSubmitButton().exists()).toBe(true);
+    expect(findSubmitButton().text()).toBe(InstanceForm.i18n.submitLabel);
+  });
+
+  it('contains Cancel button', () => {
+    expect(findCancelButton().exists()).toBe(true);
+    expect(findCancelButton().text()).toBe(InstanceForm.i18n.cancelLabel);
+    expect(findCancelButton().attributes('href')).toBe('#cancel-url');
+  });
+
+  it('contains Confirmation checkbox', () => {
+    const checkbox = findCheckbox();
+    expect(checkbox.text()).toBe(InstanceForm.i18n.checkboxLabel);
+  });
+
+  it('checkbox must be required', () => {
+    const checkbox = findCheckbox();
+    expect(checkbox.attributes('required')).toBe('true');
+  });
+});
diff --git a/spec/frontend/google_cloud/components/cloudsql/instance_table_spec.js b/spec/frontend/google_cloud/components/cloudsql/instance_table_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..286f2b8e37938cb8e1956b1a2d645fbd37d90ca2
--- /dev/null
+++ b/spec/frontend/google_cloud/components/cloudsql/instance_table_spec.js
@@ -0,0 +1,65 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlEmptyState, GlTable } from '@gitlab/ui';
+import InstanceTable from '~/google_cloud/components/cloudsql/instance_table.vue';
+
+describe('google_cloud::databases::service_table component', () => {
+  let wrapper;
+
+  const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+  const findTable = () => wrapper.findComponent(GlTable);
+
+  afterEach(() => {
+    wrapper.destroy();
+  });
+
+  describe('when there are no instances', () => {
+    beforeEach(() => {
+      const propsData = {
+        cloudsqlInstances: [],
+        emptyIllustrationUrl: '#empty-illustration-url',
+      };
+      wrapper = shallowMount(InstanceTable, { propsData });
+    });
+
+    it('should depict empty state', () => {
+      const emptyState = findEmptyState();
+      expect(emptyState.exists()).toBe(true);
+      expect(emptyState.attributes('title')).toBe(InstanceTable.i18n.noInstancesTitle);
+      expect(emptyState.attributes('description')).toBe(InstanceTable.i18n.noInstancesDescription);
+    });
+  });
+
+  describe('when there are three instances', () => {
+    beforeEach(() => {
+      const propsData = {
+        cloudsqlInstances: [
+          {
+            ref: '*',
+            gcp_project: 'test-gcp-project',
+            instance_name: 'postgres-14-instance',
+            version: 'POSTGRES_14',
+          },
+          {
+            ref: 'production',
+            gcp_project: 'prod-gcp-project',
+            instance_name: 'postgres-14-instance',
+            version: 'POSTGRES_14',
+          },
+          {
+            ref: 'staging',
+            gcp_project: 'test-gcp-project',
+            instance_name: 'postgres-14-instance',
+            version: 'POSTGRES_14',
+          },
+        ],
+        emptyIllustrationUrl: '#empty-illustration-url',
+      };
+      wrapper = shallowMount(InstanceTable, { propsData });
+    });
+
+    it('should contain a table', () => {
+      const table = findTable();
+      expect(table.exists()).toBe(true);
+    });
+  });
+});
diff --git a/spec/frontend/google_cloud/components/databases/service_table_spec.js b/spec/frontend/google_cloud/components/databases/service_table_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..142e32c1a4bfeda27e4a6e1b78d59af03f9e25d1
--- /dev/null
+++ b/spec/frontend/google_cloud/components/databases/service_table_spec.js
@@ -0,0 +1,44 @@
+import { GlTable } from '@gitlab/ui';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import ServiceTable from '~/google_cloud/components/databases/service_table.vue';
+
+describe('google_cloud::databases::service_table component', () => {
+  let wrapper;
+
+  const findTable = () => wrapper.findComponent(GlTable);
+
+  beforeEach(() => {
+    const propsData = {
+      cloudsqlPostgresUrl: '#url-cloudsql-postgres',
+      cloudsqlMysqlUrl: '#url-cloudsql-mysql',
+      cloudsqlSqlserverUrl: '#url-cloudsql-sqlserver',
+      alloydbPostgresUrl: '#url-alloydb-postgres',
+      memorystoreRedisUrl: '#url-memorystore-redis',
+      firestoreUrl: '#url-firestore',
+    };
+    wrapper = mountExtended(ServiceTable, { propsData });
+  });
+
+  afterEach(() => {
+    wrapper.destroy();
+  });
+
+  it('should contain a table', () => {
+    expect(findTable().exists()).toBe(true);
+  });
+
+  it.each`
+    name                    | testId                         | url
+    ${'cloudsql-postgres'}  | ${'button-cloudsql-postgres'}  | ${'#url-cloudsql-postgres'}
+    ${'cloudsql-mysql'}     | ${'button-cloudsql-mysql'}     | ${'#url-cloudsql-mysql'}
+    ${'cloudsql-sqlserver'} | ${'button-cloudsql-sqlserver'} | ${'#url-cloudsql-sqlserver'}
+    ${'alloydb-postgres'}   | ${'button-alloydb-postgres'}   | ${'#url-alloydb-postgres'}
+    ${'memorystore-redis'}  | ${'button-memorystore-redis'}  | ${'#url-memorystore-redis'}
+    ${'firestore'}          | ${'button-firestore'}          | ${'#url-firestore'}
+  `('renders $name button with correct url', ({ testId, url }) => {
+    const button = wrapper.findByTestId(testId);
+
+    expect(button.exists()).toBe(true);
+    expect(button.attributes('href')).toBe(url);
+  });
+});