```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.