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.
Generate a Terraform Project
Section titled “Generate a Terraform Project”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 yarn nx g @aws/nx-plugin:terraform#project npx nx g @aws/nx-plugin:terraform#project bunx nx g @aws/nx-plugin:terraform#project- Install the Nx Console VSCode Plugin if you haven't already
- Open the Nx Console in VSCode
- Click
Generate (UI)in the "Common Nx Commands" section - Search for
@aws/nx-plugin - terraform#project - Fill in the required parameters
- Click
Generate
Build your command5
Required
Options
Section titled “Options”nameRequiredstringThe name of the project.
typeenumDefault:applicationWhether this is a terraform lib (re-usable modules) or app (deployable).
applicationlibrarydirectorystringDefault:packagesThe directory of the new project.
subDirectorystringThe sub directory the project is placed in. By default this is the project name.
preferInstallDependenciesbooleanDefault:trueWhether 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.
Generator Output
Section titled “Generator Output”The generator creates different file structures depending on the project type:
Application Type
Section titled “Application Type”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-destroyandinittargets- 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 initwith the S3 backend config - env.ts Points
terraform initat the shared provider cache
- checkov.yml Checkov configuration, including the checks to skip
- project.json Project configuration and build targets
Library Type
Section titled “Library Type”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:
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 hereresource "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.
Cross project dependencies
Section titled “Cross project dependencies”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.
Environment Configuration
Section titled “Environment Configuration”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 variablesenvironment = "prod"aws_region = "us-west-2"{ "targets": { "apply": { "executor": "nx:run-commands", "defaultConfiguration": "dev", "configurations": { "dev": { "command": "terraform apply ../../../dist/packages/infra/terraform/dev.tfplan" }, "prod": { "command": "terraform apply ../../../dist/packages/infra/terraform/prod.tfplan" } }, "options": { "forwardAllArgs": true, "cwd": "{projectRoot}/src" }, "dependsOn": ["plan"] }, "destroy": { "executor": "nx:run-commands", "defaultConfiguration": "dev", "configurations": { "dev": { "command": "terraform destroy -var-file=env/dev.tfvars" }, "prod": { "command": "terraform destroy -var-file=env/prod.tfvars" } }, "options": { "forwardAllArgs": true, "cwd":"{projectRoot}/src" }, "dependsOn": ["init"] }, "init": { "executor": "nx:run-commands", "defaultConfiguration": "dev", "configurations": { "dev": { "env": { "TF_ENV": "dev" } }, "prod": { "env": { "TF_ENV": "prod" } } }, "options": { "forwardAllArgs": true, "commands": ["tsx {projectRoot}/scripts/init.ts {projectRoot}"], "cwd": "{workspaceRoot}" } }, "plan": { "executor": "nx:run-commands", "defaultConfiguration": "dev", "configurations": { "dev": { "command": "terraform plan -var-file=env/dev.tfvars -out=../../../dist/packages/infra/terraform/dev.tfplan" }, "prod": { "command": "terraform plan -var-file=env/prod.tfvars -out=../../../dist/packages/infra/terraform/prod.tfplan" } }, "options": { "forwardAllArgs": true, "cwd": "{projectRoot}/src" }, "dependsOn": ["init"] } }}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:
pnpm nx bootstrap tf-infrayarn nx bootstrap tf-infranpx nx bootstrap tf-infrabunx nx bootstrap tf-infraAvailable Targets
Section titled “Available Targets”The available targets depend on your project type:
Common Targets (Both Application and Library)
Section titled “Common Targets (Both Application and Library)”Validating your Infrastructure
Section titled “Validating your Infrastructure”You can validate your Terraform configuration using the validate target:
pnpm nx validate tf-infrayarn nx validate tf-infranpx nx validate tf-infrabunx nx validate tf-infraLinting
Section titled “Linting”Terraform projects use terraform fmt to check formatting.
Running the Linter
Section titled “Running the Linter”To invoke the linter to check your project, you can run the lint target.
pnpm nx lint tf-infrayarn nx lint tf-infranpx nx lint tf-infrabunx nx lint tf-infraFixing Lint Issues
Section titled “Fixing Lint Issues”The majority of linting or formatting issues can be fixed automatically by running with the --configuration=fix argument.
pnpm nx lint tf-infra --configuration=fixyarn nx lint tf-infra --configuration=fixnpx nx lint tf-infra --configuration=fixbunx nx lint tf-infra --configuration=fixSimilarly if you would like to fix all lint issues in all packages in your workspace, you can run:
pnpm nx run-many --target lint --all --configuration=fixyarn nx run-many --target lint --all --configuration=fixnpx nx run-many --target lint --all --configuration=fixbunx nx run-many --target lint --all --configuration=fixSkipping Lint Issues
Section titled “Skipping Lint Issues”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:
pnpm nx run-many --target build --configuration=skip-lintyarn nx run-many --target build --configuration=skip-lintnpx nx run-many --target build --configuration=skip-lintbunx nx run-many --target build --configuration=skip-lintThis skips the format check entirely during build.
Security Testing
Section titled “Security Testing”Run security checks on your infrastructure using Checkov with the checkov target:
pnpm nx checkov tf-infrayarn nx checkov tf-infranpx nx checkov tf-infrabunx nx checkov tf-infraYou 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:
skip-check: - CKV_AWS_115 # Concurrent execution limit - CKV_AWS_116 # Dead Letter QueueTo 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"}Running Terraform Tests
Section titled “Running Terraform Tests”The test target runs Terraform’s native test framework over any .tftest.hcl files in your project:
pnpm nx test tf-infrayarn nx test tf-infranpx nx test tf-infrabunx nx test tf-infraA 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:
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.
Application-Only Targets
Section titled “Application-Only Targets”The following targets are only available for application type projects:
Planning your Infrastructure
Section titled “Planning your Infrastructure”Before applying changes, you can see what Terraform will do by running the plan target:
pnpm nx plan tf-infrayarn nx plan tf-infranpx nx plan tf-infrabunx nx plan tf-infraThis 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.
Initializing Terraform
Section titled “Initializing Terraform”Initialize your Terraform working directory with the init target:
pnpm nx run tf-infra:inityarn nx run tf-infra:initnpx nx run tf-infra:initbunx nx run tf-infra:initDeploying to AWS
Section titled “Deploying to AWS”After planning, you can deploy your infrastructure to AWS using the apply target:
pnpm nx apply tf-infrayarn nx apply tf-infranpx nx apply tf-infrabunx nx apply tf-infraGetting Outputs
Section titled “Getting Outputs”Retrieve output values from your Terraform configuration:
pnpm nx output tf-infrayarn nx output tf-infranpx nx output tf-infrabunx nx output tf-infraDestroying Infrastructure
Section titled “Destroying Infrastructure”When you need to tear down your infrastructure, use the destroy target:
pnpm nx destroy tf-infrayarn nx destroy tf-infranpx nx destroy tf-infrabunx nx destroy tf-infraDestroying Bootstrap Resources
Section titled “Destroying Bootstrap Resources”To clean up the bootstrap resources (S3 bucket for state storage):
pnpm nx bootstrap-destroy tf-infrayarn nx bootstrap-destroy tf-infranpx nx bootstrap-destroy tf-infrabunx nx bootstrap-destroy tf-infraThis empties the state bucket before destroying it, and resolves the region from the AWS SDK credential chain, so it runs unattended in CI.
More Information
Section titled “More Information”For more information about Terraform, please refer to the Terraform Documentation and AWS Provider Documentation.