terraform apply Command with Examples

Tested on Ubuntu 26.04 LTS (Resolute Raccoon)
Package terraform 1.15.8-1
kreuzwerker/docker 3.9.0
Applies to Any host with Terraform installed
Lab environment Single Ubuntu VM with Terraform and Docker — Terraform lab environment on Ubuntu
Privilege Normal user (Docker socket access required)
Scope terraform apply workflow — interactive approval, automatic plan mode, saved plan files, -auto-approve, -var and -var-file, in-place updates, -replace, -parallelism, partial failure behavior, resource verification with Docker, and comparisons with plan and destroy. Does not cover full plan flag reference, destroy tutorials, CI/CD pipelines, variable precedence, or provider authentication setup.
Related guides terraform plan command
terraform init command
terraform validate command
Terraform lab environment on Ubuntu
Terraform Associate certification course

terraform apply is where configuration becomes real infrastructure. After you declare resources in .tf files and initialize the working directory, apply executes the create, update, and destroy operations Terraform planned against your providers.

This walkthrough uses the Docker provider on Ubuntu so you can confirm every change with docker ps and docker inspect, not only Terraform’s success messages. Work in ~/terraform-labs/terraform-apply/ throughout.

NOTE
Use the Terraform lab environment on Ubuntu with Docker Engine running. Run terraform init before your first apply in a new directory. For execution plan symbols and terraform plan flags, see the terraform plan command guide.

How terraform apply works

terraform apply executes the operations needed to make managed infrastructure match the selected Terraform plan.

  • It reads configuration and state
  • It calls provider APIs
  • It writes results back to state
  • Apply does not roll back successful operations automatically when a later step fails

Automatic plan mode

The default workflow is a single command:

text
terraform apply
  → build plan from configuration + state
  → show proposed actions
  → wait for approval (unless skipped)
  → execute operations

This is the mode you use for day-to-day development when you want Terraform to plan from the current directory and pause for confirmation.

Saved plan mode

Reviewed and automated workflows often split planning from execution:

text
terraform plan -out=tfplan
terraform apply tfplan
  → execute the saved plan only (no replan, no approval prompt)

Saved plan mode fits handoffs between teams, pipeline gates, and any case where you must apply exactly what was reviewed earlier.


Run and verify terraform apply

The sections below walk through one Docker container lab from first create through independent verification and a no-change re-apply.

Prepare the Docker lab

Create the working directory:

bash
mkdir -p ~/terraform-labs/terraform-apply && cd ~/terraform-labs/terraform-apply

Write the root module. This lab uses the ghcr.io/nginx/nginx-unprivileged:alpine image and container name tf-apply-web on host port 8082:

bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"

  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 3.0"
    }
  }
}

provider "docker" {}

variable "environment" {
  type    = string
  default = "dev"
}

resource "docker_image" "nginx" {
  name = "ghcr.io/nginx/nginx-unprivileged:alpine"
}

resource "docker_container" "web" {
  name    = "tf-apply-web"
  image   = docker_image.nginx.image_id
  restart = "unless-stopped"

  labels {
    label = "environment"
    value = var.environment
  }

  ports {
    internal = 8080
    external = 8082
  }
}
EOF

Initialize providers:

bash
terraform init -input=false
output
Terraform has been successfully initialized!

Review and approve the plan

Run apply without -auto-approve so Terraform shows the execution plan and waits for your decision:

bash
terraform apply

Read the proposed + create actions, then at the prompt type yes and press Enter. Terraform accepts only the literal word yes:

output
Plan: 2 to add, 0 to change, 0 to destroy.

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
docker_image.nginx: Creating...
docker_image.nginx: Creation complete after 15s
docker_container.web: Creating...
docker_container.web: Creation complete after 1s

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

First image pull can take longer than later runs depending on cache and network. Any answer other than yes cancels the apply without changes.

Verify the container outside Terraform

Terraform’s Apply complete! line confirms provider API calls succeeded. Confirm the container exists on the host:

bash
docker ps --filter name=tf-apply-web
output
CONTAINER ID   IMAGE          COMMAND                  CREATED        STATUS          PORTS                    NAMES
444c18abcf88   334d92979f15   "/docker-entrypoint.…"   1 second ago   Up 1 second     0.0.0.0:8082->8080/tcp   tf-apply-web

