This article demonstrates how to use Terraform to orchestrate dependencies across independent infrastructure components: storage, databases, and compute.
By focusing on a multi-provider workflow, we will examine how Terraform constructs an execution graph to ensure resources are provisioned in the correct sequence, while noting how to handle out-of-band application logic.
The Architecture of the App
To understand the data flow, our application architecture connects three distinct systems across the network:
TrueNAS serves as the centralized storage backend, hosting raw assets (such as product images) within a dedicated dataset.
MariaDB serves as the relational database, storing structured product metadata (Product ID, Name, Description) along with relative URIs pointing to the assets.
Proxmox VE hosts the Ubuntu virtual machine running our web application.
Instead of relying on local synchronization scripts, the web application inside the Proxmox VM communicates directly with both backends over the local network. When a user requests a product page, the VM queries MariaDB for the text metadata, reads the corresponding asset from the TrueNAS storage mount, and renders the complete page.
Terraform acts as the declarative translator for this entire pipeline. Using three separate provider plugins, a single execution plan provisions the storage dataset, creates the logical database, applies security grants, and deploys the compute instance with the correct network configurations.
Reference Providers
- TrueNAS: baladithyab/truenas
- MariaDB/MySQL: petoju/mysql
- Proxmox VE: Telmate/proxmox
The Order of Creation: Handling Dependencies
When orchestrating resources across completely different hardware appliances, lifecycle management is critical. The web application VM cannot successfully initialize if its dependent database and storage infrastructure do not yet exist.
Terraform resolves this sequence by building a directed acyclic graph (DAG) of your resources. The step-by-step execution flows as follows:
Step 1:
TrueNAS Dataset Provisioning
The TrueNAS provider contacts the storage appliance API to verify or create the target dataset.
Architectural Note: Creating the dataset only provisions the underlying ZFS storage. Exporting that dataset via an NFS share and registering it as a storage pool within Proxmox’s Datacenter configuration (storage.cfg) are infrastructure prerequisites that must exist beforehand or be managed outside this specific compute lifecycle.
Step 2:
MariaDB Database and User Initialization
Simultaneously or directly following storage validation, the MySQL provider connects to the MariaDB server. It creates the logical database (inventory_prod), instantiates the application user, and applies network-specific access privileges.
(Note: The administrative provider handles infrastructure provisioning only; application schemas and tables are intentionally left to the application’s internal runtime migration tool or a bootstrap script during the VM’s first boot.)
Step 3:
Proxmox VM Deployment
Once the storage targets and database access controls are created, the Proxmox provider deploys the virtual machine from an existing template, allocates its virtual disk within the pre-configured storage pool, and leverages Cloud-Init to provision the network interfaces.
Representing the Order in Code
Terraform evaluates execution order using two primary patterns:
Implicit Dependencies: Occur automatically when a parameter of one resource directly references an export attribute of another (e.g., interpolating a database name output directly into a VM configuration string). This is Terraform’s preferred approach.
Explicit Dependencies (
depends_on): Used when resources depend on a creation state that cannot be inferred via data interpolation. In this example, the VM consumes infrastructure details buried inside external Cloud-Init files that sit outside Terraform’s visibility. Because Terraform cannot infer this relationship from resource attributes alone, an explicitdepends_onis used to serialize execution.
Warning: Explicit dependencies ensure Terraform waits for an API creation success confirmation before moving to the next resource, but they do not guarantee application-level or network-level readiness (such as database replication lag or service startup time).
Configuration Blueprint: main.tf
# Note: Provider versions and IP configurations are for illustrative purposes.
# Verify current provider documentation and local network spacing prior to initialization.
terraform {
required_providers {
truenas = {
source = "baladithyab/truenas"
version = "~> 0.1"
}
mysql = {
source = "petoju/mysql"
version = "~> 3.0"
}
proxmox = {
source = "Telmate/proxmox"
version = "~> 2.9"
}
}
}
# -------------------------------------------------------------------------
# 1. TRUENAS STORAGE CONFIGURATION (Step 1)
# -------------------------------------------------------------------------
provider "truenas" {
api_url = "https://192.168.1.40/api/v2.0"
api_key = "your-truenas-api-key"
}
# Create the backend dataset for product assets
resource "truenas_dataset" "app_storage" {
pool = "tank"
name = "app-images"
}
# -------------------------------------------------------------------------
# 2. MARIADB DATABASE CONFIGURATION (Step 2)
# -------------------------------------------------------------------------
provider "mysql" {
endpoint = "192.168.1.50:3306"
username = "root"
password = "your-mariadb-root-password"
}
# Create the logical database
resource "mysql_database" "product_db" {
name = "inventory_prod"
}
# Create the application service account
resource "mysql_user" "app_user" {
user = "webapp_user"
host = "%"
plaintext_password = "secureapppassword123"
}
# Grant network permissions to the service account for this database
resource "mysql_grant" "app_grant" {
user = mysql_user.app_user.user
host = mysql_user.app_user.host
database = mysql_database.product_db.name
privileges = ["SELECT", "INSERT", "UPDATE"]
}
# -------------------------------------------------------------------------
# 3. PROXMOX COMPUTE CONFIGURATION (Step 3)
# -------------------------------------------------------------------------
provider "proxmox" {
pm_api_url = "https://192.168.1.30:8006/api2/json"
pm_user = "root@pam"
pm_password = "your-proxmox-password"
pm_tls_insecure = true
}
resource "proxmox_vm_qemu" "web_app_vm" {
name = "production-web-app"
target_node = "pve-node1"
clone = "ubuntu-2404-template"
cores = 2
memory = 2048
# Targets a pre-existing storage pool in PVE mapped to the TrueNAS share
disk {
type = "scsi"
storage = "truenas-nfs-mount"
size = "20G"
}
# VM Initialization via Cloud-Init
os_type = "cloud-init"
cicustom = "user=local:snippets/user-data.yaml"
ipconfig0 = "ip=192.168.1.100/24,gw=192.168.1.1"
# Explicit Dependency: Prevents the VM from provisioning until
# the TrueNAS dataset and MariaDB access rights are created.
depends_on = [
truenas_dataset.app_storage,
mysql_grant.app_grant
]
}