Skip to content

Terraform Infrastructure

Terraform is an open-source infrastructure as code software tool that enables you to safely and predictably create, change, and improve infrastructure.

The Terraform infrastructure generator creates a Terraform infrastructure project. The generated application includes security best practices through Checkov security checks.

You can generate a new Terraform project in two ways:

Run this generator@aws/nx-plugin:terraform#project

pnpm nx g @aws/nx-plugin:terraform#project
Build your command5

Required

Generator Options5 options
nameRequiredstring

The name of the project.

typeenumDefault: application

Whether this is a terraform lib (re-usable modules) or app (deployable).

applicationlibrary
directorystringDefault: packages

The directory of the new project.

subDirectorystring

The sub directory the project is placed in. By default this is the project name.

preferInstallDependenciesbooleanDefault: true

Whether to prefer installing dependencies after the generator runs. Set to false to defer installing when batching multiple generators (an install still runs if needed so subsequent generators can compute the Nx project graph); install once at the end.

The generator creates different file structures depending on the project type:

type = application

For application projects (--type=application), the generator creates a complete Terraform application with remote state management:

  • Directorysrc
    • main.tf Main Terraform configuration file
    • providers.tf Provider configuration with S3 backend
    • variables.tf Input variable definitions
    • outputs.tf Output value definitions
    • Directoryenv Environment-specific variable files
      • dev.tfvars Development environment variables
  • Directorybootstrap Bootstrap configuration for remote state
    • main.tf S3 bucket and policies for state storage
    • providers.tf AWS provider configuration
    • variables.tf Bootstrap variable definitions
  • Directoryscripts Node helpers run by the nx bootstrap, bootstrap-destroy and init targets
    • aws-config.ts Resolves account + region via the AWS SDK credential chain
    • bootstrap.ts Pulls/pushes the bootstrap tfstate and runs terraform apply
    • bootstrap-destroy.ts Empties the state bucket and runs terraform destroy
    • init.ts Runs terraform init with the S3 backend config
    • env.ts Points terraform init at the shared provider cache
  • checkov.yml Checkov configuration, including the checks to skip
  • project.json Project configuration and build targets
type = library

For library projects (--type=library), the generator creates a simpler structure for reusable Terraform modules:

  • Directorysrc
    • main.tf Main Terraform module file
  • checkov.yml Checkov configuration, including the checks to skip
  • project.json Project configuration and build targets

Implementing your Terraform Infrastructure

Section titled “Implementing your Terraform Infrastructure”

You can start writing your Terraform infrastructure inside src/main.tf, for example:

src/main.tf
locals {
account_id = data.aws_caller_identity.current.account_id
aws_region = data.aws_region.current.id
}
resource "null_resource" "print_info" {
# triggers = {
# always_run = timestamp()
# }
provisioner "local-exec" {
command = "echo 'AWS Region: ${local.aws_region}, AWS Account: ${local.account_id}, Environment: ${var.environment}'"
}
}
# Declare your infrastructure here
resource "aws_s3_bucket" "my_bucket" {
bucket = "my-unique-bucket-name"
}

Note that the S3 bucket above would fail the Checkov security scan, which checks that the bucket has the appropriate security settings enabled.

If you wanted to execute a module from a separate project (lib), you could do so as follows:

module "lib_module" {
source = "../../path/to/my-lib/src"
}

This will automatically update the Nx graph to add a dependency between your consuming application and your lib.

