Introduction to AI Cloud Security - Discover how to revolutionize your cloud security operations with artificial intelligence. Register now ❯

CloudQuery

GCP
Template
Tutorials

GCP Quick Wins

Google Cloud Platform offers powerful tools and services, but optimizing your GCP environment doesn't have to be overwhelming. This tutorial covers practical GCP optimization strategies that you can implement quickly to achieve immediate cost savings, security improvements, and operational efficiency.

GCP Safe Deletes Tutorial #

In this tutorial, you will GCP resources that are likely safe to delete. This can be a significant cost savings: one CloudQuery customer saves approximately $30k/month in costs by using queries like this.

Get started #

Install CloudQuery, point it at a Postgres you control (local Docker or Cloud SQL for Postgres), and use a read-only GCP identity with compute.disks.list, compute.addresses.list, compute.instances.list, compute.snapshots.list, and storage.buckets.list. Save the config below as gcp-safe-deletes.yaml, set POSTGRES_URL and your GCP credentials, then run a single cloudquery sync. Paste the SQL into Postgres for a screenshot-ready scorecard.

CloudQuery Config and Sync #

This config will sync AWS metadata for EC2 and related resources to Postgres.
kind: source
spec:
  name: aws
  path: cloudquery/source/aws
  version: 'vX.Y.Z' # pin a recent version
  tables:
    - aws_ec2_ebs_volumes
    - aws_ec2_eips
    - aws_ec2_security_groups
    - aws_ec2_network_interfaces
  destinations: [postgres]

---
kind: destination
spec:
  name: postgres
  path: cloudquery/destination/postgresql
  version: 'vA.B.C' # whatever your postgres version is
  spec:
    connection_string: ${POSTGRES_URL} # e.g. postgres://user:pass@host:5432/db
After you create your config file, all you need to do is run a sync. Make sure to set your environment variables!
cloudquery sync gcp-safe-deletes.yaml

Data structure #

gcp_compute_disks
Rows: ~hundreds (depends on project)
Columns:
 • self_link (utf8)        e.g. "https://www.googleapis.com/compute/v1/projects/my-proj/zones/us-east1-b/disks/data-disk-1"
 • name (utf8)             e.g. "data-disk-1"
 • project_id (utf8)       e.g. "my-proj"
 • zone (utf8)             e.g. "us-east1-b"
 • size_gb (int64)         e.g. 100
 • status (utf8)           e.g. "READY"
users (list)      e.g. ["https://.../instances/web-1"]
 • labels (json)           e.g. {"env":"prod","owner":"teamA"}

gcp_compute_addresses
Rows: usually small (<50)
Columns:
 • self_link (utf8)        e.g. "https://.../addresses/ip-1"
 • name (utf8)             e.g. "static-ip-1"
 • project_id (utf8)       e.g. "my-proj"
 • region (utf8)           e.g. "us-central1"
 • address (utf8)          e.g. "35.201.22.44"
 • address_type (utf8)     e.g. "EXTERNAL"
 • status (utf8)           e.g. "RESERVED" or "IN_USE"
users (list)      e.g. [] (empty if unattached)
 • network_tier (utf8)     e.g. "PREMIUM"
 • labels (json)           e.g. {"env":"staging"}

gcp_compute_instances
Rows: active fleet size (tens → thousands)
Columns:
 • self_link (utf8)        e.g. "https://.../instances/web-1"
 • name (utf8)             e.g. "web-1"
 • project_id (utf8)       e.g. "my-proj"
 • zone (utf8)             e.g. "us-central1-a"
 • status (utf8)           e.g. "RUNNING", "TERMINATED"
 • last_stop_timestamp (utf8) e.g. "2024-11-10T08:15:00.000-07:00"
 • labels (json)           e.g. {"role":"frontend"}