Read the label Terraform set from var.environment:

bash
docker inspect tf-apply-web --format '{{index .Config.Labels "environment"}}'
output
dev

List managed resource addresses in state:

bash
terraform state list
output
docker_container.web
docker_image.nginx

State maps configuration addresses like docker_container.web to real container and image IDs. When verification disagrees with state, run terraform plan before you apply again.

What happens when nothing changed

Run apply again without editing main.tf:

bash
terraform apply

Terraform refreshes state, compares configuration to live infrastructure, and reports that nothing needs to change — there is no approval prompt because the plan is empty:

output
No changes. Your infrastructure matches the configuration.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

That idempotent result is normal — apply converges infrastructure toward configuration, not toward “always do something.”


Apply plans and common options

The subsections below reuse the same working directory. Later examples overwrite main.tf with heredocs where the configuration must change.

Apply a saved plan

Saved plans separate planning from execution. Change restart to no in main.tf:

bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"

  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 3.0"
    }
  }
}

provider "docker" {}

variable "environment" {
  type    = string
  default = "dev"
}

resource "docker_image" "nginx" {
  name = "ghcr.io/nginx/nginx-unprivileged:alpine"
}

resource "docker_container" "web" {
  name    = "tf-apply-web"
  image   = docker_image.nginx.image_id
  restart = "no"

  labels {
    label = "environment"
    value = var.environment
  }

  ports {
    internal = 8080
    external = 8082
  }
}
EOF

Write the plan to a file instead of applying immediately:

bash
terraform plan -out=tfplan
output
# docker_container.web will be updated in-place
  ~ resource "docker_container" "web" {
      ~ restart = "unless-stopped" -> "no"
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Saved the plan to: tfplan

Review the artifact, then apply exactly that plan. terraform apply tfplan does not replan and does not ask for approval:

bash
terraform apply tfplan
output
docker_container.web: Modifying...
docker_container.web: Modifications complete after 0s

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

Treat saved plan files as potentially sensitive artifacts. They contain the full planned changes and can store sensitive values in cleartext.

Skip approval with -auto-approve

-auto-approve skips the interactive prompt and applies the generated plan immediately.

Change restart back to unless-stopped in main.tf, then run:

bash
terraform apply -auto-approve
output
# docker_container.web will be updated in-place
  ~ resource "docker_container" "web" {
      ~ restart = "no" -> "unless-stopped"
    }

Plan: 0 to add, 1 to change, 0 to destroy.
docker_container.web: Modifying...
docker_container.web: Modifications complete after 0s

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

Use -auto-approve in disposable labs and controlled automation. Prefer interactive apply or a saved plan workflow for production changes you have not reviewed.

Pass variables with -var and -var-file

Pass values at apply time without editing main.tf.

Override environment for one run:

bash
terraform apply -var='environment=staging' -auto-approve
output
Plan: 1 to add, 0 to change, 1 to destroy.
docker_container.web: Destroying...
docker_container.web: Destruction complete after 0s
docker_container.web: Creating...
docker_container.web: Creation complete after 1s

Apply complete! Resources: 1 added, 0 changed, 1 destroyed.

Create a variable file for repeatable values:

bash
printf 'environment = "qa"\n' > test.tfvars

Load it during apply:

bash
terraform apply -var-file=test.tfvars -auto-approve
output
Plan: 1 to add, 0 to change, 1 to destroy.
...
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.

Confirm the label on the running container:

bash
docker inspect tf-apply-web --format '{{index .Config.Labels "environment"}}'
output
qa

Variable precedence between CLI flags, files, and defaults is out of scope here — this section shows only how to pass values at apply time.

Force replacement with -replace

-replace requests recreation of a specific resource even when configuration and state already agree.

Align main.tf with the live container after the qa apply — set default = "qa":

bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"

  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 3.0"
    }
  }
}

provider "docker" {}

variable "environment" {
  type    = string
  default = "qa"
}

resource "docker_image" "nginx" {
  name = "ghcr.io/nginx/nginx-unprivileged:alpine"
}

