As a fun experiment using the Terraform Minecraft provider, I created a symmetrical pyramid entirely out of modules. The build uses a combination of local variables and structured iteration to layer solid cuboids upward into a clean, golden shape.

Beyond the visual result, the real takeaway here is how Terraform module composition and scoped iteration patterns can be used to manage complexity and assign clear responsibilities across layers of abstraction.

High-Level Design

The goal was to create a square-based pyramid that tapers as it rises, with each level formed from a solid cuboid. The pyramid is symmetrical and built upward from a defined starting position. Each layer shrinks inward by two blocks on both the X and Z axes, eventually culminating in a 2×2 apex. While the end product is visual and fun, the implementation is focused on clarity, reusability, and proper scoping of logic across modules.

Alt Diamond Block instantiation of the module

Module Interface

The outer pyramid module takes in just three inputs:

variable "material" {
  type = string
}

variable "start_position" {
  type = object({
    x = number
    y = number
    z = number
  })
}

variable "length" {
  type = number
}

These inputs define what block type to use (material), where the structure starts (start_position), and how wide the base should be (length). The pyramid is always square in plan, so this single length drives both width and depth.

module "pyramid" {
  source = "./modules/solid-pyramid"

  material = "minecraft:gold_block"

  start_position = {
    x = -1650,
    y = 62,
    z = -1100
  }
  length = 20

}

This makes it super simply to construct a huge pyramid in Minecraft with just a material, starting position, and a size.

Layer Calculation with Locals

The real work of shaping the pyramid happens in a locals block, where I calculate each horizontal layer of the pyramid. This approach decouples the geometry logic from the actual block placement, letting Terraform iterate over cleanly defined values:

locals {
  layers = flatten([
    for i in range(0, 100) : [
      {
        offset_x = i
        offset_z = i
        length   = var.length - 2 * i
        width    = var.length - 2 * i
        y        = var.start_position.y + i
      }
    ]
    if var.length - 2 * i >= 2 && var.length - 2 * i >= 2
  ])
}

Each loop iteration defines a single layer:

  • offset_x and offset_z shift the layer inward as it shrinks.
  • length and width define the layer’s footprint, shrinking by 2 blocks per level.
  • y increments upward to stack layers vertically.

The if guard ensures that iteration stops before the dimensions drop below 2×2, at which point the pyramid’s apex is formed.

Delegating to the Cuboid Module

Once the layers are defined, I delegate to a cuboid module for each layer:

module "pyramid_layers" {
  for_each = {
    for i, layer in local.layers : "layer_${i}" => layer
  }

  source   = "../cuboid"
  material = var.material

  start_position = {
    x = var.start_position.x + each.value.offset_x
    y = each.value.y
    z = var.start_position.z + each.value.offset_z
  }

  width  = each.value.length
  length = 1
  depth  = each.value.width
}

This is where the principle of scoping iteration outside the module comes into play. Rather than having the cuboidmodule calculate its own offsets or manage multiple layers, the outer pyramid module iterates explicitly and feeds each layer to a distinct instance of the cuboid module.

This pattern keeps modules single-purpose. Each module only needs to know about its own concern:

  • The pyramid module handles geometric logic and stacking.
  • The cuboid module builds a solid rectangular prism.
  • Inside the cuboid, I use further module composition to build individual pillar blocks, and those pillar modules themselves perform their own internal iteration to stack Minecraft blocks along the Y-axis.

The result is a clean chain of delegation, where each module has a clear responsibility and works together in tandem.

The Math Behind the Layers

The pyramid’s geometry is derived from a simple arithmetic progression. Each new layer of the pyramid is offset by one block inward along both the X and Z axes and rises by one block along the Y-axis. At every step, the width and depth of the layer shrink by 2 — one from each side — until the base has narrowed to a minimum viable platform of 2×2.

This creates a predictable pattern where the total number of layers is roughly:

total_layers = floor((length - 2) / 2) + 1

For example, with a base length of 10:

  • Layer 0: 8×8
  • Layer 1: 6×6
  • Layer 2: 4×4
  • Layer 3: 2×2 (apex)

This gives us 5 total layers. The logic ensures a clean taper, and the +1 accounts for the final 2×2 apex layer.

This mathematical relationship is directly encoded in the locals block used to generate the layer data.

Alt

The use of flatten() and range(0, 100) is a practical way to generate candidate layers and then filter out the ones that no longer meet the minimum size requirement. Since the iteration halts at the 2×2 apex, this makes the process deterministic and robust for a wide range of input sizes.

Conclusion

Using the Terraform Minecraft provider, I was able to construct a pyramid in the game world with a single block of code. With local variables to drive intermediate calculations and strict boundaries between module responsibilities, the build is both visually and structurally elegant. And since everything is modular, it’s easy to tweak: change the material, shift the base, adjust the height — and the rest just works.

This was a fun project, but also a valuable exercise in thinking modularly with Terraform — whether you’re deploying infrastructure or building golden pyramids in Minecraft.

Alt