```hcl
provider "google" {
  project = "[PROJECT-ID]"
  region  = "[REGION]"
  zone    = "[ZONE]"
}
```

Auth for the provider, in order of preference:
1. ADC in the environment running terraform (WIF in CI, user ADC locally).
2. Impersonation: `impersonate_service_account = "[SA-EMAIL]"` in the provider block. No key file.
3. `credentials` pointing at a key file. Last resort; the file then lives wherever state runs.

Things that bite:

1. deletion_protection. Several resources (notably Cloud SQL instances) default deletion_protection to true. `terraform destroy` then fails until you set it false and apply. This is a safety feature, not a bug; flip it deliberately.

2. 409 Already exists on apply. Someone (or a previous partial apply) created the resource outside state. Do not change the name to dodge it; import it: `terraform import google_storage_bucket.my_bucket [BUCKET-NAME]` (import ID formats vary per resource, check the provider docs per resource).

3. Provider-level project vs resource-level project. Setting project on the provider is the default; any resource can override with its own project argument. In multi-project setups, be explicit per resource or you will create things in the wrong project.

4. google vs google-beta. Some fields only exist in the beta provider. If a documented field errors as unsupported, you probably need the beta provider for that resource.

5. State backend: use a GCS backend with versioning enabled on the state bucket. Local state in CI is how you get two applies fighting.

Verify: `terraform plan` shows no diff on a second run, and `gcloud` confirms resources landed in the intended project and region.