terraform apply is the command that actually makes changes to your infrastructure. It calculates a fresh execution plan (or uses a saved one), shows you that plan, waits for your approval, then provisions your infrastructure.
terraform applyUnlike terraform plan, which is always safe, apply has real side effects. It calls cloud APIs to create, update, or delete resources. Run plan and review the output carefully before every apply.
When you run terraform apply without a saved plan file:
terraform plan)Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
output blocks definedYou must type exactly yes to proceed. Typing y, Y, or anything else cancels the apply.
As Terraform applies changes, it prints real-time status:
aws_vpc.main: Creating...
aws_vpc.main: Creation complete after 2s [id=vpc-0a1b2c3d4e5f]
aws_subnet.public: Creating...
aws_subnet.public: Creation complete after 1s [id=subnet-0abc123]
aws_internet_gateway.main: Creating...
aws_internet_gateway.main: Creation complete after 1s [id=igw-0xyz789]
aws_security_group.web: Creating...
aws_security_group.web: Creation complete after 3s [id=sg-0def456]
aws_instance.web: Creating...
aws_instance.web: Still creating... [10s elapsed]
aws_instance.web: Still creating... [20s elapsed]
aws_instance.web: Creation complete after 23s [id=i-0abc12345def67890]
Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
Outputs:
instance_public_ip = "54.123.45.67"
vpc_id = "vpc-0a1b2c3d4e5f"
Notice how Terraform creates resources in dependency order, the VPC first, then subnets, then the internet gateway, then the security group, then the instance.
Terraform does not apply changes sequentially. It builds a dependency graph from your configuration and runs independent resources in parallel: up to 10 at a time by default:
aws_s3_bucket.logs: Creating... â no dependencies, starts immediately
aws_s3_bucket.assets: Creating... â no dependencies, starts immediately
aws_vpc.main: Creating... â no dependencies, starts immediately
aws_s3_bucket.logs: Creation complete after 2s
aws_s3_bucket.assets: Creation complete after 2s
aws_vpc.main: Creation complete after 2s
aws_subnet.public: Creating... â depends on VPC, starts after VPC
To change the parallelism limit:
terraform apply -parallelism=5You can also disable parallelism completely (rarely needed):
terraform apply -parallelism=1When you saved a plan with terraform plan -out=tfplan, apply it directly:
terraform apply tfplanWhen applying a saved plan:
This is the recommended production workflow and the standard CI/CD pattern.
In automated pipelines, you cannot have an interactive approval prompt. Skip it with -auto-approve:
terraform apply -auto-approveImportant: Only use
-auto-approvein CI/CD pipelines with proper code review and branch protection. Never use it interactively on production infrastructure, you lose the safety check.
A complete GitLab CI apply stage:
apply:
stage: apply
script:
- terraform init -input=false
- terraform apply -input=false -auto-approve
when: manual # Requires a human to click "Play" in GitLab
environment: productionNote the when: manual, even with -auto-approve, the CI job itself requires a human to trigger it. This gives you both automation and a human gate.
After apply completes, view your declared outputs:
# Show all outputs
terraform output
# Show a specific output
terraform output instance_public_ip
# Get output as JSON (useful in scripts)
terraform output -jsonExample output block in your Terraform config:
output "instance_public_ip" {
description = "Public IP of the web server"
value = aws_instance.web.public_ip
}Just like plan, you can apply to a specific resource only:
terraform apply -target=aws_instance.webWarning: Using
-targetcan leave your state inconsistent. Dependencies of the targeted resource will not be created/updated. Use only for emergencies or debugging.
Every time apply succeeds, Terraform updates the state file (terraform.tfstate) to record what it built. The state file contains:
i-0abc123 for an EC2 instance)This is why apply is idempotent, if you run it again without changing your config, plan will show no changes and apply will do nothing.
If an error occurs mid-apply, Terraform:
You will see something like:
â·
â Error: error creating EC2 Instance: InvalidParameterValue: Invalid value for InstanceType
â with aws_instance.web,
â on main.tf line 10
â
â aws_instance.web: Creation failed after 2s
âµ
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Error: 1 error occurred.
After fixing the error, run terraform plan again to see the remaining changes, then terraform apply again. Terraform won't try to recreate resources that were already successfully created.
<MCQExercise exercise={{ id: 'tf-apply-q1', title: 'Question 1: Apply Confirmation', description: 'You run terraform apply. Terraform shows the execution plan and asks: "Enter a value:". You type "y" and press Enter. What happens?', options: [ { id: 'a', text: 'Terraform accepts "y" as a shorthand for "yes" and proceeds' }, { id: 'b', text: 'Terraform cancels the apply, only the exact word "yes" is accepted' }, { id: 'c', text: 'Terraform asks you to confirm again with a full "yes"' }, { id: 'd', text: 'Terraform applies but with a warning about the invalid input' }, ], correctId: 'b', explanation: 'Terraform requires the exact text "yes" to proceed. Typing "y", "Y", "Yes", or anything else cancels the apply. This is intentional, it prevents accidental applies from muscle memory (e.g., someone used to typing "y" for every prompt). You must type "yes" in full.', wrongExplanations: { a: 'Terraform does not accept shorthands. Only the exact text "yes" works.', c: 'Terraform does not re-prompt. Anything other than "yes" cancels the operation immediately.', d: 'There is no partial apply with warnings. Non-"yes" input cancels cleanly.', }, }} />
<MCQExercise exercise={{ id: 'tf-apply-q2', title: 'Question 2: Parallel Execution', description: 'You have 10 independent S3 buckets to create (no dependencies between them). How does terraform apply handle this by default?', options: [ { id: 'a', text: 'Creates them one at a time in the order they appear in the config file' }, { id: 'b', text: 'Creates them all simultaneously in parallel (up to 10 at a time by default)' }, { id: 'c', text: 'Asks which buckets to create first' }, { id: 'd', text: 'Batches them into groups of 5' }, ], correctId: 'b', explanation: 'Terraform builds a resource dependency graph and executes independent resources in parallel. The default parallelism limit is 10. Since your 10 S3 buckets have no dependencies on each other, Terraform will start creating all 10 simultaneously. Resources that depend on others (e.g., an EC2 instance in a subnet) wait for their dependencies to be created first.', wrongExplanations: { a: 'Terraform does not process resources in file order. It uses a dependency graph for optimal parallel execution.', c: 'Terraform never asks about ordering. It determines the optimal execution order automatically.', d: 'The default parallelism is 10 (not 5) and is not batched, all 10 independent resources start at once.', }, }} />
<MCQExercise exercise={{ id: 'tf-apply-q3', title: 'Question 3: -auto-approve', description: 'When is it safe to use terraform apply -auto-approve?', options: [ { id: 'a', text: 'Always, it just skips a redundant confirmation prompt' }, { id: 'b', text: 'Never, Terraform recommends against it' }, { id: 'c', text: 'In CI/CD pipelines with proper code review and branch protection in place' }, { id: 'd', text: 'Only for dev environments, never for staging or production' }, ], correctId: 'c', explanation: '-auto-approve is appropriate in CI/CD pipelines where the human approval gate is the code review / PR process and pipeline trigger (e.g., when: manual in GitLab). The key is that there IS still a human gate, it is just earlier in the process. What you must never do is use -auto-approve interactively on production infrastructure, where the plan review step is your last safety check before real changes happen.', wrongExplanations: { a: 'The confirmation prompt is a critical safety check, not redundant. Skipping it without proper controls is dangerous.', b: 'Terraform does not recommend against -auto-approve, it is explicitly designed for CI/CD use. The key is to have other safety gates in place.', d: 'The environment (dev vs prod) is less relevant than whether proper review processes are in place. Many teams use -auto-approve in all environments with proper PR workflows.', }, }} />
In CI, yes, but only when applying a saved plan file that a human reviewed: terraform apply tfplan needs no approval flag because the plan is already fixed. Using -auto-approve on a fresh plan means nobody saw what was about to happen, and it recalculates against whatever state exists at that moment. Interactively, type the confirmation. The pause before destroying something is worth the three seconds.
Resources created before the failure are kept and recorded in state. Terraform does not roll back, because destroying successfully created infrastructure is usually worse than leaving it. Fix the cause and run apply again; Terraform reconciles from where it is, creating only what is still missing. The state file is written continuously during apply, so an interrupted run rarely loses track of what was made. If the process was killed hard, a stale lock may need clearing with terraform force-unlock.
It builds a dependency graph from references between resources and walks it, running up to ten operations concurrently by default. Resources that reference each other are serialised; independent ones are not. -parallelism=n adjusts the limit, which is occasionally worth lowering when a provider's API rate limits start returning errors under load, and rarely worth raising.
Declare output blocks and read them with terraform output, or terraform output -json for machine-readable form that scripts can parse with jq. In CI this is how you pass a generated URL, an IP address or a database endpoint to a later job. Outputs marked sensitive = true are hidden in console output but still present in -json and in state, so they are not a security control, only a protection against shoulder-surfing and log leakage.
Check whether Terraform knows about it. If the resource was created but the provider errored afterwards, it is usually in state and a subsequent apply will fix or replace it. If it was created and Terraform lost track, import it with terraform import rather than letting a second apply create a duplicate. If state contains something that no longer exists, terraform state rm removes the entry so Terraform stops trying to manage it. Always run a plan and read it before acting on any of these.
Run terraform destroy safely: read the destroy plan, preview it first, remove single resources with -target, and keep code tidy with fmt and validate.
See how data flows through Terraform: input variables, locals and outputs, where each belongs, how variable precedence works and how to handle secrets safely.
Declare Terraform input variables with types, defaults and validation, then set them via tfvars files, CLI flags and environment variables, including secrets.