Connecting to GCP Resources

Quote

“The interesting thing about cloud computing is that we’ve redefined cloud computing to include everything that we already do.”

Larry Ellison, Oracle analyst conference (2008)

Every GCP resource has different connectivity patterns. This note provides the exact commands for connecting to each resource type you will encounter in data engineering, from both Linux and PowerShell, with the expected output so you can debug when things go wrong.

The connectivity model for GCP resources falls into three categories: resources you SSH into (Compute Engine VMs), resources that require an IAP tunnel before you can connect (SQL Server, Airflow, PostgreSQL on private VMs), and Google-managed services that expose HTTPS API endpoints directly (BigQuery, Cloud Run, Cloud Storage).


flowchart LR
    Dev["Developer<br>Workstation"]

    subgraph IAP["IAP-Protected (Private VMs)"]
        IPAD[" "]
        SSH["GCE VM<br>SSH port 22"]
        SQL["SQL Server<br>TDS port 1433"]
        Airflow["Airflow<br>HTTP port 8080"]
        DD["Datadog Agent<br>localhost only"]
        IPAD ~~~ SSH
        IPAD ~~~ SQL
        IPAD ~~~ Airflow
        IPAD ~~~ DD
    end

    subgraph API["Google-Managed APIs (HTTPS/443)"]
        APAD[" "]
        BQ["BigQuery<br>bigquery.googleapis.com"]
        CR["Cloud Run<br>*.run.app"]
        GCS["Cloud Storage<br>storage.googleapis.com"]
        APAD ~~~ BQ
        APAD ~~~ CR
        APAD ~~~ GCS
    end

    Dev -->|"gcloud compute ssh<br>(IAP automatic)"| SSH
    Dev -->|"gcloud start-iap-tunnel<br>then sqlcmd / SSMS"| SQL
    Dev -->|"gcloud start-iap-tunnel<br>then browser"| Airflow
    SSH -->|"SSH then<br>datadog-agent status"| DD
    style IPAD fill:transparent,stroke:transparent,color:transparent
    style APAD fill:transparent,stroke:transparent,color:transparent
    Dev -->|"bq / Python client<br>(IAM only)"| BQ
    Dev -->|"curl / Invoke-RestMethod<br>(identity token)"| CR
    Dev -->|"gsutil / Python client<br>(IAM only)"| GCS

PowerShell / Linux | Compute Engine | SSH access

gcloud compute ssh wraps standard SSH with automatic IAP tunneling and OS Login key management. It connects you to a Compute Engine VM over port 22 through Google’s Identity-Aware Proxy, meaning the VM itself does not need a public IP address. The first connection may take 10–30 seconds while gcloud propagates your SSH public key to VM metadata.

PowerShell / Linux | gcloud compute ssh | interactive and remote commands

Use gcloud compute ssh for both interactive shells and one-off remote command execution. The --tunnel-through-iap flag routes the SSH connection through IAP, which is required for VMs with no external IP.

Open an interactive SSH session on a GCE VM

gcloud compute ssh data-pipeline-sql --zone=europe-west1-b --tunnel-through-iap

Run a single remote command without opening an interactive shell

The --command flag executes a shell command on the VM and streams its output back to your terminal. This is useful for health checks and quick diagnostics without starting a full session.

gcloud compute ssh data-pipeline-sql --zone=europe-west1-b --tunnel-through-iap \
    --command="free -h && df -h && ss -tlnp"

Common SSH connection errors

  • “Permission denied” — SSH key not propagated yet. Run gcloud compute os-login ssh-keys add or wait for metadata sync.
  • “Connection timed out” — VM is stopped, or the firewall blocks 35.235.240.0/20 on port 22.
  • “Could not fetch resource” — wrong zone, wrong instance name, or the VM has been deleted.

Verify before connecting

Run gcloud compute instances list to confirm the VM name, zone, and status (RUNNING) before attempting SSH. For firewall issues, confirm the allow-ingress rule for 35.235.240.0/20 on port 22 is attached to the VM’s network.

PowerShell / Linux | gcloud compute scp | file transfer

gcloud compute scp transfers files between your local machine and a GCE VM using the same IAP-tunneled SSH channel. The remote path uses the format instance-name:/path/on/vm.

Upload a local file to the VM

gcloud compute scp ./local-file.py data-pipeline-sql:/tmp/ \
    --zone=europe-west1-b --tunnel-through-iap

