---
cuppy_hub: gcp
cuppy_path: guides/iac/terraform-first-apply.md
cuppy_section: docs
cuppy_doc: T9SJ-G1Q2
cuppy_doc_uuid: e4f25f74-c485-4d09-9117-ffb431eb2f1e
cuppy_permalink: https://cuppy.io/gcp/d/T9SJ-G1Q2
cuppy_version: 1
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/gcp/docs/guides/iac/terraform-first-apply
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 896b67d4362e59b14cb24c9d28e3bb05ff710930d57517323691fd7620bc8358
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# A first Terraform apply, in order

## Step 1 — enable the services first

```
gcloud services list
gcloud services enable SERVICE
```

Several at once is supported: `gcloud services enable bigquery.googleapis.com pubsub.googleapis.com`. You need
Service Usage Admin (`roles/serviceusage.serviceUsageAdmin`).

*Prevents:* the commonest first-apply failure, which looks like a resource problem and is a project problem
(`reference/iac/errors.md` §1). Put the enablements **in the configuration**, so project number twelve does not
need a runbook.

## Step 2 — create the state bucket, and turn versioning on

Create the bucket **before** the backend block exists. Then:

- **enable object versioning** — the vendor's own tutorial does, so that "earlier versions of the state are
  preserved in the bucket";
- **lock it down**: IAM only, and public access prevention
  (`reference/storage/cloud-storage-access-control.md`).

*Prevents:* the one unrecoverable failure in a Terraform estate. A truncated state write on an unversioned
bucket has nothing to restore from.

## Step 3 — wire the backend, then migrate

```hcl
terraform {
  backend "gcs" {
    bucket = "BUCKET_NAME"
  }
}
```

```
terraform init -migrate-state
```

*Prevents:* losing the local state you already have. `init` without `-migrate-state` leaves it behind.

## Step 4 — authenticate without a key

Pick one, in this order of preference:

1. **the attached identity** — the runner is a Google Cloud resource (Cloud Build, a GCE instance, GKE with
   Workload Identity Federation), so it already has one;
2. **workload identity federation** — the runner is outside Google Cloud
   (`guides/identity/github-actions-without-keys.md`);
3. **impersonation** from your own credentials, for local work.

*Prevents:* a key in a repository, a CI secret store, or a developer's home directory. Keys are "a security
risk if not managed correctly", and the vendor names all three alternatives above.

## Step 5 — pin the provider

**The hub's rule, not the vendor's** — the Terraform-on-Google-Cloud page makes no version recommendation.
Pin an exact provider version in `required_providers`.

*Prevents:* a plan that differs between two runs of the same configuration, which is the hardest IaC bug to
reason about because nothing in your repository changed.

## Step 6 — plan, read the plan, then apply

Read every **destroy** and **replace** line before applying. On Google Cloud specifically, watch for:

- a **zonal disk** being replaced: it cannot move zone, so a zone change is a destroy-and-create of the data
  (`reference/overview/regions-zones-and-locations.md` §3);
- anything in **Cloud KMS**: key rings cannot be deleted, deleted key names cannot be reused, and protection
  level, purpose and type are immutable, so a "change" is a new key
  (`reference/identity/secret-manager-and-kms.md` §2);
- a **project** being destroyed. Nothing in this hub's register justifies that in a first apply.

If Infra Manager is doing the applying, step 6 is its **preview** object, and steps 2 and 3 are unnecessary
because it keeps the state itself (`reference/iac/terraform-on-google-cloud.md` §3).

## Before you write a module

Check **Blueprints** — Google publishes "deployable, reusable Terraform modules" — for the thing you are
about to model. A landing zone, a network or a GKE cluster is almost certainly already there, and reading one
is also the fastest way to learn the idioms this guide assumes.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 91 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/gcp/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