resource "docker_container" "web" {
  name    = "tf-apply-web"
  image   = docker_image.nginx.image_id
  restart = "unless-stopped"

  labels {
    label = "environment"
    value = var.environment
  }

  ports {
    internal = 8080
    external = 8082
  }
}
EOF

A normal plan shows nothing to do:

bash
terraform plan
output
No changes. Your infrastructure matches the configuration.

Request replacement explicitly on apply:

bash
terraform apply -replace=docker_container.web -auto-approve
output
# docker_container.web will be replaced, as requested
-/+ resource "docker_container" "web" {

Plan: 1 to add, 0 to change, 1 to destroy.
docker_container.web: Destroying...
docker_container.web: Destruction complete after 1s
docker_container.web: Creating...
docker_container.web: Creation complete after 1s

Apply complete! Resources: 1 added, 0 changed, 1 destroyed.

Nothing needed changing in configuration, but -replace forced recreation anyway. Deleting a container manually outside Terraform causes drift — Terraform will plan recreation on the next run, but that is not the same as a requested replace.

Limit concurrency with -parallelism

-parallelism=n limits how many resource operations Terraform runs concurrently during apply. The default is 10. It is mainly useful when API or provider rate limits require lower concurrency — raising the value does not always speed up applies because dependency ordering and provider latency still apply.


How terraform apply handles changes and failures

In-place update vs replacement

Plan symbols tell you how apply will change each resource:

Change type Plan symbol Example in this lab
Restart policy change ~ update in-place restart = "unless-stopped""no" via saved plan
Label value change -/+ replace terraform apply -var='environment=staging'
Explicit -replace -/+ replace (as requested) terraform apply -replace=docker_container.web when plan was empty

If the plan shows ~, expect the same resource address in state afterward. If it shows -/+, Terraform destroys the old object and creates a new one.

Partial apply failures

Apply is not transactional. Terraform may complete some operations, update state for those successes, and stop when a later operation errors.

Add an invalid bind mount to reproduce a safe failure:

bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"

  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 3.0"
    }
  }
}

provider "docker" {}

variable "environment" {
  type    = string
  default = "qa"
}

resource "docker_image" "nginx" {
  name = "ghcr.io/nginx/nginx-unprivileged:alpine"
}

resource "docker_container" "web" {
  name    = "tf-apply-web"
  image   = docker_image.nginx.image_id
  restart = "unless-stopped"

  labels {
    label = "environment"
    value = var.environment
  }

  ports {
    internal = 8080
    external = 8082
  }

  mounts {
    target = "/data"
    source = "/nonexistent/terraform-apply-lab-bind-path"
    type   = "bind"
  }
}
EOF

Run apply:

bash
terraform apply -auto-approve
output
docker_container.web: Destroying...
docker_container.web: Destruction complete after 0s
docker_container.web: Creating...
╷
│ Error: Unable to create container: Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /nonexistent/terraform-apply-lab-bind-path
│
│   with docker_container.web,
│   on main.tf line 23, in resource "docker_container" "web":
╵

The old container was destroyed before the replacement failed. Check state:

bash
terraform state list
output
docker_image.nginx

docker_container.web is gone from state and the container no longer appears in docker ps. Restore the working configuration without the mounts block:

bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"

  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 3.0"
    }
  }
}

provider "docker" {}

variable "environment" {
  type    = string
  default = "qa"
}

resource "docker_image" "nginx" {
  name = "ghcr.io/nginx/nginx-unprivileged:alpine"
}

resource "docker_container" "web" {
  name    = "tf-apply-web"
  image   = docker_image.nginx.image_id
  restart = "unless-stopped"

  labels {
    label = "environment"
    value = var.environment
  }

  ports {
    internal = 8080
    external = 8082
  }
}
EOF

Plan shows Terraform needs to recreate the missing container:

bash
terraform plan
output
Plan: 1 to add, 0 to change, 0 to destroy.

Apply the fix:

bash
terraform apply -auto-approve
output
docker_container.web: Creating...
docker_container.web: Creation complete after 1s

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

The container is back in state and running on the host before you continue to cleanup.

Interrupting terraform apply