Download a file from the VM to local

gcloud compute scp data-pipeline-sql:/tmp/output.csv ./local/ \
    --zone=europe-west1-b --tunnel-through-iap

Upload a local file to the VM (PowerShell)

gcloud compute scp .\local-file.py data-pipeline-sql:/tmp/ `
    --zone=europe-west1-b --tunnel-through-iap

Download a file from the VM to local (PowerShell)

gcloud compute scp data-pipeline-sql:/tmp/output.csv .\local\ `
    --zone=europe-west1-b --tunnel-through-iap
FlagSyntaxDescription
--zone--zone=europe-west1-bZone of the target instance (required)
--tunnel-through-iap--tunnel-through-iapRoute SSH through IAP; required for VMs with no public IP
--command--command="<shell-cmd>"Execute a single command on the VM and return output
--ssh-flag--ssh-flag="-L 5432:localhost:5432"Pass arbitrary flags to the underlying SSH client
--project--project=my-project-123Override the active gcloud project
--recurse--recurse(scp only) Recursively copy a directory

PowerShell / Linux | SQL Server on Compute Engine | IAP tunnel

SQL Server runs on a private GCE VM and listens on TDS port 1433. Because the VM has no public IP, access requires a two-step process: open an IAP tunnel that forwards a local port to the VM’s port 1433, then connect through that local port using any SQL Server client.

PowerShell / Linux | gcloud start-iap-tunnel | open tunnel to SQL Server

The gcloud compute start-iap-tunnel command creates a TCP forwarding tunnel through IAP. While the tunnel process runs, any connection to 127.0.0.1:1435 (the local port) is transparently forwarded to port 1433 on the target VM.

Open the IAP tunnel in the background (Linux)

Run the tunnel in a background process so the terminal remains available for the sqlcmd connection step.

gcloud compute start-iap-tunnel data-pipeline-sql 1433 \
    --local-host-port=127.0.0.1:1435 --zone=europe-west1-b &

Open the IAP tunnel in a separate window (PowerShell)

In PowerShell, start the tunnel in a new window to keep it running while you work in the current session.

gcloud compute start-iap-tunnel data-pipeline-sql 1433 `
    --local-host-port=0.0.0.0:1435 `
    --zone=europe-west1-b
Listening on port [1435].

SQL Server uses a comma separator for port in the connection string

sqlcmd -S 127.0.0.1,1435 uses a comma between host and port — this is SQL Server’s convention inherited from the TDS protocol. Using a colon (127.0.0.1:1435) will fail to parse correctly.

Always use comma syntax for SQL Server host:port

Correct form: 127.0.0.1,1435 in sqlcmd, SSMS, and pyodbc connection strings. Only pymssql takes host and port as separate arguments.

PowerShell / Linux | sqlcmd | connect and query through tunnel

sqlcmd is the SQL Server command-line client. After the IAP tunnel is open, connect to the local forwarding port as if SQL Server were running locally.

Open an interactive sqlcmd session (Linux)

sqlcmd -S 127.0.0.1,1435 -U sa -P "$SA_PASSWORD" -d analytics_db

Run a quick one-off query to verify the connection (Linux)

sqlcmd -S 127.0.0.1,1435 -U sa -P "$SA_PASSWORD" -d analytics_db \
    -Q "SELECT @@VERSION" -W
Microsoft SQL Server 2019 (RTM-CU18) 15.0.4261.1 (X64)

Verify SQL Server is listening on the VM before connecting

If the tunnel is open but the connection fails, SSH into the VM and confirm that sqlservr is actively bound to port 1433.

gcloud compute ssh data-pipeline-sql --zone=europe-west1-b --tunnel-through-iap \
    --command="ss -tlnp | grep 1433"
LISTEN 0  128  0.0.0.0:1433  0.0.0.0:*  users:(("sqlservr",pid=1234,fd=12))

Connect and run a query via Invoke-Sqlcmd (PowerShell)

Invoke-Sqlcmd -ServerInstance "127.0.0.1,1435" -Database "analytics_db" `
    -Username "sa" -Password $env:SA_PASSWORD `
    -TrustServerCertificate -Query "SELECT COUNT(*) AS cnt FROM gold.scores_daily"
