This guide takes you from zero to a running (and self-deleting) VM.

1. Add the dependencies

def deps do
  [
    {:gcp_compute, "~> 0.1.0"},
    {:goth, "~> 1.4"}   # recommended token provider (optional dependency)
  ]
end

2. Authenticate

GcpCompute needs an OAuth2 access token for every request. In production that comes from Goth, which picks up Application Default Credentials (a service-account key via GOOGLE_APPLICATION_CREDENTIALS, the GCE metadata server, or gcloud auth application-default login in dev).

Start a Goth server in your supervision tree:

# lib/my_app/application.ex
children = [
  {Goth, name: MyApp.Goth}
  # ...
]

Auth is pluggable — see GcpCompute.TokenProvider to use workload identity, the metadata server, Vault, etc. instead of Goth.

3. Build a config

A GcpCompute.Config is a cheap, immutable handle you build once and pass to every call. There is no process to superviseReq handles connection pooling internally.

{:ok, config} =
  GcpCompute.Config.production(
    project: "my-project",
    zone: "us-central1-a",
    goth: MyApp.Goth
  )

Or load it from application env with GcpCompute.Config.from_env/2.

4. Spawn a VM and wait for it

insert_instance_and_wait/3 inserts the instance, polls the resulting operation to completion, and returns the live GcpCompute.Instance:

{:ok, instance} =
  GcpCompute.insert_instance_and_wait(config,
    name: "worker-1",
    machine_type: "e2-micro",
    spot: true,                       # cheap, preemptible, deletes on termination
    max_run_duration: 3600,           # hard server-side TTL (seconds)
    startup_script: "#!/bin/bash\necho ready > /tmp/ready",
    labels: %{"owner" => "platform"}
  )

GcpCompute.Instance.external_ip(instance)   #=> "34.x.x.x"

See Configuring machines for every spec/1 option.

5. Drive the lifecycle yourself (optional)

Every mutating call returns an GcpCompute.Operation you can poll explicitly:

{:ok, op}   = GcpCompute.insert_instance(config, name: "worker-1", machine_type: "e2-micro")
{:ok, _done} = GcpCompute.wait_for_operation(config, op, timeout: :timer.minutes(3))
{:ok, vm}   = GcpCompute.get_instance(config, "worker-1")

6. Tear it down

{:ok, _op} = GcpCompute.delete_instance_and_wait(config, "worker-1")

(Spot VMs with max_run_duration also delete themselves server-side — belt and suspenders.)

Error handling

Every function returns {:ok, _} or {:error, %GcpCompute.Error{}}. The error carries a coarse :reason you can match on:

case GcpCompute.insert_instance(config, name: "w", machine_type: "e2-micro") do
  {:ok, op} -> op
  {:error, %GcpCompute.Error{reason: :api_error, status: 403, message: msg}} -> {:denied, msg}
  {:error, %GcpCompute.Error{reason: :operation_failed} = err} -> {:failed, err}
  {:error, %GcpCompute.Error{reason: :timeout}} -> :timed_out
  {:error, %GcpCompute.Error{reason: :transport}} -> :network
end

Observability

Attach a quick logger during development, or wire the spans into Telemetry.Metrics:

GcpCompute.Telemetry.attach_default_logger()

See GcpCompute.Telemetry for the full event list.