If you press Ctrl+C during apply, Terraform attempts to stop gracefully. Behavior depends on which operation was in progress and how the provider handles cancellation.

Do not assume interrupt rolls back completed steps. Some resources may already exist, and state may reflect partial progress. Run terraform plan after an interrupt to see what still diverges from configuration, then apply again to converge.


terraform apply vs plan vs destroy

Command Purpose Changes infrastructure
terraform plan Preview proposed changes No (normal speculative plan)
terraform apply Execute a plan Yes
terraform destroy Tear down managed resources Yes (destroy actions)

terraform plan answers what would change. terraform apply performs the change. terraform destroy is a specialized apply path that removes managed resources — see the terraform destroy lesson for the full teardown workflow.

You normally do not need terraform validate immediately before every plan or apply when you are already in a full workflow; plan includes an implied validation check. Use terraform validate as a fast standalone or CI gate.


Common terraform apply errors

Symptom Likely cause Next step
Provider API or daemon error Invalid argument, missing host path, quota limit Read the provider error; fix configuration or environment; plan again
Insufficient permissions IAM, RBAC, or Docker socket access Fix credentials or group membership; retry
Error acquiring state lock Another process holds the backend lock Wait or follow your backend’s lock guidance
Resource already exists Name conflict outside Terraform state Import, rename, or remove the conflicting object
Timeout Slow API or network Retry; check provider status; adjust timeouts if supported
Dependency failure Parent resource failed earlier in the graph Fix the root error; plan shows remaining work
Invalid variable input Wrong type or missing required variable Fix -var, tfvars, or variable definitions

The bind-mount failure above is a provider API error: destroy succeeded, create failed, and state no longer listed the container.


Cleanup

When you finish the lab, tear down managed resources:

bash
terraform destroy -auto-approve
output
Plan: 0 to add, 0 to change, 2 to destroy.
docker_container.web: Destroying...
docker_container.web: Destruction complete after 1s
docker_image.nginx: Destroying...
docker_image.nginx: Destruction complete after 0s

Destroy complete! Resources: 2 destroyed.

Confirm Docker no longer shows the managed container:

bash
docker ps -a --filter name=tf-apply-web
output
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

An empty table means the lab container is gone. State should be empty after a successful destroy.


References


Summary

terraform apply turns Terraform's execution plan into real infrastructure changes.

You ran interactive apply with the literal yes prompt, verified the Docker container with docker ps and docker inspect, matched live objects to terraform state list, and saw a no-change re-apply when configuration already matched reality.

Saved plans with terraform plan -out=tfplan and terraform apply tfplan separate review from execution.

Flags such as -auto-approve, -var, -var-file, and -replace cover the cases you reach for after the basics — including -replace on a configuration that already showed no changes.

The bind-mount failure demonstrated partial apply:

  • Destroy completed
  • Create failed
  • State no longer listed the container

Apply is not an automatic rollback. After any failure or interrupt, plan again, fix configuration or credentials, and re-apply to converge. Finish labs with terraform destroy so containers do not keep running on the host.


Frequently Asked Questions

1. What does terraform apply do?

terraform apply executes the operations needed to make managed infrastructure match the selected plan. In automatic plan mode it plans and then asks for approval before changes. With a saved plan file it applies exactly what was planned earlier without replanning.

2. What do I type at the terraform apply approval prompt?

Type yes and press Enter. Terraform accepts only the literal word yes. Other answers such as y or yes please are rejected and the apply is cancelled.

3. What is the difference between terraform apply and terraform apply tfplan?

terraform apply without a plan file creates a fresh plan from current configuration and state, then applies after approval. terraform apply tfplan applies a saved plan from terraform plan -out without generating a new plan, which is useful for reviewed or automated workflows.

4. Should I use terraform apply -auto-approve in production?

Use -auto-approve only in controlled automation or disposable labs where skipping the prompt is intentional. Production changes should go through human or pipeline review, often with a saved plan file.

5. What happens if terraform apply fails partway through?

Terraform is not a single database transaction. Operations that already succeeded are recorded in state and remain in your infrastructure. Fix the underlying error, run terraform plan, and apply again to converge. Do not assume a failed apply rolls everything back automatically.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)