cnt
---
891
FlagSyntaxDescription
-S-S 127.0.0.1,1435Server and port (comma-separated — SQL Server convention)
-U-U saSQL Server login username
-P-P "$SA_PASSWORD"Password (use env var to avoid shell history exposure)
-d-d analytics_dbDatabase to connect to on login
-Q-Q "SELECT @@VERSION"Execute query, print result, and exit
-W-WRemove trailing spaces from column output
-o-o output.txtRedirect output to a file

PowerShell / Linux | pymssql | Python connection through IAP tunnel

pymssql is a Python library that connects to SQL Server using the TDS protocol. It takes host and port as separate arguments, unlike pyodbc and SQLAlchemy which use the comma syntax in the connection string.

Connect to SQL Server and query through the tunnel (Python)

Extract the inline notes on driver syntax differences so they are explicit for comparison:

  • pymssql: server="127.0.0.1", port="1435" — separate arguments
  • pyodbc: "SERVER=127.0.0.1,1435" — comma syntax in the DSN string
  • SQLAlchemy: "mssql+pyodbc://sa:pass@127.0.0.1,1435/analytics_db" — comma syntax in the URL
import pymssql
import os
 
conn = pymssql.connect(
    server="127.0.0.1",
    port="1435",
    user="sa",
    password=os.environ["SA_PASSWORD"],
    database="analytics_db",
    as_dict=True
)

PowerShell / Linux | BigQuery | direct API access

BigQuery is a serverless, multi-tenant analytics service. There is no server running on a VM — you do not need an IAP tunnel, a port, or a hostname. Every query is sent as an HTTPS request to bigquery.googleapis.com. BigQuery allocates compute resources on demand, executes the query, and returns results. The only access control is IAM: the calling identity must have the bigquery.jobs.create permission (typically granted via the BigQuery User or BigQuery Data Viewer role).

PowerShell / Linux | bq | CLI queries and dataset inspection

The bq CLI is part of the Google Cloud SDK and is available on both Linux and PowerShell. It sends queries directly to the BigQuery API. The --use_legacy_sql=false flag is required for all queries — without it, bq defaults to legacy SQL, which has different syntax and limitations.

Count rows in a BigQuery table (Linux)

Backtick-delimited fully-qualified table names (project.dataset.table) must be escaped as ``` in bash to prevent shell interpretation.

bq query --use_legacy_sql=false \
    "SELECT COUNT(*) AS row_count FROM \`data-platform-prod.data-pipeline.signals_daily\`"
+----------+
| row_count|
+----------+
|   1482930|
+----------+

Export query output as JSON for scripting (Linux)

bq query --use_legacy_sql=false --format=json \
    "SELECT symbol, trade_date FROM \`data-platform-prod.data-pipeline.signals_daily\` LIMIT 3"
[{"symbol":"AAPL","trade_date":"2024-12-31"},{"symbol":"MSFT","trade_date":"2024-12-31"},{"symbol":"GOOGL","trade_date":"2024-12-31"}]

List all datasets in a project (Linux)

bq ls data-platform-prod:

List all tables in a dataset (Linux)

bq ls data-platform-prod:data-pipeline

Debug authentication before running queries (Linux)

The active account must have bigquery.jobs.create on the target project. If queries fail with “Access Denied,” verify which account is active.

gcloud auth list
   Credentialed Accounts
ACTIVE  ACCOUNT
*       user@example.com
 
To set the active account, run:
    $ gcloud config set account `ACCOUNT`

Run a BigQuery query from PowerShell

In PowerShell, backtick is the escape character, so fully-qualified table names require double backticks to produce a literal backtick in the string passed to bq.

bq query --use_legacy_sql=false `
    "SELECT COUNT(*) FROM ``data-platform-prod.data-pipeline.signals_daily``"
FlagSyntaxDescription
--use_legacy_sql--use_legacy_sql=falseUse standard SQL (required; legacy SQL is the default)
--format--format=jsonOutput format: json, csv, pretty, sparse
--max_rows--max_rows=1000Maximum number of rows returned (default: 100)
--location--location=EUBigQuery processing region
--project_id--project_id=my-project-123Override the active project
--nouse_cache--nouse_cacheBypass cached query results

PowerShell / Linux | google-cloud-bigquery | Python client

The google-cloud-bigquery Python library queries BigQuery using Application Default Credentials (ADC). On a developer workstation, ADC is configured by running gcloud auth application-default login. In production, ADC resolves automatically from the Workload Identity or service account attached to the compute resource.

Query BigQuery and load results into a DataFrame (Python)

from google.cloud import bigquery
 
