A cutting-edge, production-grade infrastructure management framework (or, just a reference architecture for infrrastructure-at-scale, with Terragrunt) designed to be modular, flexible, and scalable. It's very opinionated, and it came from years of experience building infrastructure at scale. Use it, adapt it to your needs, and make it your own.
Feature | Description |
---|---|
π§© Modular Architecture | Discrete, composable infrastructure units, that can inherit from shared unit's configurations, and from their parents in the hierarchy (stacks, or environments) |
π Highly Hierarchical Flexible Overrides | Designed to support multiple environments (the most common abstraction), where each environment can hold many stacks, and each stack can hold many units |
π Multi-Provider Compatibility | Support for diverse cloud and infrastructure providers. Dynamically set providers, versions and overrides. It passes the control of the providers, and versions (if applicable) to the units, which are the smallest components of the architecture that deals directly with the terraform abstractions (modules). |
π§ Dynamic Environment Configuration | Utilizes .env files for straightforward environment variable management. Settings are typically defined in a .env file (copied from .env.example ), which is automatically loaded by just recipes. See Environment Variables Documentation for full details. |
π§Ό Clean Code Configuration | Strict separation of configuration logic, with clear distinctions between global settings, provider management, and Terragrunt generation rules in config.hcl and root.hcl . Implements comprehensive commenting and modular configuration design. |
graph TD
subgraph "Root Configuration"
A[Root Configuration]
end
subgraph "Shared Layer"
SharedConfig[Shared Configuration /_shared/_config]
SharedUnits[Shared Units /_shared/_units]
end
subgraph "Infrastructure Hierarchy"
Environment[Environment]
Stack[Stack]
Unit[Unit]
end
A -->|Inherit common configuration, metadata, etc. | SharedConfig
A -->|Provides Base Templates, and shared units configuration| SharedUnits
A -->|References an| Environment
SharedConfig -->|Provides Configuration specific to the stack| Stack
SharedConfig -->|Configures and resolve automatically common settings| Unit
SharedUnits -->|Automatically includes, and injects environment variables, or configuration| Environment
SharedUnits -->|Includes stack-specific configuration| Stack
SharedUnits -->|Minimal configuration at the unit level, most of it is defined in their parents units| Unit
Environment -->|Organizes| Stack
Stack -->|Contains| Unit
classDef rootConfig fill:#f96,stroke:#333,stroke-width:2px;
classDef sharedComponent fill:#6f9,stroke:#333,stroke-width:2px;
classDef infrastructureHierarchy fill:#69f,stroke:#333,stroke-width:2px;
class A rootConfig;
class SharedConfig,SharedUnits sharedComponent;
class Environment,Stack,Unit infrastructureHierarchy;
The project structure follows the pattern:
- Environment: A collection of stacks, and units. The most logical approach to organise the infrastructure is to group the infrastructure by environment.
- Stack: A collection of units.
- Unit: A collection of terraform modules.
- Stack: A collection of units.
Note
Each layer is fully flexible, and can be changed to fit the needs of your project, or particular domain. E.g.: instead of environment, you could use a different layer to group the infrastructure by region, or by application.
The project structure is as follows:
infra/terragrunt
βββ README.md
βββ _shared
β βββ _config
β β βββ README.md
β β βββ app.hcl
β β βββ remote_state.hcl
β β βββ tags.hcl
β βββ _units
β βββ README.md
β βββ <unit>.hcl
β βββ <unit-2>.hcl
βββ _templates
βββ config.hcl
βββ default.tfvars
βββ <environment>
β βββ default.tfvars
β βββ <stack>
β β βββ <unit>
β β β βββ README.md
β β β βββ terragrunt.hcl
β β β βββ unit_cfg_providers.hcl
β β β βββ unit_cfg_versions.hcl
β β βββ <unit-2>
β β β βββ README.md
β β β βββ terragrunt.hcl
β β β βββ unit_cfg_providers.hcl
β β β βββ unit_cfg_versions.hcl
β β βββ stack.hcl
β βββ env.hcl
βββ root.hcl
This project uses a straightforward environment variable management system based on .env
files:
.env.example
: A template file in the project root lists available environment variables and provides example values..env
: You create this file by copying.env.example
. It's where you set your actual, local-specific or sensitive variable values. This file should be added to.gitignore
.- Automatic Loading with
just
: Thejustfile
is configured withset dotenv-load
, so anyjust
command will automatically load variables from your.env
file.
This approach simplifies environment setup and customization. For a comprehensive guide and list of all supported variables, please refer to the Environment Variables Documentation.
To set up your development environment:
- Install Prerequisites: Ensure you have Terraform, Terragrunt, mise, and Just installed.
- Set up Environment Variables:
- Copy the
.env.example
file to.env
in the project root:cp .env.example .env
- Edit your
.env
file to set the appropriate values for your setup. Refer todocs/environment-variables.md
for details on available variables.
- Copy the
- Activate Development Environment:
- Run
just dev
. This command will usemise
to set up the shell with the correct tool versions defined inmise.toml
. Variables from your.env
file will also be available withinjust
recipes.
- Run
Environment variables are managed using .env
files, as detailed above and in the Environment Variables Documentation. This method allows for easy customization of settings like AWS regions, Terraform versions, and application-specific parameters.
The full list of supported environment variables, their descriptions, default values, and usage contexts can be found in the Environment Variables Documentation. Here are a few examples typically found in .env.example
:
Category | Variable Name | Example Value (in .env.example ) |
---|---|---|
Cloud Provider & Region | DEFAULT_REGION |
us-east-1 |
Terraform & Terragrunt | TG_NON_INTERACTIVE |
true |
TG_STACK_TF_VERSION |
1.11.3 |
|
Remote State Config | TG_STACK_REMOTE_STATE_BUCKET_NAME |
your-tf-state-bucket |
Note
Always refer to docs/environment-variables.md
for the most current and comprehensive list of environment variables.
- Install prerequisites (Terraform, Terragrunt, mise, Just).
- Copy
.env.example
to.env
and customize it. - Run
just dev
to activate the mise environment. Variables from.env
will be loaded forjust
commands.
For more detailed information, consult the Environment Variables Documentation.
Normally, using Terragrunt and depending on what type of Terraform modules your units are using, you might want to skip the generation, override if it was generated by terragrunt or just override these files from the terraform modules being used:
providers.tf
versions.tf
For that, this reference architecture support a very flexible approach to manage these scenarios, based on the following principle: the unit which interacts directly with the terraform (modules) interface is the one that defines the providers and versions (if applicable). At the unit level, a certain terraform module might require several providers, and versions, and at the same time, another terraform module might require a different set of providers and versions. This flexibility is achieved by using the unit_cfg_providers.hcl
and unit_cfg_versions.hcl
files, which are located in the unit directory.
At minimum, an unit in this architecture will have 4 files. Let's take a look at the following example:
infra/terragrunt/global/dni/dni_generator
βββ README.md
βββ terragrunt.hcl
βββ unit_cfg_providers.hcl
βββ unit_cfg_versions.hcl
If a given unit requires a specific provider, and version, it will be defined in the unit_cfg_providers.hcl
and unit_cfg_versions.hcl
files. From there, the credentials (if applicable) and the providers, and versions shape can be defined in a reliable, and secure way. See the unit_cfg_providers.hcl and unit_cfg_versions.hcl files for a more real-world example.
When the providers, and versions are defined, they are reliably read automatically by terragrunt (the config.hcl file) and used to generate the providers.tf
and versions.tf
files if the following conditions are met:
The providers.tf
file is generated dynamically by terragrunt, and it's generated based on the following conditions:
- The
unit_cfg_providers.hcl
file is present in the unit directory (e.g.:infra/terragrunt/<environment></environment>/<stack>/<unit>/unit_cfg_providers.hcl
) - The
unit_cfg_providers.hcl
file is not empty. - The
TG_STACK_FLAG_ENABLE_PROVIDERS_OVERRIDE
feature flag is set totrue
(default behavior)
The versions.tf
file is generated dynamically by terragrunt, and it's generated based on the following conditions:
- The
unit_cfg_versions.hcl
file is present in the unit directory (e.g.:infra/terragrunt/<environment></environment>/<stack>/<unit>/unit_cfg_versions.hcl
) - The
unit_cfg_versions.hcl
file is not empty. - The
TG_STACK_FLAG_ENABLE_VERSIONS_OVERRIDE
feature flag is set totrue
(default behavior)
This reference architecture uses AWS S3 and DynamoDB for secure, scalable remote state management. A properly configured remote backend provides state locking, versioning, encryption, and access control.
The architecture requires an S3 bucket and DynamoDB table for storing and locking Terraform state. See our AWS Remote Backend Setup Guide for detailed instructions on:
- Creating a secure S3 bucket with proper versioning and encryption
- Configuring a DynamoDB table for state locking
- Setting up appropriate security measures
- Configuring your environment to use the remote backend
Once configured, update your environment variables to reference your backend:
TG_STACK_REMOTE_STATE_BUCKET_NAME="your-state-bucket"
TG_STACK_REMOTE_STATE_LOCK_TABLE="your-lock-table"
Dive deep into our architecture with our detailed documentation:
-
Infrastructure Configuration Management
- Configuration strategies
- Environment variable management
- Best practices
-
- Reusable configuration modules
- Centralized resource management
- Standardization techniques
-
- Modular stack design
- Component interaction patterns
- Scalability considerations
-
- S3 state storage configuration
- DynamoDB state locking
- Security best practices
- Troubleshooting guidance
-
- Explains the included Dagger module for CI automation.
- Terragrunt
- Terraform
- mise (for managing tool versions and development shell)
- Just (for running project commands and loading
.env
files)
- Clone the repository.
- Install prerequisites listed above.
- Set up your local environment variables:
- Copy
.env.example
to.env
:cp .env.example .env
- Edit
.env
with your specific configurations. See Environment Variables Documentation for details.
- Copy
- Activate the development environment:
- Run
just dev
. This will usemise
to prepare your shell with the correct tool versions.
- Run
This reference architecture already includes a stack implementation that simulates a DNI generation (showing the most common case, where units are orchestrated in an specific order, with dependencies between them, etc.), a dni-generator
module that requires a age-generator
module, name-generator
module, and lastname-generator
module. See the terraform/modules directory for more details.
Run a Terragrunt command on a specific unit:
# Run a Terragrunt command on a specific unit:
just tg-run global dni dni_generator plan
# or just tg-run since these are the default values.
just tg-run
Or, in this particular scenario, you can run the entire stack, and leave Terragrunt to handle the dependencies between the units, ordering the execution of the units in the correct order, etc.:
# Running terragrunt run-all plan, in the environment 'global', and stack 'dni'
just tg-run-all-plan global dni
Tip
More recipes are available in the justfile file.
- Clone the repository
- Install prerequisites (Terraform, Terragrunt, mise, Just)
- Set up your local environment variables:
- Copy
.env.example
to.env
:cp .env.example .env
- Edit
.env
with your specific configurations. See Environment Variables Documentation for details.
- Copy
- Activate the development environment:
- Run
just dev
. This will usemise
to prepare your shell with the correct tool versions.
- Run
- Review documentation and customize configurations
This reference architecture includes a Dagger module written in Go, located in pipeline/infra/
, to automate Terragrunt and Terraform workflows, providing consistent and reproducible environments for CI pipelines and local development.
- GitLab CI Integration: The module is primarily used within the GitLab CI/CD pipelines defined in the
.gitlab/
directory. See the GitLab CI/CD Configuration Guide for details on its structure and how it powers the CI jobs. - Local Execution: The
justfile
provides convenient recipes (e.g.,just ci-job-units-static-check
) for running Dagger CI-like jobs locally. You can also invoke Dagger functions directly from thepipeline/infra/
directory using the Dagger CLI for more granular control or debugging (e.g.,dagger call open-terminal --src ../../ up --stdout
). - Module Details: For a detailed explanation of the Dagger module's functions, how it handles tool versions, environment variables (including
.env
files), and authentication, refer to the Dagger Integration section in the GitLab CI/CD Configuration Guide.
Contributions are welcome! Please follow our guidelines:
For questions, support, or collaboration: