HCP Terraform の workspace と run: plan から apply までの流れと state の置き場所

HCP Terraform に .tf を渡して plan と apply を実行させると、state は HCP Terraform の workspace に置かれ、apply の前には人が承認する機会ができる。まずはその一連の動きを、Git との連携を入れずに、ローカルの開発環境の terraform コマンドから動かして確かめる。
この記事では、ローカルの terraform コマンドから HCP Terraform の workspace を作り、plan と apply を run として実行する。run がどこで動いたか、承認待ちでどう止まるか、state がどこに残ったかを、ターミナルの出力と画面の両方で見る。
contents
- CLI-driven run workflow
- terraform login で API token を保存する
- .tf を書く
- terraform init で workspace ができる
- terraform plan は plan だけの run になる
- terraform apply は plan のあと承認を待つ
- state は workspace の中にある
- 画面から run を始める
- 全体像
- 参考 URL
CLI-driven run workflow
HCP Terraform で run を始める形は 2 つある。
- VCS-driven run workflow:
.tfを GitHub などのリポジトリに置く。PR やマージをきっかけに、HCP Terraform が run を自動で始める - CLI-driven run workflow:
.tfをローカルの開発環境のディレクトリに置く。ローカルのterraform planとterraform applyで run を始める
この記事は CLI-driven で進める。Git との連携を入れる前に、run と state と画面の 3 つだけを見るため。リポジトリを挟むと、run が始まらないときに HCP Terraform の動きと Git 連携の設定のどちらが原因かを切り分ける必要が出てくる。
CLI-driven は、VCS-driven で運用している workspace でも、PR を出す前にローカルから plan だけを実行して結果を見る用途で使われる。
登場するもの
| 登場するもの | 何か | どこにあるか |
|---|---|---|
| organization | HCP Terraform のいちばん外側の箱。この中に workspace を作る | HCP Terraform |
| API token | ローカルの terraform コマンドが自分のユーザーとして HCP Terraform に接続するための文字列。パスワードの代わり | HCP Terraform が発行し、ローカルの ~/.terraform.d/credentials.tfrc.json に保存される |
cloud ブロック | この .tf の run をどの organization のどの workspace で行うかを書く設定 | ローカルの main.tf |
| workspace | 1 つの .tf の集まりに対応する作業場所。state、変数、run の記録を持つ | organization の中 |
| configuration version | terraform plan と terraform apply のたびに、ローカルの .tf 一式を HCP Terraform に送った写し。run はこれを使う | workspace の中 |
| run | 1 回の plan と apply。ログと結果が記録として残る | workspace の中 |
| state | .tf の定義と作ったものの対応を記録したデータ。過去のバージョンも残る | workspace の中。ローカルに terraform.tfstate はできない |
| run の実行環境 | run のたびに用意される使い捨ての Linux の仮想マシン。terraform はここで動く | HashiCorp のクラウド |
terraform login で API token を保存する
ローカルの terraform コマンドが HCP Terraform に .tf を送って run を始めるとき、HCP Terraform は誰からの指示かを確かめる。そのための API token を取得し、ローカルに保存する。
API token は HCP Terraform が発行する文字列で、これを持っている terraform コマンドは、ログインしているユーザーと同じ権限で扱われる。有効期限を付けられ、画面からいつでも無効化できる。
terraform login の流れは次のとおり。
terraform loginを実行すると、ブラウザで HCP Terraform の Tokens の画面(自分のアカウント設定の中)が開く- 画面の Create an API token で token を作る。Description は用途のメモ、有効期限は学習の間だけ使えればよいので 30 日程度
- 表示された文字列をターミナルに貼り付ける。入力は画面には表示されない
$ terraform login
Terraform will request an API token for app.terraform.io using your browser.
If login is successful, Terraform will store the token in plain text in
the following file for use by subsequent commands:
/Users/rito/.terraform.d/credentials.tfrc.json
Do you want to proceed?
Only 'yes' will be accepted to confirm.
Enter a value: yes
---------------------------------------------------------------------------------
Terraform must now open a web browser to the tokens page for app.terraform.io.
If a browser does not open this automatically, open the following URL to proceed:
https://app.terraform.io/app/settings/tokens?source=terraform-login
---------------------------------------------------------------------------------
Generate a token using your browser, and copy-paste it into this prompt.
Terraform will store the token in plain text in the following file
for use by subsequent commands:
/Users/rito/.terraform.d/credentials.tfrc.json
Token for app.terraform.io:
Enter a value:
Retrieved token for user rito328
Retrieved token for user の行が、貼り付けた token で HCP Terraform に自分のユーザー名を問い合わせ、通ったことを示す。このあとに HCP Terraform からのメッセージ(Welcome to HCP Terraform!)が続く。
token は ~/.terraform.d/credentials.tfrc.json に平文で保存され、以降の terraform コマンドはこのファイルの token を使う。
{
"credentials": {
"app.terraform.io": {
"token": "(token の文字列)"
}
}
}
token を無効にしたいときは、画面の Tokens で削除する。ファイルを削除しただけでは、token そのものは HCP Terraform の中で有効なまま。
.tf を書く
.tf は main.tf の 1 ファイルだけ。
terraform {
cloud {
organization = "ritolabo"
workspaces {
name = "tfc-study-stg"
}
}
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.9"
}
}
}
resource "random_pet" "name" {
length = 2
}
output "pet_name" {
value = random_pet.name.id
}
cloud ブロック
terraform ブロックの中の cloud ブロックが、HCP Terraform を使うための設定のすべて。書くのは 2 つ。
organization: 自分の organization の名前(この記事ではritolabo)workspacesのname: workspace の名前。この名前の workspace がまだなければ、terraform initが作る
接続先のホスト名は書いていない。書かなければ app.terraform.io になる。state の置き場所を決める backend ブロックも書いていない。cloud ブロックがあるとき、state はその workspace に置かれる。
random_pet
random_pet は、hashicorp/random という provider が持つリソース。
- provider は Terraform の中だけで動き、外部のサービスとは通信しない
applyすると形容詞-動物の形のランダムな名前を 1 つ作り、state に保存する- 定義を変えない限り、何度
applyしても同じ名前のまま
この記事で random_pet を使うのは、クラウドの認証情報なしで plan に差分が出て、AWS に何も作らないため。output "pet_name" は、作られた名前をターミナルと画面に出すためのもの。
terraform init で workspace ができる
main.tf のあるディレクトリで terraform init を実行する。
$ terraform init
Initializing HCP Terraform...
Initializing provider plugins...
- Finding hashicorp/random versions matching "~> 3.9"...
- Installing hashicorp/random v3.9.1...
- Installed hashicorp/random v3.9.1 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
HCP Terraform has been successfully initialized!
You may now begin working with HCP Terraform. Try running "terraform plan" to
see any changes that are required for your infrastructure.
If you ever set or change modules or Terraform Settings, run "terraform init"
again to reinitialize your working directory.
Initializing HCP Terraform... の行で、cloud ブロックを読んで HCP Terraform に接続している。このとき起きたことは 4 つ。
sequenceDiagram
participant tf as terraform コマンド<br>(ローカル)
participant hcp as HCP Terraform
participant reg as Terraform Registry
tf->>hcp: token を添えて、organization ritolabo に<br>workspace tfc-study-stg があるか問い合わせる
hcp-->>tf: ない
tf->>hcp: workspace tfc-study-stg を作る(Default Project)
tf->>hcp: workspace の Terraform のバージョンを 1.13.3 に設定する
tf->>reg: hashicorp/random の ~> 3.9 を要求する
reg-->>tf: v3.9.1
Note over tf: .terraform/providers/ に置き、<br>.terraform.lock.hcl にバージョンを書く
credentials.tfrc.jsonの token で、organizationritolaboにtfc-study-stgという workspace があるかを問い合わせる- ないので、
terraformコマンドが HCP Terraform に workspace を作らせる。入る project は、organization に最初からある Default Project - 作った workspace の Terraform のバージョンを、ローカルと同じ 1.13.3 に設定する
hashicorp/randomの provider をローカルの.terraform/providers/にダウンロードし、選んだバージョンを.terraform.lock.hclに書く。これは HCP Terraform を使わないときのinitと同じ
出力に「workspace を作った」という行はないが、HCP Terraform の画面(ブラウザで https://app.terraform.io を開き、organization を選んだ状態)の Workspaces の一覧に tfc-study-stg ができている。開くと次の状態になっている。
- Resources: 0
- Terraform のバージョン: v1.13.3
- Execution mode: Remote(run を HCP Terraform の仮想マシンで行う設定)
- Project: Default Project
- まだ run がないので、CLI-driven の手順の案内が表示されている

terraform plan は plan だけの run になる
terraform plan を実行すると、計算は HCP Terraform の実行環境で行われ、結果だけがターミナルに流れてくる。
$ terraform plan
Running plan in HCP Terraform. Output will stream here. Pressing Ctrl-C
will stop streaming the logs, but will not stop the plan running remotely.
Preparing the remote plan...
To view this run in a browser, visit:
https://app.terraform.io/app/ritolabo/tfc-study-stg/runs/run-sNzHc2koLBeVaCoj
Waiting for the plan to start...
Terraform v1.13.3
on linux_amd64
Initializing plugins and modules...
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# random_pet.name will be created
+ resource "random_pet" "name" {
+ id = (known after apply)
+ length = 2
+ separator = "-"
}
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ pet_name = (known after apply)
このとき起きたことは 3 つ。
sequenceDiagram
participant tf as terraform コマンド<br>(ローカル)
participant hcp as HCP Terraform
participant vm as run の実行環境<br>(Linux の仮想マシン)
tf->>hcp: ディレクトリの中身を圧縮して送る<br>(configuration version)
hcp->>hcp: run を作る
hcp->>vm: 仮想マシンを用意し、configuration version を渡す
vm->>vm: terraform plan を実行する
vm-->>hcp: ログと結果
hcp-->>tf: ログを流す
- ローカルのディレクトリの中身(
.terraform/などは除く)を圧縮して HCP Terraform に送る。これが workspace の configuration version になる - HCP Terraform が run を 1 つ作り、使い捨ての Linux の仮想マシンで、その configuration version に対して
terraform planを実行する - そのログがターミナルに流れてくる。
To view this run in a browserの URL が、この run の画面のページ
Terraform v1.13.3 の次の行の on linux_amd64 が、ローカル(macOS、arm64)ではなく Linux の仮想マシンで動いたことを示している。ローカルで terraform version を実行すると on darwin_arm64 と出る。
terraform plan で始まる run は、公式ドキュメントで speculative plan(plan-only run)と呼ばれる。この run は apply に進めず、state も書かない。画面の Runs タブには、plan-only run の表示が付いた run が 1 つ、状態 Planned and finished で並ぶ。
terraform apply は plan のあと承認を待つ
terraform apply を実行すると、1 つの run が plan、承認待ち、apply と進む。
sequenceDiagram
participant tf as terraform コマンド<br>(ローカル)
participant hcp as HCP Terraform
participant vm as run の実行環境<br>(Linux の仮想マシン)
tf->>hcp: ディレクトリの中身を圧縮して送る<br>(configuration version)
hcp->>hcp: run を作る
hcp->>vm: terraform plan を実行させる
vm-->>tf: plan のログ
Note over hcp: 承認待ちで止まる<br>(workspace を lock)
tf->>hcp: yes(または画面の Confirm & Apply)
hcp->>vm: terraform apply を実行させる
vm->>hcp: state を書く
vm-->>tf: apply のログ
Note over hcp: lock が外れる
$ terraform apply
Running apply in HCP Terraform. Output will stream here. Pressing Ctrl-C
will cancel the remote apply if it's still pending. If the apply started it
will stop streaming the logs, but will not stop the apply running remotely.
Preparing the remote apply...
To view this run in a browser, visit:
https://app.terraform.io/app/ritolabo/tfc-study-stg/runs/run-mAUoRxa52UtLLZju
Waiting for the plan to start...
Terraform v1.13.3
on linux_amd64
Initializing plugins and modules...
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# random_pet.name will be created
+ resource "random_pet" "name" {
+ id = (known after apply)
+ length = 2
+ separator = "-"
}
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ pet_name = (known after apply)
Do you want to perform these actions in workspace "tfc-study-stg"?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value:
承認待ちで止まっている run
Enter a value: で止まっている間に、ターミナルに出た URL を開くと、run のページは次の状態になっている。
- run の状態は Planned。本文に Pending confirmation と Apply pending が出て、下に Confirm & Apply と Discard run のボタンがある
- workspace の上部に鍵のマークと Running が出て、右上のボタンは Lock から Force unlock に変わっている

鍵のマークは、この run が workspace を lock していることを表す。run が plan を終えて apply を待っている間、同じ workspace のほかの run は次のようになる。
- ほかの人が
terraform applyを実行すると、その run は Pending のまま並んで待つ。この run が apply されるか破棄されて lock が外れると動き出す terraform plan(plan-only run)は lock の影響を受けず、承認待ちの間でも実行できる
承認待ちの run を放置すると、その間の apply がすべて Pending で詰まるので、承認するか破棄するかを早めに決める。破棄しても .tf の変更がなくなるわけではなく、破棄した run の plan が適用されないだけになる。次の run は、自分の configuration version と現在の state から plan を計算し直す。
止まっている場所は HCP Terraform の中の run で、ローカルのターミナルはその run の状態を見ながら yes の入力を待っている。承認の方法は 2 つある。
- ターミナルで
yesと入力する - 画面で Confirm & Apply を押す。この場合もターミナルには apply のログがそのまま流れる
yes で apply に進む
ターミナルに戻り、yes と入力する。
Enter a value: yes
random_pet.name: Creating...
random_pet.name: Creation complete after 0s [id=national-starfish]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
pet_name = "national-starfish"
random_pet が national-starfish という名前を作り、state に保存された。この run は、画面の Runs タブで次の順に状態を変えている(状態の名前は公式ドキュメントの Run states and stages から)。
| 順 | 状態 | 何をしていたか |
|---|---|---|
| 1 | Pending | 順番待ち。この workspace にほかの run がないので、すぐ次へ進む |
| 2 | Planning | 仮想マシンで terraform plan を実行中 |
| 3 | Needs confirmation | plan が終わり、人の承認待ち。画面の表記は Planned |
| 4 | Applying | 承認されて、仮想マシンで terraform apply を実行中 |
| 5 | Applied | 終了。state が書かれ、workspace の lock が外れる |
terraform plan の run は 2 で終わり(Planned and finished)、3 以降には進まない。
state は workspace の中にある
apply の結果は、ローカルではなく workspace の中に残る。
HCP Terraform の画面の States タブに state が 1 つある。開くと次のものが見える。
- どの run で作られたか
- state の中身。
random_petのidにnational-starfishが入っている
workspace の Resources は 1 になっている。次に apply すると 2 つ目の state が作られ、1 つ目も残る。

ローカルのディレクトリには terraform.tfstate がない。HCP Terraform を使わないときなら、apply のあとにここにできていたもの。
$ ls -a
. .. .terraform .terraform.lock.hcl main.tf
.terraform/ の中に terraform.tfstate という名前のファイルはあるが、リソースの state ではない。init が「このディレクトリは cloud ブロックでこの organization のこの workspace に接続している」と記録したもので、中身は "backend": { "type": "cloud", ... } の形。random_pet は出てこない。
ローカルの terraform コマンドは、state を読む必要があるときは、token を使って workspace から取得する。
$ terraform state list
random_pet.name
画面から run を始める
run を始める入口は、ローカルの terraform コマンドだけではなく、画面からも始められる。
- workspace のページ右上の + New run を押す
- run の種類で Plan and apply (standard) を選び、Start run を押す
- run が Pending、Planning と進む
plan の結果は No changes になり、run は apply に進まず Planned and finished で終わる。apply の段には Apply will not run と表示される。

変更なしになる理由は、画面から始めた run が使う .tf にある。この run は、terraform apply のときにローカルから送った configuration version をそのまま使い、state もそれに合っている。ローカルの main.tf を書き換えただけでは、HCP Terraform はそれを知らない。ローカルの変更を HCP Terraform に届けるのは、terraform plan と terraform apply が送る configuration version だけになる(VCS-driven では、リポジトリへの push がこれを置き換える)。
Runs タブの一覧には、ここまでの 3 つの run が並び、終わり方を見分けられる。
- plan-only run: Planned and finished
terraform applyの run: Applied- 画面から始めた run: Planned and finished

全体像
ここまでに動いたものをまとめる。random_pet は外部に何も作らないので、作られたものは state の中にしかない。
flowchart LR
me[自分]
tfcmd[["terraform コマンド"]]
tf["main.tf"]
subgraph hcp[HCP Terraform]
subgraph ws["workspace: tfc-study-stg"]
cv["configuration version<br>(送った .tf の写し)"]
run[run の記録]
state[state]
end
vm[["run の実行環境<br>(terraform v1.13.3、Linux)"]]
end
me -- "plan / apply を実行" --> tfcmd
me -- "run を見る・承認する・New run" --> run
tfcmd -- ".tf 一式を送る" --> cv
tfcmd -- "ログを受け取る" --> vm
cv -- "読む" --> vm
vm -- "読む・書く" --> state
ローカルの terraform コマンドがしているのは、.tf を送ることと、ログを受け取って表示することだけ。plan と apply の計算は実行環境で行われ、state は workspace の中だけにある。
まとめ
- ローカルの
terraformコマンドから HCP Terraform を使うには、terraform loginで API token を~/.terraform.d/credentials.tfrc.jsonに保存し、.tfのterraformブロックにcloudブロック(organization と workspace の名前)を書く terraform initは、cloudブロックの名前の workspace がなければ作り、Terraform のバージョンをローカルと同じにするterraform planとterraform applyは、ローカルの.tf一式を configuration version として送り、HCP Terraform の使い捨ての Linux の仮想マシンで実行される。ターミナルにはそのログが流れるterraform planの run は plan-only run で、apply に進まず、state を書かず、workspace を lock しないterraform applyの run は plan のあと承認待ちで止まり、その間 workspace を lock する。承認はターミナルのyesでも画面の Confirm & Apply でもよい- state は workspace の中に置かれ、ローカルに
terraform.tfstateはできない - 画面から始めた run は、最後に送った configuration version を使う
参考 URL
- [official] The CLI-driven remote run workflow for HCP Terraform | Terraform | HashiCorp Developer
- [official] Remote operations in HCP Terraform | Terraform | HashiCorp Developer
- [official] Run states and stages in HCP Terraform | Terraform | HashiCorp Developer
- [official] Manage and view runs in HCP Terraform | Terraform | HashiCorp Developer
- [official] HCP Terraform's run environment | Terraform | HashiCorp Developer
- [official] UI and VCS-driven run workflow in HCP Terraform | Terraform | HashiCorp Developer
- [official] Manage workspace state in HCP Terraform | Terraform | HashiCorp Developer
- [official] Manage API tokens for HCP Terraform | Terraform | HashiCorp Developer
- [official] terraform login command reference | Terraform | HashiCorp Developer
- [official] Connect to HCP Terraform | Terraform | HashiCorp Developer
- [official] random_pet | Resources | hashicorp/random | Terraform Registry