client = bigquery.Client(project="data-platform-prod")
 
df = client.query(
    "SELECT * FROM data-pipeline.signals_daily LIMIT 10"
).to_dataframe()

PowerShell / Linux | Cloud Run | HTTPS endpoint access

Cloud Run services expose an HTTPS endpoint at a *.run.app domain. Calling a Cloud Run service is identical to calling any REST API. Services can be public (accessible without authentication) or private (require an identity token in the Authorization header).

PowerShell / Linux | gcloud + curl | authenticate and call Cloud Run

A Google identity token (produced by gcloud auth print-identity-token) proves who the caller is — it is a short-lived JWT scoped to the caller’s identity. This is distinct from an access token, which proves what a service account or user is permitted to do. Cloud Run uses identity tokens to enforce its IAM invoker policy.

Retrieve the service URL (Linux)

gcloud run services describe data-pipeline-pipeline \
    --region=europe-west1 \
    --format="value(status.url)"
https://data-pipeline-pipeline-abc123-ew.a.run.app

Call an authenticated Cloud Run service (Linux)

curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
    https://data-pipeline-pipeline-abc123-ew.a.run.app/health
{"status":"healthy"}

Call a public Cloud Run service with no authentication (Linux)

curl https://data-pipeline-dashboard-abc123-ew.a.run.app

Retrieve the service URL and call an authenticated endpoint (PowerShell)

