Terraform with Docker Provider
The Terraform Docker provider enables managing Docker containers, images, networks, and volumes using Terraform's declarative infrastructure-as-code approach. This integration allows you to define containerized infrastructure in code, enabling version control, repeatable deployments, and multi-environment consistency. This guide covers Docker provider configuration, container management, image handling, networking, volumes, Docker Compose comparison, and practical examples.
Table of Contents
- Docker Provider Overview
- Provider Configuration
- Managing Docker Images
- Managing Containers
- Docker Networks
- Docker Volumes
- Docker Compose Comparison
- Practical Examples
- Best Practices
- Conclusion
Docker Provider Overview
The Terraform Docker provider enables infrastructure-as-code management of Docker resources. Instead of manually running docker commands, you define desired container infrastructure in Terraform configuration, enabling version control, collaboration, and automated deployment.
Key advantages:
- Infrastructure as Code: Manage containers like other infrastructure
- Version Control: Track container configuration changes in git
- Repeatability: Spin up identical environments reliably
- Multi-Environment: Use same configuration across dev/staging/prod
- Integration: Combine with other Terraform resources
- Documentation: Terraform becomes infrastructure documentation
Docker provider architecture:
┌──────────────────────┐
│ Terraform Config │
│ (docker resources) │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Docker Provider │
│ (Terraform Plugin) │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Docker API │
│ (Local/Remote) │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ Docker Engine │
│ (Create/Manage) │
└──────────────────────┘
Provider Configuration
Configure Terraform to use the Docker provider.
Basic provider configuration:
# versions.tf
terraform {
required_version = ">= 1.0"
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 3.0"
}
}
}
provider "docker" {
host = "unix:///var/run/docker.sock"
}
Remote Docker configuration:
provider "docker" {
# Connect to remote Docker daemon
host = "tcp://docker-host.example.com:2375"
# Enable TLS for secure connection
# host = "tcp://docker-host.example.com:2376"
# ca_data = file("${path.module}/ca.pem")
# cert_data = file("${path.module}/cert.pem")
# key_data = file("${path.module}/key.pem")
}
Docker registry configuration:
provider "docker" {
host = "unix:///var/run/docker.sock"
# Configure registries
registry_auth {
address = "ghcr.io"
username = var.ghcr_username
password = var.ghcr_password
}
registry_auth {
address = "docker.io"
username = var.dockerhub_username
password = var.dockerhub_password
}
}
Variables for credentials:
# variables.tf
variable "docker_host" {
type = string
description = "Docker daemon socket/host"
default = "unix:///var/run/docker.sock"
}
variable "ghcr_username" {
type = string
description = "GitHub Container Registry username"
sensitive = true
}
variable "ghcr_password" {
type = string
description = "GitHub Container Registry password"
sensitive = true
}
Managing Docker Images
Build and manage Docker images with Terraform.
Pull existing image:
# main.tf
# Pull image from Docker Hub
resource "docker_image" "ubuntu" {
name = "ubuntu:22.04"
keep_locally = true # Keep image after destroy
}
# Pull from private registry
resource "docker_image" "private" {
name = "ghcr.io/myorg/myapp:latest"
keep_locally = true
pull_triggers = [
var.image_tag # Repull on tag change
]
}
Build image from Dockerfile:
# Build image
resource "docker_image" "myapp" {
name = "myapp:1.0"
build {
context = "${path.module}/app"
dockerfile = "Dockerfile"
force_remove = true
build_arg = {
ENVIRONMENT = var.environment
VERSION = var.app_version
}
label = {
Name = "myapp"
Environment = var.environment
}
}
}
# Use built image
resource "docker_container" "app" {
image = docker_image.myapp.image_id
name = "myapp-container"
}
Build with registry push:
# Build and push to registry
resource "docker_image" "registry" {
name = "ghcr.io/myorg/myapp:${var.version}"
build {
context = "${path.module}/app"
dockerfile = "Dockerfile"
}
# Push after build
force_remove = true
# Push to registry
registry_digest = docker_registry_image.registry_image.digest
}
resource "docker_registry_image" "registry_image" {
name = docker_image.registry.name
}
Image outputs:
output "image_id" {
value = docker_image.myapp.image_id
description = "Image ID"
}
output "image_sha" {
value = docker_image.myapp.sha256_digest
description = "Image SHA256 digest"
}
output "image_repo_digest" {
value = docker_image.myapp.repo_digest
description = "Repository digest"
}
Managing Containers
Create and manage Docker containers.
Basic container:
resource "docker_container" "web" {
image = "nginx:latest"
name = "web-server"
ports {
internal = 80
external = 8080
}
env = [
"NGINX_HOST=example.com",
"NGINX_PORT=80"
]
restart_policy = "on-failure"
}
Advanced container configuration:
resource "docker_container" "app" {
image = docker_image.myapp.image_id
name = "myapp-${var.environment}"
# Port mapping
ports {
internal = 3000
external = 3000
}
ports {
internal = 3001
external = 3001
protocol = "tcp"
}
# Port ranges
ports {
internal = 5000
external = 5001
ip = "127.0.0.1"
}
# Environment variables
env = [
"APP_ENV=${var.environment}",
"APP_VERSION=${var.app_version}",
"DATABASE_URL=${var.database_url}",
]
# Mounts
mounts {
type = "bind"
source = "/host/data"
target = "/container/data"
read_only = false
}
mounts {
type = "volume"
source = docker_volume.app_data.name
target = "/app/data"
}
# Volume management
volumes {
container_path = "/data"
host_path = "/host/data"
read_only = false
}
# Resource limits
memory = 512 # MB
memory_swap = -1 # Unlimited swap
cpus = 1.0
# Logging
log_driver = "json-file"
log_opts = {
"max-size" = "10m"
"max-file" = "3"
}
# Restart policy
restart_policy = "on-failure"
max_retries = 5
# Privileged and capabilities
privileged = false
capabilities {
add = ["NET_ADMIN"]
drop = ["ALL"]
}
# Health check
healthchecks {
test = ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval = "30s"
timeout = "5s"
start_period = "5s"
retries = 3
}
# Depends on other containers
depends_on = [
docker_container.database
]
# DNS and hosts
dns = ["8.8.8.8", "8.8.4.4"]
dns_search = ["example.com"]
hostname = "app-server"
# User and groups
user = "appuser"
# Working directory
working_dir = "/app"
# Command and entrypoint
command = ["npm", "start"]
entrypoint = ["/entrypoint.sh"]
# Labels
labels = {
"com.example.app" = "myapp"
"environment" = var.environment
"managed_by" = "terraform"
}
}
Docker Networks
Create and manage Docker networks for container communication.
Create network:
resource "docker_network" "private" {
name = "app-network"
driver = "bridge"
check_duplicate = true
ipam_config {
subnet = "172.20.0.0/16"
}
labels = {
"network_type" = "private"
}
}
# Connect containers to network
resource "docker_container" "web" {
image = "nginx:latest"
name = "web"
networks_advanced {
name = docker_network.private.name
ipv4_address = "172.20.0.2"
}
}
resource "docker_container" "app" {
image = "myapp:latest"
name = "app"
networks_advanced {
name = docker_network.private.name
ipv4_address = "172.20.0.3"
}
}
# Container communication using hostnames
# web can reach app at: http://app:3000
Multi-network setup:
# Frontend network
resource "docker_network" "frontend" {
name = "frontend-network"
driver = "bridge"
}
# Backend network (isolated)
resource "docker_network" "backend" {
name = "backend-network"
driver = "bridge"
}
# Web tier on frontend
resource "docker_container" "nginx" {
image = "nginx:latest"
name = "nginx"
networks_advanced {
name = docker_network.frontend.name
}
}
# App tier bridges networks
resource "docker_container" "app" {
image = "myapp:latest"
name = "app"
networks_advanced {
name = docker_network.frontend.name
}
networks_advanced {
name = docker_network.backend.name
}
}
# Database tier on backend only
resource "docker_container" "db" {
image = "postgres:14"
name = "postgres"
networks_advanced {
name = docker_network.backend.name
}
}
Docker Volumes
Manage persistent storage for containers.
Create volume:
# Named volume
resource "docker_volume" "app_data" {
name = "app-data"
driver = "local"
labels = {
backup = "daily"
}
}
# Use volume in container
resource "docker_container" "app" {
image = "myapp:latest"
name = "app"
volumes {
volume_name = docker_volume.app_data.name
container_path = "/app/data"
read_only = false
}
}
# Volume outputs
output "volume_name" {
value = docker_volume.app_data.name
}
output "volume_mountpoint" {
value = docker_volume.app_data.mountpoint
}
Bind mount for development:
resource "docker_container" "dev" {
image = "myapp:latest"
name = "app-dev"
# Bind mount for live code
volumes {
host_path = "${path.module}/app"
container_path = "/app/src"
read_only = false
}
# Named volume for dependencies
volumes {
volume_name = docker_volume.node_modules.name
container_path = "/app/node_modules"
read_only = false
}
}
resource "docker_volume" "node_modules" {
name = "app-node-modules"
}
Docker Compose Comparison
Terraform vs Docker Compose for container orchestration.
Terraform strengths:
# Define other infrastructure alongside containers
resource "aws_security_group" "app" {
name = "app-sg"
# Security group rules
}
resource "docker_container" "app" {
image = "myapp:latest"
name = "app"
# Container with security group reference
# (demonstrates IaC integration)
}
# Version control and state management
# Easy multi-environment configuration
# Team collaboration with terraform workflows
Docker Compose strengths:
# docker-compose.yml - Simpler for local development
version: '3.8'
services:
web:
image: nginx:latest
ports:
- "8080:80"
depends_on:
- app
app:
build: .
environment:
- DATABASE_URL=postgres://db:5432/myapp
depends_on:
- db
db:
image: postgres:14
environment:
- POSTGRES_PASSWORD=secret
Using both together:
# Terraform manages images and infrastructure
resource "docker_image" "myapp" {
build {
context = "${path.module}/app"
dockerfile = "Dockerfile"
}
}
# Docker Compose runs on developer machines
# Terraform manages production infrastructure
Practical Examples
Web application stack:
# Network
resource "docker_network" "app" {
name = "app-network"
}
# Database volume
resource "docker_volume" "db_data" {
name = "postgres-data"
}
# PostgreSQL database
resource "docker_container" "db" {
image = "postgres:14"
name = "postgres"
env = [
"POSTGRES_PASSWORD=${var.db_password}",
"POSTGRES_USER=appuser",
"POSTGRES_DB=myapp"
]
networks_advanced {
name = docker_network.app.name
}
volumes {
volume_name = docker_volume.db_data.name
container_path = "/var/lib/postgresql/data"
}
}
# Application
resource "docker_image" "app" {
name = "myapp:${var.version}"
build {
context = "${path.module}/app"
}
}
resource "docker_container" "app" {
image = docker_image.app.image_id
name = "app"
env = [
"DATABASE_URL=postgres://appuser:${var.db_password}@postgres:5432/myapp",
"ENVIRONMENT=${var.environment}"
]
networks_advanced {
name = docker_network.app.name
}
depends_on = [docker_container.db]
}
# Nginx reverse proxy
resource "docker_container" "nginx" {
image = "nginx:latest"
name = "nginx"
ports {
internal = 80
external = 80
}
volumes {
host_path = "${path.module}/nginx.conf"
container_path = "/etc/nginx/nginx.conf"
}
networks_advanced {
name = docker_network.app.name
}
depends_on = [docker_container.app]
}
Best Practices
Image management:
# Use specific versions
resource "docker_image" "app" {
name = "myapp:1.0.0" # Specific version
# NOT "myapp:latest"
}
# Tag images consistently
resource "docker_image" "app" {
name = "ghcr.io/myorg/myapp:v${var.app_version}"
}
# Remove dangling images
resource "docker_image" "app" {
force_remove = true
}
Container management:
# Always set restart policy
resource "docker_container" "app" {
restart_policy = "on-failure"
max_retries = 5
}
# Health checks
resource "docker_container" "app" {
healthchecks {
test = ["CMD", "curl", "-f", "http://localhost:3000"]
interval = "30s"
timeout = "5s"
retries = 3
}
}
# Resource limits
resource "docker_container" "app" {
memory = 1024 # 1 GB
cpus = 1.0
}
Conclusion
The Terraform Docker provider brings infrastructure-as-code principles to containerized applications, enabling version-controlled, repeatable Docker deployments. By managing containers, networks, and volumes with Terraform, you gain consistency across environments, better collaboration through git-based workflows, and seamless integration with other infrastructure resources. Combine Terraform's Docker provider with your container strategy to build scalable, maintainable containerized infrastructure.