Configure environment-specific variables in the src/env/*.tfvars files.

To add new environments, create a new src/env/<environment>.tfvars file with the environment-specific variables and add new entries for apply, destroy, init, plan in the project.json for the new env configuration. For example, let’s assume we want to add a prod env:

# Production environment variables
environment = "prod"
aws_region = "us-west-2"
type = application

Remote State Bootstrap (Application Projects Only)

Section titled “Remote State Bootstrap (Application Projects Only)”

Before deploying your infrastructure, you’ll need to bootstrap the remote state backend. This creates an S3 bucket to store your Terraform state files:

Terminal window
pnpm nx bootstrap tf-infra

The available targets depend on your project type:

Common Targets (Both Application and Library)

Section titled “Common Targets (Both Application and Library)”

You can validate your Terraform configuration using the validate target:

Terminal window
pnpm nx validate tf-infra

Terraform projects use terraform fmt to check formatting.

To invoke the linter to check your project, you can run the lint target.

Terminal window
pnpm nx lint tf-infra

The majority of linting or formatting issues can be fixed automatically by running with the --configuration=fix argument.

Terminal window
pnpm nx lint tf-infra --configuration=fix

Similarly if you would like to fix all lint issues in all packages in your workspace, you can run:

Terminal window
pnpm nx run-many --target lint --all --configuration=fix

To avoid linting issues slowing you down during development (particularly if you have non auto-fixable issues in your project), you can run a build with the skip-lint configuration:

Terminal window
pnpm nx run-many --target build --configuration=skip-lint

This skips the format check entirely during build.

Run security checks on your infrastructure using Checkov with the checkov target:

Terminal window
pnpm nx checkov tf-infra

You will find your security test results in the root dist folder, under dist/packages/<my-terraform-project>/checkov.

Checkov runs as part of build.

Checks are configured in the project’s checkov.yml. Add a check id to skip-check to suppress it across the whole project:

checkov.yml
skip-check:
- CKV_AWS_115 # Concurrent execution limit
- CKV_AWS_116 # Dead Letter Queue

To suppress a check for a single resource instead, add a #checkov:skip=<id>:<reason> comment inside the resource block:

resource "aws_s3_bucket" "example" {
#checkov:skip=CKV_AWS_18:Access logging not required for this bucket
bucket = "example"
}

The test target runs Terraform’s native test framework over any .tftest.hcl files in your project:

Terminal window
pnpm nx test tf-infra

A project with no test files is a no-op success, so you can add tests when you need them. build runs this target, so your tests run as part of a normal build.

Each run block evaluates your configuration. Use command = plan to check what Terraform would do (this expands the whole module graph, so it catches plan-time errors that validate cannot), or command = apply to create real resources and assert on their outputs. Declaring mock_provider means no API calls are made and no AWS credentials are needed, which keeps plan tests fast and safe to run in CI:

src/main.tftest.hcl
mock_provider "aws" {
mock_data "aws_caller_identity" {
defaults = { account_id = "123456789012" }
}
mock_data "aws_region" {
defaults = { region = "us-east-1" }
}
}
variables {
aws_region = "us-east-1"
environment = "dev"
}
run "plan_is_valid" {
command = plan
assert {
condition = data.aws_caller_identity.current.account_id == "123456789012"
error_message = "Unexpected account id"
}
}

Set every variable your configuration requires in the variables block, otherwise the run fails with “has a required variable … with no set value”.

Every target that runs terraform init reuses a provider cache under .terraform/plugin-cache in your workspace root, so providers are downloaded once rather than on every run. Each project gets its own directory there: two terraform init runs filling one cache at the same time can each compute a different hash for the same provider, which terraform then rejects against your .terraform.lock.hcl. See the Terraform documentation for more information.

Set TF_PLUGIN_CACHE_DIR in your environment to point the vended init script at a cache you manage yourself — a volume shared between workspaces, say. Note that the test target reads its path from project.json, so change it there too.

type = application

The following targets are only available for application type projects:

Before applying changes, you can see what Terraform will do by running the plan target:

Terminal window
pnpm nx plan tf-infra

This will create a plan file in dist/packages/<my-terraform-project>/terraform/dev.tfplan.

plan depends on assemble, so it produces the artifacts your modules reference, such as the Lambda bundles and generated operations metadata, without running the lint, test and type-check gates.

Initialize your Terraform working directory with the init target:

Terminal window
pnpm nx run tf-infra:init

After planning, you can deploy your infrastructure to AWS using the apply target:

Terminal window
pnpm nx apply tf-infra

Retrieve output values from your Terraform configuration:

Terminal window
pnpm nx output tf-infra

When you need to tear down your infrastructure, use the destroy target:

Terminal window
pnpm nx destroy tf-infra

To clean up the bootstrap resources (S3 bucket for state storage):

Terminal window
pnpm nx bootstrap-destroy tf-infra

This empties the state bucket before destroying it, and resolves the region from the AWS SDK credential chain, so it runs unattended in CI.

For more information about Terraform, please refer to the Terraform Documentation and AWS Provider Documentation.