$token = gcloud auth print-identity-token
$url = gcloud run services describe data-pipeline-pipeline `
    --region=europe-west1 --format="value(status.url)"
Invoke-RestMethod -Uri "$url/health" -Headers @{Authorization = "Bearer $token"}
FlagSyntaxDescription
--region--region=europe-west1Region where the Cloud Run service is deployed
--format--format="value(status.url)"Extract a specific field from the resource description
--platform--platform=managedTarget managed Cloud Run (default; use gke for Cloud Run on GKE)

PowerShell / Linux | Airflow on Compute Engine | IAP tunnel to port 8080

Airflow’s webserver runs on port 8080 inside the VM and is not exposed publicly. The connection pattern is identical to SQL Server: open an IAP tunnel forwarding a local port to the VM’s port 8080, then open the Airflow UI in a browser pointing at the local tunnel endpoint.

PowerShell / Linux | gcloud start-iap-tunnel | open tunnel to Airflow

Open the IAP tunnel to Airflow (Linux)

gcloud compute start-iap-tunnel data-pipeline-airflow 8080 \
    --local-host-port=127.0.0.1:8080 \
    --zone=europe-west1-b

Once the tunnel is running, open http://localhost:8080 in a browser and log in with your Airflow credentials.

Verify Airflow is healthy via the REST API (Linux)

curl -s http://localhost:8080/api/v1/health | python -m json.tool
{
    "metadatabase": {
        "status": "healthy"
    },
    "scheduler": {
        "status": "healthy"
    }
}

Open the IAP tunnel and launch the browser (PowerShell)

gcloud compute start-iap-tunnel data-pipeline-airflow 8080 `
    --local-host-port=127.0.0.1:8080 `
    --zone=europe-west1-b

After the tunnel is running, open the browser automatically:

Start-Process "http://localhost:8080"

Verify Airflow health via the REST API (PowerShell)

Invoke-RestMethod -Uri "http://localhost:8080/api/v1/health"
FlagSyntaxDescription
--local-host-port--local-host-port=127.0.0.1:8080Local address and port to bind the tunnel on
--zone--zone=europe-west1-bZone of the target instance
--project--project=my-project-123Override active project

PowerShell / Linux | Datadog Agent on Compute Engine | SSH-only access

The Datadog agent binds all its listeners to 127.0.0.1 (localhost), not to any externally reachable interface. There is no way to open an IAP tunnel to these ports from outside the VM — you must SSH into the VM first and run agent commands from within the SSH session.

The agent exposes three ports, all bound to 127.0.0.1:

  • 5000 — agent HTTP API (health, configuration, metadata)
  • 5001 — agent IPC (internal process communication between agent components)
  • 8126 — APM trace agent (receives application traces from instrumented services)

PowerShell / Linux | gcloud compute ssh | Datadog agent diagnostics

Check agent status and active checks

gcloud compute ssh data-pipeline-sql --zone=europe-west1-b --tunnel-through-iap \
    --command="sudo datadog-agent status | head -30"
Agent (running)
...
  Version: 7.50.0
  ...
  Active checks: sqlserver, disk, cpu, memory, network

Test connectivity from the agent to the Datadog intake endpoints

gcloud compute ssh data-pipeline-sql --zone=europe-west1-b --tunnel-through-iap \
    --command="sudo datadog-agent diagnose --include connectivity"

Verify all three agent ports are listening on localhost

A healthy agent shows all three ports (5000, 5001, 8126) bound to 127.0.0.1.

gcloud compute ssh data-pipeline-sql --zone=europe-west1-b --tunnel-through-iap \
    --command="ss -tlnp | grep -E '(5000|5001|8126)'"
LISTEN  0  128  127.0.0.1:5000   0.0.0.0:*  users:(("agent",pid=2345,fd=8))
LISTEN  0  128  127.0.0.1:5001   0.0.0.0:*  users:(("agent",pid=2345,fd=9))
LISTEN  0  128  127.0.0.1:8126   0.0.0.0:*  users:(("trace-agent",pid=2346,fd=5))

Pattern for localhost-only services

Any service bound to 127.0.0.1 on a VM (Datadog, local databases, internal APIs) can only be reached by SSHing into the VM. IAP tunnels route traffic to the VM’s network interface, not to localhost — so you cannot tunnel directly into a localhost-only listener.

Connection quick reference matrix

The table below summarizes the connectivity model for every GCP resource type covered in this note. Use it as a first reference when diagnosing connection failures.

Connection requirements by resource type

ResourceProtocolNeeds Tunnel?Local CommandPort
GCE VM (SSH)SSHIAP (automatic)gcloud compute ssh22
SQL Server on GCETDSIAP tunnelgcloud start-iap-tunnel → SSMS/sqlcmd1433
PostgreSQL on GCEPostgreSQLIAP tunnelgcloud start-iap-tunnel → psql5432
Airflow on GCEHTTPIAP tunnelgcloud start-iap-tunnel → browser8080
BigQueryHTTPS/APINobq query / Python client443
Cloud RunHTTPSNocurl / browser443
Cloud StorageHTTPS/APINogsutil / Python client443
Datadog AgentHTTPSSH into VMdatadog-agent status (on VM)5000/8126
Docker on GCEUnix socketSSH into VMdocker ps (on VM)N/A

The pattern

Anything running on a VM with no public IP requires an IAP tunnel (or SSH). Anything that is a Google-managed service (BigQuery, Cloud Run, GCS) uses HTTPS APIs directly — no tunnel, no port management, just IAM.

Warnings

gsutil rsync -d permanently deletes destination-only objects

The -d flag removes objects in the destination that do not exist in the source. There is no GCS trash or recycle bin. Always preview with gsutil rsync -n (dry-run) first.

Default service accounts often have overly broad permissions

The Compute Engine default service account has the Editor role. Follow least-privilege: create a custom service account with only the IAM roles your workload needs.

gcloud compute ssh requires the correct zone

Without --zone, the command uses the default zone from gcloud config. If the VM is in a different zone, the command fails or connects to the wrong instance. Always specify --zone explicitly.

Recommendations

ScenarioRecommendation
SSH to a VMgcloud compute ssh <vm> --zone=<zone> --tunnel-through-iap for private VMs.
Copy files to VMgcloud compute scp --zone=<zone> local_file <vm>:~/remote_path.
Upload to GCSgcloud storage cp local_file gs://bucket/path/. Use -m for parallel multi-file upload.
Sync directory to GCSgsutil rsync -r -n local_dir/ gs://bucket/path/ (dry-run first), then remove -n.
Get bearer token for APIsgcloud auth print-access-token for one-off requests. Use client libraries for production.
Authenticate in CI/CDgcloud auth activate-service-account --key-file=key.json. Prefer Workload Identity where available.

Troubleshooting

SymptomLikely causeFix
SSH fails with “Could not fetch resource”Wrong VM name or zone.gcloud compute instances list to verify name and zone.
”Permission denied” on GCS operationService account lacks Storage Object Viewer/Creator role.Grant the required role: gcloud projects add-iam-policy-binding.
gsutil rsync is very slowSingle-threaded by default for large files.Use gsutil -m rsync for parallel operations, or switch to gcloud storage which parallelizes by default.
gcloud auth print-access-token returns expired tokenToken TTL is 1 hour.Re-run the command to get a fresh token. For long-running scripts, use client libraries that auto-refresh.

Cross-references