gcp_compute_snapshots
Rows: varies (depends on backup policy)
Columns:
 • self_link (utf8)        e.g. "https://.../snapshots/snap-1"
 • name (utf8)             e.g. "snap-1"
 • project_id (utf8)       e.g. "my-proj"
 • creation_timestamp (utf8) e.g. "2024-05-01T12:00:00.000-07:00"
 • disk_size_gb (int64)    e.g. 200
 • auto_created (bool)     e.g. false
 • status (utf8)           e.g. "READY"
 • labels (json)           e.g. {"backup":"weekly"}

gcp_storage_buckets
Rows: modest (dozens per project)
Columns:
 • name (utf8)             e.g. "my-data-bucket"
 • project_id (utf8)       e.g. "my-proj"
 • location (utf8)         e.g. "US"
 • public_access_prevention_string_value (utf8) e.g. "enforced", NULL
 • uniform_bucket_level_access (json) e.g. {"enabled": true}
 • acl (json)              e.g. [{"entity":"allUsers","role":"READER"}]
 • labels (json)           e.g. {"team":"analytics"}

Useful queries #

Unattached Persistent Disks → storage burn with no usage #

SELECT
  d.project_id,
  COALESCE(d.zone, d.region) AS location,
  d.name,
  d.self_link,
  d.size_gb,
  d.status,
  d.labels
FROM gcp_compute_disks d
WHERE d.users IS NULL
   OR (jsonb_typeof(d.users) = 'array' AND jsonb_array_length(d.users) = 0)
ORDER BY d.size_gb DESC NULLS LAST, location, d.name;

Idle Static External IPs → you pay when RESERVED and unused #

SELECT
  a.project_id,
  a.region,
  a.name,
  a.self_link,
  a.address,
  a.address_type,
  a.status,
  a.network_tier,
  a.labels
FROM gcp_compute_addresses a
WHERE a.address_type = 'EXTERNAL'
  AND a.status = 'RESERVED'
  AND (
        a.users IS NULL OR
        (jsonb_typeof(a.users) = 'array' AND jsonb_array_length(a.users) = 0)
      )
ORDER BY a.region, a.name;

Stopped/Terminated VMs → surface likely cleanup/savings #

SELECT
  i.project_id,
  i.zone,
  i.name,
  i.self_link,
  i.status,
  i.last_stop_timestamp,
  i.labels
FROM gcp_compute_instances i
WHERE i.status IN ('TERMINATED', 'STOPPED')
ORDER BY i.zone, i.name;

Stale Snapshots → storage creep #

SELECT
  s.project_id,
  s.name,
  s.self_link,
  s.creation_timestamp,
  s.disk_size_gb,
  s.auto_created,
  s.status,
  s.labels
FROM gcp_compute_snapshots s
WHERE s.auto_created = FALSE
  AND (s.creation_timestamp::timestamp AT TIME ZONE 'UTC') < (NOW() AT TIME ZONE 'UTC') - INTERVAL '90 days'
ORDER BY s.creation_timestamp::timestamp ASC;

Public or weakly protected GCS buckets #

SELECT
  b.project_id,
  b.name,
  b.location,
  b.public_access_prevention_string_value AS pap,
  b.uniform_bucket_level_access,
  b.labels
FROM gcp_storage_buckets b
WHERE COALESCE(b.public_access_prevention_string_value, '') <> 'enforced'
  AND b.acl IS NOT NULL
  AND EXISTS (
        SELECT 1
        FROM jsonb_array_elements(b.acl) a
        WHERE (a->>'entity') IN ('allUsers', 'allAuthenticatedUsers')
      )
ORDER BY b.project_id, b.name;

Wrap-up #

CloudQuery is lightning-fast data movement that runs on your infrastructure. This is perfect for working with sensitive data (like your key GCP resources!). Get started free here to realize these cost savings. We’ve got a 14 day free trial, with no credit card required!

Related posts

Turn cloud chaos into clarity

Find out how CloudQuery can help you get clarity from a chaotic cloud environment with a personalized conversation and demo.


© 2025 CloudQuery, Inc. All rights reserved.