Simple Local Development Provisioning with Terraform
2025-04-17Discover how to simplify local development environment setup with Terraform. Learn about its declarative state management, secure credential handling, and minimal configuration for managing dotfiles and templates effectively.
Simple Local Development Provisioning with Terraform
For years, I managed my local development environment using mitamae, an Ansible-like configuration automation tool. It served me well for a while, but over time, I found myself wrestling with several frustrations:
- The DSL wasn't widely adopted, making troubleshooting a solitary endeavor
- Debugging issues was often painful and time-consuming
- No straightforward way to track and remove files once I deleted them from source
- Complexity kept growing as I added more features
These pain points led me to reconsider my approach. I wanted something:
- Minimal with fewer dependencies
- Simple yet capable of handling credentials securely
- Declarative to easily track state (added and removed files)
- Familiar using tools and languages I already know well
The answer turned out to be right in front of me: Terraform.
Why Terraform for Local Environment Management?
You might be thinking, "Isn't Terraform for managing cloud infrastructure?" Yes, but its declarative approach to state management works remarkably well for local environment configuration too.
Here's why I made the switch:
- State Management: Terraform tracks the exact state of your system, making it easy to add or remove files
- Idempotent Operations: Run it as many times as you want with the same result
- Simple HCL Syntax: Much easier to read and write than specialized DSLs
- Extensive Documentation: Massive community support for troubleshooting
- Templating System: Built-in support for dynamic configuration files
Implementation Overview
My implementation focuses on three key components:
- File resources for straightforward dotfiles
- Template resources for configurations that need dynamic variables
- Variable management for secure credential handling
Let's break down the approach.
Core Terraform Configuration
My main Terraform configuration is refreshingly simple:
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "2.5.2"
}
}
}
provider "local" {
}
locals {
path_home = pathexpand("~")
files = toset([
".gitconfig",
".zprofile",
".zshrc",
".vim/ftplugin/make.vim",
".vimrc",
".ssh/config",
// and more dotfiles...
])
templates = {
"Library/Application Support/Claude/claude_desktop_config.json" : {
BRAVE_API_KEY : "${var.brave_api_key}",
},
}
}
resource "local_file" "files" {
for_each = local.files
content = file("${path.module}/files/${each.key}")
filename = "${local.path_home}/${each.key}"
file_permission = "0766"
directory_permission = "0766"
}
resource "local_file" "templates" {
for_each = local.templates
content = templatefile("${path.module}/templates/${each.key}", each.value)
filename = "${local.path_home}/${each.key}"
file_permission = "0766"
directory_permission = "0766"
}
The elegance here is in the simplicity. I maintain a list of dotfiles and template files, and Terraform handles creating, updating, or removing them as needed.
Secure Credential Management
For sensitive data like API keys, I use Terraform variables combined with 1Password CLI:
// variables.tf
variable "brave_api_key" {
description = "API key for Brave"
type = string
sensitive = true
}
Then, I pull these credentials at runtime using a simple wrapper script:
#!/usr/bin/env bash
set -euo pipefail
state_dir="states/$(hostname)/${USER}"
state_path="${state_dir}/terraform.tfstate"
mkdir -p ${state_dir}
terraform apply -auto-approve -state=${state_path} \
-var="brave_api_key=$(op read "op://<foo>/<bar>/<value>")"
This approach keeps credentials out of version control while making them available when needed.
Machine-Specific State Management
One of the most powerful aspects of this setup is maintaining separate state files for each machine:
states/
MacBook-Pro/
ken/
terraform.tfstate
iMac/
ken/
terraform.tfstate
This allows me to:
- Track machine-specific configurations
- Handle different requirements across devices
- Easily add/remove files on a per-machine basis
Benefits Over Configuration Management
After switching to this Terraform-based approach, I've noticed several advantages:
- Reduced Complexity: The entire setup is under 100 lines of Terraform code
- Improved Reliability: Clear state tracking means no more mystery changes
- Better Security: Credentials are never stored in plain text
- Easier Maintenance: Adding or removing dotfiles is a one-line change
- Portability: Works the same across macOS and Linux systems
Conclusion
Moving from configuration management tools to Terraform for local environment setup has been a revelation. The approach is minimal, secure, and remarkably effective for maintaining consistent environments across multiple machines.
The key insight was realizing that complex configuration management tools are often overkill for personal development environments. Terraform's declarative approach and state management provide exactly what's needed without the overhead.
Sharpening your knife is important to do your work. Cleaning your room is essential to improve your productivity. However, sharpening your knife or cleaning your room themselves are never the end goal of programming. As I moved to a simpler devops approach, I find more focus time on writign actual softwares, which is fun.