| Tested on | Ubuntu 26.04 LTS (Resolute Raccoon) |
|---|---|
| Package | terraform 1.15.8-1hashicorp/local 2.9.0hashicorp/random 3.9.0kreuzwerker/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; sudo only if Terraform or Docker is not installed yet |
| Scope | Terraform provider plugins — source addresses, required_providers, provider blocks, installation with terraform init, terraform providers, default and aliased configurations, multiple providers, conceptual authentication, and local/Docker verification. Does not cover provider version locking in depth, init flags, module provider mapping, or cloud credential walkthroughs. |
| Related guides | Terraform HCL syntax Install Terraform on Ubuntu Terraform lab environment on Ubuntu Terraform Associate certification course |
Terraform providers are plugins between Terraform Core and external systems. The configuration below declares the local provider, installs it with terraform init, and manages a file on disk — no cloud account required:
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
provider "local" {}
resource "local_file" "example" {
filename = "${path.module}/example.txt"
content = "Managed by Terraform"
}The sections below explain what providers do, how installation differs from configuration, and how aliases let you run multiple provider configurations in one root module.
~/terraform-labs/providers so they stay separate from other lessons.
How Terraform providers fit the workflow
Terraform Core reads your .tf files and state, but it does not natively know how to manage Docker containers, AWS EC2 instances, or GitHub repositories. A provider plugin implements the resource and data source types for one API.
Terraform configuration
│
▼
Terraform Core
│
▼
Provider plugin
│
▼
External API or resourceFor local_file, the plugin writes to your filesystem. For docker_container, it calls the Docker Engine API. For aws_instance, it calls AWS — this guide keeps cloud providers conceptual and practices with hashicorp/local and hashicorp/random.
What is a Terraform provider?
A provider is a compiled plugin published on the Terraform Registry (or loaded from a local mirror). Each provider adds resource types such as local_file, docker_image, or aws_s3_bucket, and data sources that read existing objects.
Common registry addresses you will see in documentation and exams:
| Source address | Manages |
|---|---|
hashicorp/local |
Files and directories on the machine running Terraform |
kreuzwerker/docker |
Docker Engine API (containers, images, networks) |
hashicorp/random |
Random values useful in tests and naming |
hashicorp/aws |
Amazon Web Services (conceptual in this guide) |
hashicorp/azurerm |
Microsoft Azure (conceptual) |
hashicorp/google |
Google Cloud Platform (conceptual) |
You declare which providers a module needs, run terraform init to download plugins, then reference provider-managed resources in resource blocks.
Declare a provider with required_providers
The terraform block lists provider dependencies for the current module. The local name (local) is what you use in provider "local" blocks and in provider = local.secondary meta-arguments. The source is the global registry address.
Create the lab directory:
mkdir -p ~/terraform-labs/providersMove into it — the remaining examples assume this path:
cd ~/terraform-labs/providersCreate versions.tf with the provider requirement:
cat > versions.tf <<'EOF'
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
EOFThe three fields work together:
| Field | Example | Role |
|---|---|---|
| Local name | local |
Identifier inside this module |
| Source address | hashicorp/local |
Registry namespace and provider type |
| Version constraint | ~> 2.5 |
Acceptable plugin versions for terraform init |
Version constraint syntax and .terraform.lock.hcl behavior belong in a dedicated provider versioning guide — this article only shows enough constraint to install a current plugin. After you change required_providers, run terraform init again so Terraform can download new plugins.
Configure a provider
The provider block configures a provider instance Terraform passes to resources. The local provider needs no arguments on Ubuntu:
cat > providers.tf <<'EOF'
provider "local" {}
EOFOther providers require settings before they can reach an API. A Docker provider might only need the default local socket; an AWS provider would expect a region and credentials through environment variables or shared config files — not hard-coded secrets in .tf files.
Add the resource from the introduction:
cat > main.tf <<'EOF'
resource "local_file" "example" {
filename = "${path.module}/example.txt"
content = "Managed by Terraform"
}
EOFprovider "local" {} is the default configuration for the local provider type. Resources that omit provider = … use this unaliased block.
Install a Terraform provider
required_providers declares the dependency; terraform init downloads the plugin binaries.
Run initialization in the lab directory:
terraform initInitializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Installing hashicorp/local v2.9.0...
- Installed hashicorp/local v2.9.0 (signed by HashiCorp)
Terraform has been successfully initialized!Terraform may also create .terraform.lock.hcl on first init. Treat that file as a record of selected provider versions — the dedicated lock-file guide explains when to commit it and how upgrades work. This article focuses on installation and configuration, not lock-file operations.
List what the configuration requires:
terraform providersProviders required by configuration:
.
└── provider[registry.terraform.io/hashicorp/local] ~> 2.5Three concepts beginners often merge:
| Mechanism | Purpose |
|---|---|
required_providers |
Declares which plugins the module needs and acceptable versions |
provider block |
Configures a provider instance (region, endpoint, credentials path) |
terraform init |
Downloads plugins into .terraform/providers/ |
terraform providers |
Prints the provider dependency tree for the working directory |
Where Terraform stores provider plugins
Plugins live under .terraform/providers/ after terraform init. Inspect the directory layout without editing files inside it — Terraform manages that tree.
find .terraform -maxdepth 4 -type d.terraform
.terraform/providers
.terraform/providers/registry.terraform.io
.terraform/providers/registry.terraform.io/hashicorp
.terraform/providers/registry.terraform.io/hashicorp/localOn this host the selected version directory is 2.9.0/. Do not hand-edit or delete individual plugin binaries unless you are deliberately resetting the working directory; use terraform init when provider requirements change.
Use a provider with a resource
Apply the configuration so Terraform creates the managed file:
terraform apply -auto-approvelocal_file.example: Creating...
local_file.example: Creation complete after 0s [id=837c55ffe364651b77f5c4ba046777d02d10e2dc]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.Confirm Terraform wrote the expected content on disk:
cat example.txtManaged by TerraformThe provider plugin performed the write. terraform destroy would remove the resource through the same plugin path.
Configure multiple Terraform providers
Multiple providers can mean two different things:
- Different provider types in one module — for example
localandrandomtogether. - Multiple configurations of the same type — handled with provider aliases below.
Create a separate directory for two provider types:
mkdir -p ~/terraform-labs/providers/multiSwitch into the multi-provider directory:
cd ~/terraform-labs/providers/multiWrite a configuration that uses both hashicorp/local and hashicorp/random:
cat > main.tf <<'EOF'
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
provider "local" {}
provider "random" {}
resource "random_id" "suffix" {
byte_length = 2
}
resource "local_file" "tagged" {
filename = "${path.module}/id-${random_id.suffix.hex}.txt"
content = "suffix=${random_id.suffix.hex}"
}
EOFInitialize and inspect the provider tree:
terraform initPrint the provider requirement tree for both plugins:
terraform providersProviders required by configuration:
.
├── provider[registry.terraform.io/hashicorp/local] ~> 2.5
└── provider[registry.terraform.io/hashicorp/random] ~> 3.6Each top-level provider block configures the default instance for its type. The random_id resource uses the random provider implicitly; local_file uses local.
Terraform provider aliases
An alias labels a non-default provider configuration. Use aliases when you need two regions, two Docker hosts, or two accounts of the same provider type.
Create an aliases exercise directory:
mkdir -p ~/terraform-labs/providers/aliasesChange into that directory:
cd ~/terraform-labs/providers/aliasesSave the aliased provider configuration:
cat > main.tf <<'EOF'
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
provider "local" {}
provider "local" {
alias = "secondary"
}
resource "local_file" "default_cfg" {
filename = "${path.module}/default.txt"
content = "default provider config"
}
resource "local_file" "aliased_cfg" {
provider = local.secondary
filename = "${path.module}/aliased.txt"
content = "secondary alias config"
}
EOFInitialize and apply:
terraform initApply so both default and aliased files are created:
terraform apply -auto-approvelocal_file.aliased_cfg: Creation complete after 0s
local_file.default_cfg: Creation complete after 0s
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.List both files on disk:
ls -l default.txt aliased.txtBoth files appear because default_cfg uses the unaliased provider "local" {} and aliased_cfg selects provider = local.secondary.
Rules to remember:
- The unaliased
provider "local" {}block is the default configuration. alias = "secondary"createslocal.secondaryfor explicit selection.- Resources use
provider = <local-name>.<alias>when they should not use the default.
When every provider block uses an alias
When every provider block for a type carries an alias, Terraform still creates an implied empty default configuration for that provider. Resources that omit the provider meta-argument bind to that implied default—not to any aliased block.
This configuration declares only an aliased local provider:
provider "local" {
alias = "secondary"
}
resource "local_file" "x" {
filename = "${path.module}/x.txt"
content = "x"
}local_file.x uses the implied empty default local configuration. That works here because the local provider has no mandatory configuration arguments. Whether the implied empty default works depends on the provider. Providers that require configuration not available from defaults or the environment can fail when resources use that empty configuration.
Aliased blocks such as local.secondary remain available for resources that set provider = local.secondary. When you refactor provider blocks, watch state: removing a configuration that existing resources still reference produces the Provider configuration not present error covered in troubleshooting.
Provider configuration and modules
Child modules automatically inherit default (unaliased) provider configurations from their parent. Aliased configurations are not inherited automatically; the child declares them with configuration_aliases, and the caller passes the matching configurations through the providers argument.
Module provider inheritance and the providers meta-argument belong in the modules guide. For now, remember that required_providers in a child module declares dependencies, while the root module supplies configured provider instances.
Provider authentication
Authentication is provider-specific. Terraform does not centralize cloud logins in one block.
Typical patterns:
- Environment variables — many providers read
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,ARM_*, orGOOGLE_CREDENTIALS. - Shared credential files — AWS CLI profiles, Azure CLI, or
~/.docker/config.json. - Workload identity — IAM roles for EC2, managed identities on Azure, or GKE workload identity on Google Cloud.
- Local sockets — the Docker provider talks to
/var/run/docker.sockby default on Linux.
Do not paste secrets into .tf files to make examples easy to copy. Use environment variables or credential files the provider documents, and keep secret values out of version control.
Provider source address vs local name
The registry address and the module-local name are different on purpose. This pattern scales when the same module uses multiple sources or aliases:
terraform {
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}| Symbol | Value | Meaning |
|---|---|---|
| Global source | hashicorp/random |
Registry namespace and provider type |
| Local name | random |
Key used in provider "random" and random.secondary |
A module could map random = { source = "hashicorp/random" } while resources still call provider = random.replica. The local name is arbitrary but must stay consistent inside the module.
View providers used by a configuration
terraform providers shows the dependency tree for the current working directory. From the multi-provider example:
cd ~/terraform-labs/providers/multiRe-run the providers report from that directory:
terraform providersProviders required by configuration:
.
├── provider[registry.terraform.io/hashicorp/local] ~> 2.5
└── provider[registry.terraform.io/hashicorp/random] ~> 3.6When modules are nested, the output indents child modules under their parents. Use it after refactors to confirm the root module still pulls the providers you expect.
Optional Docker provider example
When Docker Engine is installed (as in the Terraform lab environment), the kreuzwerker/docker provider talks to the local daemon:
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 3.0"
}
}
}
provider "docker" {}
resource "docker_image" "hello" {
name = "hello-world:latest"
}terraform init installs kreuzwerker/docker the same way as hashicorp/local. Image pull and container lifecycle are covered in resource-focused lessons — here the point is that any registry provider follows the same declare → init → configure → resource pattern.
Common Terraform provider errors
| Symptom | Likely cause | Fix |
|---|---|---|
| Failed to query available provider packages | Wrong source address or typo in namespace |
Verify the address on the registry; fix required_providers |
| Missing required provider / Required plugins are not installed | terraform init not run, or .terraform/ removed |
Run terraform init in the working directory |
| Inconsistent dependency lock file | Config changed without re-init | Run terraform init after editing required_providers |
| Provider configuration not present | State contains resources tied to a provider configuration or alias that was later removed from the configuration | Restore that provider configuration long enough to destroy or migrate the affected resources |
| Unsupported argument | Unsupported argument in provider block |
Compare with the provider documentation |
Failed to query available provider packages
Use a deliberately invalid source such as hashicorp/not-a-real-provider, then initialize:
terraform initError: Failed to query available provider packages
Could not retrieve the list of available versions for provider
hashicorp/not-a-real-provider: provider registry registry.terraform.io does
not have a provider named registry.terraform.io/hashicorp/not-a-real-providerFix the source string in required_providers to match a real registry address such as hashicorp/local.
Provider not installed
If the configuration has not been initialized, or .terraform/ was removed, run terraform init before terraform validate or terraform plan. Validation and planning expect required provider plugins to already be installed — HashiCorp defines terraform init as the command that downloads those plugins from required_providers.
From ~/terraform-labs/providers with a valid configuration but no plugin cache, remove the local plugin directory:
cd ~/terraform-labs/providersDelete .terraform/ to simulate a fresh clone or manual cache removal:
rm -rf .terraformRe-run initialization so Terraform reinstalls the required plugins:
terraform initInitializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Installing hashicorp/local v2.9.0...
- Installed hashicorp/local v2.9.0 (signed by HashiCorp)
Terraform has been successfully initialized!The Installing lines confirm Terraform repopulated .terraform/providers/. Run terraform init again whenever you clone a configuration, delete .terraform/, or change required_providers.
Provider configuration not present
This error appears when state still references a provider configuration you removed from .tf files. A common path: apply a resource with provider = local.secondary, delete the provider "local" { alias = "secondary" } block, then run a later Terraform operation that needs to work with that state. terraform plan surfaces it:
terraform planError: Provider configuration not present
To work with local_file.z its original provider configuration at
provider["registry.terraform.io/hashicorp/local"].secondary is required, but
it has been removed. This occurs when a provider configuration is removed
while objects created by that provider still exist in the state. Re-add the
provider configuration to destroy local_file.z, after which you can remove
the provider configuration again.Terraform needs the original provider configuration to operate on those state objects. Re-add the missing provider block (or the old alias name), run terraform destroy or migrate state, then remove the configuration once nothing in state depends on it.
Provider vs resource vs data source
| Layer | Role | Example |
|---|---|---|
| Provider | API integration and configuration | provider "local" {} |
| Resource | Managed object Terraform creates, updates, and destroys | resource "local_file" "example" { … } |
| Data source | Read-only lookup of existing objects | data "local_file" "readme" { … } |
Providers enable resources and data sources. Resource and data source syntax are covered in their own articles — this guide stops at how plugins are declared, installed, and selected.
References
- Providers — Terraform documentation
- Provider requirements — Terraform documentation
- Provider configuration — Terraform documentation
- Terraform Registry
- local provider documentation
- Terraform Associate 004 Learning Path
Summary
You declared Terraform providers with required_providers, configured default instances with provider blocks, and installed plugins through terraform init into .terraform/providers/.
The hashicorp/local walkthrough connected provider configuration to a real local_file on disk. The local plus random example showed multiple provider types in one module.
Provider aliases let you name alternate configurations and select them with provider = local.secondary:
- Resources without an explicit
providerargument use the default or implied empty configuration for that type - Removing a provider block that state still references triggers
Provider configuration not presentuntil you destroy or migrate those resources
Authentication stays provider-specific — use environment variables and credential files rather than secrets in .tf files.
When you are ready to constrain provider versions and understand .terraform.lock.hcl in depth, continue with the provider version guide. For initialization flags and backend setup, see the dedicated terraform init lesson.
Next, practice resource lifecycle details once providers are installed, or return to the Terraform lab environment if Docker is not available yet.

