Managing Docker containers with Terraform brings the same declarative infrastructure-as-code approach to your container workloads that you use for cloud resources. The shift away from imperative docker run commands toward HCL definitions changes how teams reason about container lifecycle. Instead of running sequential shell commands that mutate state without record, you define the desired container state in HCL and let Terraform handle the rest. This guide covers everything from basic container creation to advanced configurations including health checks, resource limits, and restart policies.
The adoption of Terraform for Docker is not a replacement for Docker itself. It is a layer of reproducibility on top of the Docker Engine. The declarative model means the configuration becomes the single source of truth for what containers should exist, which ports they expose, which images they use, and how they are connected. Terraform tracks the state of your containers, making updates and teardowns predictable. When a configuration changes, Terraform computes a plan that shows additions, changes, and destroys before any mutation occurs. This predictability reduces drift between development environments and single-host deployments and makes testing scenarios repeatable across machines.
Why Manage Containers with Terraform
Using Terraform for Docker containers makes sense when you want a unified workflow for both infrastructure and container management. It is especially useful for local development environments, single-host Docker deployments, and testing scenarios where you need reproducible container configurations.
The unified workflow impact is felt in teams that already use Terraform for cloud resources. The same tooling, state files, and approval processes can be extended to containers without introducing a second workflow. For local development, a developer can clone a repository, run Terraform init and apply, and obtain the exact same set of containers as the rest of the team. For single-host Docker deployments, the infrastructure definition lives in version control, which allows rollback to a previous commit and audit of who changed which container definition. For testing scenarios, reproducible container configurations mean tests can be torn down and recreated with a single Terraform destroy and apply cycle.
Terraform tracks the state of your containers, making updates and teardowns predictable. State tracking means Terraform knows which container resources exist, which images are pulled, and which network attachments are in place. When an update is required, Terraform compares the desired state in HCL with the real state recorded in the state file. This comparison prevents accidental recreation of containers that should be updated in place and ensures that teardown removes only resources that were created by Terraform.
Prerequisites and Working Directory Requirements
You need the following to follow along:
- Terraform 1.0 or later
- Docker Engine installed and running
- The Docker provider for Terraform kreuzwerker/docker
- Basic knowledge of Docker and Terraform
The prerequisite set establishes a minimum operational baseline. Terraform 1.0 or later provides stable provider dependency resolution and state management features. Docker Engine installed and running ensures the local daemon is reachable by the provider. The Docker provider for Terraform kreuzwerker/docker is the bridge between Terraform and the Docker Engine API. Basic knowledge of Docker and Terraform reduces the friction of understanding resource naming, image pulling, and port mapping concepts.
A Terraform configuration must live in its own working directory. Each Terraform configuration must be in its own working directory. You created a working directory previously in learn-terraform-docker-container. Review the main.tf file.
In a tutorial flow, the steps to create a working directory are explicit:
mkdir learn-terraform-docker-container
cd learn-terraform-docker-container
touch main.tf
The working directory isolation prevents resource name collisions and allows multiple independent container stacks on the same host. The main.tf file becomes the entry point for the provider configuration and resource definitions.
Provider Configuration
Provider configuration defines how Terraform talks to Docker.
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 4.0"
}
}
}
provider "docker" {}
The required_providers block pins the source to kreuzwerker/docker. Version constraints such as ~> 4.0 or ~> 4.2.0 are used in examples. The provider docker {} block uses the local Docker daemon by default.
In the tutorial configuration, a slightly different constraint appears:
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 4.2.0"
}
}
}
provider "docker" {}
The variation between ~> 4.0 and ~> 4.2.0 reflects the evolution of provider releases. The provider block remains empty because the default configuration connects to the local Docker daemon. This simplicity means a developer does not need to supply endpoint URLs unless a remote Docker host is used.
The Terraform Docker provider lets you manage Docker images, containers, volumes, and networks declaratively using HCL. Here’s how to set it up. The provider scope covers images, containers, volumes, and networks, which means the same HCL configuration can describe the full container environment rather than isolated containers.
Basic Container Creation
The simplest container configuration pulls an image and runs it.
resource "docker_image" "nginx" {
name = "nginx:1.25-alpine"
}
resource "docker_container" "web" {
name = "web-server"
image = docker_image.nginx.image_id
ports {
internal = 80
external = 8080
protocol = "tcp"
}
must_run = true
}
The dockerimage resource declares the image to be present locally. The name attribute points to nginx:1.25-alpine. The dockercontainer resource references the imageid from the image resource, sets a name of web-server, maps container port 80 to host port 8080 with protocol tcp, and sets mustrun = true so the container starts automatically.
An alternative example uses nginx:latest with keep_locally = false:
resource "docker_image" "nginx" {
name = "nginx:latest"
keep_locally = false
}
resource "docker_container" "nginx" {
image = docker_image.nginx.image_id
name = "tutorial"
ports {
internal = 80
external = 8000
}
}
The name tutorial is used for the container and the ports block maps internal 80 to external 8000. The keep_locally flag controls whether the image remains after Terraform destroys the container.
The set of files used to describe infrastructure in Terraform is known as a Terraform configuration. This is a complete configuration that you can deploy with Terraform. The configuration completeness means a single apply creates the image, creates the container, and configures port bindings without additional manual steps.
Image Management and Pull Behavior
By default, the Docker image resource in Terraform pulls an image.
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "4.0.0"
}
}
}
resource "docker_image" "alpine" {
name = "alpine:latest"
}
The resource declares alpine:latest. Before applying the code, the host may have no images. Let’s apply this code and see what happens:
Terraform will perform the following actions:
```
docker_image.alpine will be created
- resource "docker_image" "alpine" {
- id = (known after apply)
- image_id = (known after apply)
- name = "alpine:latest"
- repo_digest = (known after apply)
}
```
Plan: 1 to add, 0 to change, 0 to destroy.
docker_image.alpine: Creating...
docker_image.alpine: Creation complete after 4s [id=sha256:0b4426ad4bf25e13fb09112b9dcb5d5b09b3c5684599654583913b2714a705a2alpine:latest]
Apply complete
The plan shows one resource to add. The apply output confirms creation with an image ID hash.
Replace 3.9.0 with the latest version from the Terraform Registry before you copy-paste this. The note reflects the need to keep provider versions current.
Always use specific image tags instead of latest to ensure reproducibility. Set resource limits on containers to prevent any single container from consuming all host resources. Use health checks for all application containers so Docker can detect and report failures. Configure restart policies for containers that should survive host reboots. Use Docker networks instead of container links for inter-container communication. Store sensitive environment variables in Terraform variables marked as sensitive.
These guidance points shape operational reliability. Specific image tags avoid unexpected upgrades when latest moves. Resource limits protect host stability. Health checks enable Docker to detect and report failures. Restart policies ensure containers survive host reboots. Docker networks provide proper inter-container communication instead of legacy links. Sensitive variables protect secrets.
Container Configuration with Environment Variables
Pass configuration to your container through environment variables.
resource "docker_image" "app" {
name = "node:20-alpine"
}
resource "docker_container" "app" {
name = "my-application"
image = docker_image.app.image_id
env =
The example shows pulling node:20-alpine and creating a container named my-application. The env attribute is the mechanism to inject configuration without rebuilding images.
The Terraform Docker provider lets you manage Docker images, containers, volumes, and networks declaratively using HCL. The TL;DR summary states this capability. The article will explain how to use Terraform for automation to set up and deploy Docker containers. The demonstration uses Docker for Windows Desktop to deploy a demo container with the Terraform Docker provider.
What we will cover includes the actual deployment of Docker containers and CI/CD pipeline integration. There are multiple integrations that you could leverage using Terraform and Docker, from simply building a Docker image to pushing it to a registry and the actual creation of the container.
Running Terraform Inside a Docker Container
Terraform is an infrastructure as code IaC tool that allows you to build, change, and version infrastructure safely and efficiently. This includes low-level components such as compute instances, storage, and networking, as well as high-level components such as DNS entries, SaaS features, etc. Terraform can manage both existing service providers and custom in-house solutions.
Running Terraform inside a Docker container requires more configuration than running the Terraform CLI executables directly. Unless you need container isolation, we recommend using the non-containerized Terraform CLI packages.
The Terraform team publishes a Docker image to this repository for each official release of Terraform CLI. Each versioned image includes the Terraform CLI release with the same version number.
These images wrap the terraform executable, allowing you to run Terraform subcommands by passing in their names and arguments as part of docker run. For example, the command below uses the 'latest' tag to generate a plan using the most recent version of Terraform:
docker run -i -t hashicorp/terraform:latest plan
Note that for production use, we recommend specifying a specific version instead of using latest.
You will likely need to further configure your container so that Terraform can access your configuration files and provider credentials.
The containerized Terraform approach separates the CLI from the host environment. The latest tag provides convenience for experimentation but production use benefits from a pinned version to avoid unexpected CLI changes. Access to configuration files and provider credentials requires volume mounts and environment variables, which adds configuration complexity compared to a native CLI install.
Best Practices and Operational Guidance
The guide covers everything from basic container creation to advanced configurations including health checks, resource limits, and restart policies.
Best practices from the reference material include:
- Always use specific image tags instead of latest to ensure reproducibility
- Set resource limits on containers to prevent any single container from consuming all host resources
- Use health checks for all application containers so Docker can detect and report failures
- Configure restart policies for containers that should survive host reboots
- Use Docker networks instead of container links for inter-container communication
- Store sensitive environment variables in Terraform variables marked as sensitive
The impact of these practices is stability and security. Reproducibility reduces incident variance across environments. Resource limits protect noisy neighbor scenarios. Health checks provide early failure detection. Restart policies maintain availability across host restarts. Networks provide proper isolation and discoverability. Sensitive variable handling prevents secret leakage in state files and logs.
Terraform provides a powerful declarative approach to managing Docker containers. From simple single-container deployments to complex multi-container stacks, you can define your entire container infrastructure as code. This makes your deployments reproducible, version-controlled, and easy to tear down and recreate. Combined with Docker networks and volumes, you can build complete application environments that are fully managed through Terraform.
The declarative approach means changes are expressed as differences in HCL rather than imperative steps. Version control applies to the configuration. Tear down is achieved with terraform destroy. Networks and volumes allow complete application environments to be managed through Terraform.
Monitoring with OneUptime
Keep track of your container health and performance with OneUptime. Monitor container uptime, track resource utilization, and get alerted when containers become unhealthy or restart unexpectedly.
Observability closes the loop on declarative management. Terraform defines the desired state, Docker enforces it, and monitoring surfaces deviations. Uptime tracking shows availability. Resource utilization tracking shows performance trends. Alerts on unhealthy containers or unexpected restarts provide timely operational response.
Configuration Summary Tables
| Item | Example Value |
| Provider source | kreuzwerker/docker |
| Provider version constraints | ~> 4.0, ~> 4.2.0, 4.0.0 |
| Terraform minimum version | 1.0 or later, 0.15+ |
| Docker image example | nginx:1.25-alpine, nginx:latest, node:20-alpine, alpine:latest |
| Container name example | web-server, tutorial, my-application |
| Port mapping example | internal 80 to external 8080, internal 80 to external 8000 |
| Prerequisite | Requirement Detail |
| Terraform | 1.0 or later |
| Docker Engine | installed and running |
| Docker provider | kreuzwerker/docker |
| Knowledge | Basic Docker and Terraform |
| Resource Type | Key Attributes |
| dockerimage | name, keeplocally |
| dockercontainer | name, image, ports, mustrun, env |
Conclusion
Terraform provides a powerful declarative approach to managing Docker containers. From simple single-container deployments to complex multi-container stacks, you can define your entire container infrastructure as code. This makes your deployments reproducible, version-controlled, and easy to tear down and recreate. Combined with Docker networks and volumes, you can build complete application environments that are fully managed through Terraform.
The long-term value of this approach is in consistency and automation. Teams that adopt Terraform for Docker reduce manual docker run drift, gain auditability through state files, and align container workflows with broader IaC practices. The provider abstraction means image pulls, container creation, port publishing, environment variable injection, and network attachment are all expressed in the same HCL language used for cloud resources. When integrated with CI/CD pipelines, container deployments become automated from image build through registry push to container creation. The result is a unified, versioned, and reproducible container lifecycle that scales from local development to single-host production.