Panfactum LogoPanfactum
Infrastructure-as-CodeDeploying Modules

Deploying Modules

Prerequisites

This guide assumes:

  • You have prior experience working with infrastructure tools such as OpenTofu (Terraform) and Terragrunt. If you are not, please review the concept docs before proceeding.

  • You have completed the necessary repository setup guide.

Using Terragrunt to Deploy Modules

In the Panfactum stack, we use Terragrunt to configure and deploy OpenTofu / Terraform modules.

The configuration-as-code for each deployed module is stored in your repository's environments folder. 1 Terragrunt uses an extended version of the Hashicorp Configuration Language (HCL) to define each module's deployment configuration. If you are new to terragrunt, you should review the basics of the syntax here.

Terragrunt wraps tofu / terraform CLI and contains the same subcommands (e.g., apply, plan, etc.). Simply navigate to the module subdirectory in your environments folder and run the desired command (e.g., terragrunt apply).

While Terragrunt should have been properly configured when completing the bootstrapping guide, you can review the standard Panfactum configuration rules here.

Local Deployments

When working with modules locally, it can be helpful to override your organization's values in the standard terragrunt configuration files. For example, you may want to use a different provider configuration to pull in your specific AWS profile, or you may want to test a specific module version.

Each of the standard files has .user.yaml counterparts that will override the normal file values:

  • global.yaml: global.user.yaml
  • environment.yaml: environment.user.yaml
  • region.yaml: region.user.yaml
  • module.yaml: module.user.yaml

These files are automatically ignored by version control.

Defining a Module for Deployment

When defining infrastructure modules for deployment, each module will receive its own folder. You will add a terragrunt.hcl file to each folder which will contain your deployment configuration.

All the standard Terragrunt syntax is available to you when using the Panfactum stack.

However, to take advantage of the Panfactum functionality, your terragrunt.hcl must at minimum have the following include block:

include "panfactum" {
  path = find_in_parent_folders("panfactum.hcl")
  expose = true
}

You will also want to include a terraform source such as the following:

# Even though this is "terraform", it can source either Terraform or OpenTofu modules
terraform {
  source = "github.com/owner/repo.git//some-module"
}

Finally, you will want an inputs field to define the provided inputs to the sourced infrastructure module:

inputs = {
  foo = "bar"
}

A simple, but complete example might look something like this:

include "panfactum" {
  path = find_in_parent_folders("panfactum.hcl")
  expose = true
}

terraform {
   source = "github.com/owner/repo.git//some-module"
}

inputs = {
  foo = "bar"
}

Versioning

The Terragrunt configuration values on the primary integration branch (set by the repo_primary_branch repo variable) are the values that will be deployed by CI / CD pipelines. As a result, the source-of-truth for what is running every environment is consolidated in this branch's environments folder.

However, this is only the configuration-as-code. The infrastructure modules (i.e., infrastructure-as-code) may be pinned to specific versions via the terraform.source field.

We provide two terragrunt variables for setting the module version:

  • pf_stack_version: the Panfactum module version to use; used by include.panfactum.locals.pf_stack_source. (See below)

  • version: the first-party module version to use; used by include.panfactum.locals.source.

Deploying Panfactum Modules

Example

If you are deploying a Panfactum module, use include.panfactum.locals.pf_stack_source to dynamically construct the terraform.source. This adds version management, caching, and other convenience functionality. For example:

terraform {
  source = include.panfactum.locals.pf_stack_source
}

We use the following terragrunt variables to construct include.panfactum.locals.pf_stack_source:

  • pf_stack_version: Check here for the full list of available releases. This should be the same version as used for the Panfactum devShell. We recommend setting this in your environment.yaml so that functionality remains consistent across an entire environment.

  • module: The name of the module to deploy. Defaults to the directory name if not provided. Should be set in the module.yaml.

Setting up Providers

Before you deploy a module, you must setup your OpenTofu (Terraform) providers.

We provide ready-made configuration for many providers (see the full list here).

We also provide a convenience command, pf-tf-init, that will automatically inspect the sourced infrastructure module and add the correct providers and provider hashes to your .terraform.lock.hcl files. 2

Some providers require extra configuration variables. To view each provider's required configuration variables, see the reference docs.

Generally, you will want to configure a provider on an environment-wide or region-wide basis (i.e., in the environment.yaml or region.yaml files).

Using Secrets

Example

Secrets are encrypted and stored directly inside the repository just like any other configuration setting. The Panfactum stack uses sops for ensuring this is done securely and in a manner that integrates with terragrunt. See our guide for setting up sops.

Assuming you have an encrypted file called secrets.yaml adjacent to your terragrunt.hcl, you can access its values using the following syntax: 3

locals {
  secrets = yamldecode(sops_decrypt_file("${get_terragrunt_dir()}/secrets.yaml"))
}

inputs = {
  secret_input = local.secrets.my_secret
}

Defining the Dependency Graph

Example

Typically, there will be an order in which your infrastructure modules must be deployed and updated. For example, you will need to launch your Kubernetes cluster before you can deploy Helm charts to it.

You should explicitly declare these dependencies via terragrunt dependency blocks (docs).

This will ensure that modules are always updated in the desired order. Additionally, you can use this functionality to pass the outputs of one module into the inputs of another.

Resource Tagging

All Panfactum modules deploy resources with standard tags / labels..

You can add arbitrary additional tags by setting extra_tags in any of the following files:

  • global.yaml
  • environment.yaml
  • region.yaml
  • module.yaml

extra_tags is an object where the keys and values must both be strings.

The defined tags will be applied to all modules in the specified scope. For example, if extra_tags is defined in environment.yaml, all resources in that environment will receive the tags.

Common Inputs

Many Panfactum modules share the same input variables so that particular behaviors can be enabled / disabled across many modules at once.

Shared inputs to groups of modules can be supplied by setting extra_inputs in any of the following files:

  • global.yaml
  • environment.yaml
  • region.yaml
  • module.yaml

For a list of all the common input variables see this reference list.

Using a Local Copy of Panfactum Modules

You may want to make modifications to Panfactum modules found in the Stack repository and deploy them to your infrastructure.

You can easily do this for any set of modules by manipulating a few Terragrunt variables:

  1. Clone the Panfactum Stack repository to your local machine (git clone https://github.com/Panfactum/stack.git).

  2. Set pf_stack_version to local.

  3. Set pf_stack_local_path to an absolute path to the local copy of the Panfactum Stack repository you cloned in step 1.

Now when you next run terragrunt apply, the local module code will be used. This applies to both Panfactum direct modules and submodules. 4

Footnotes

  1. This folder lives in your stack repository, is defined by the repo variable environments_dir, and was created when running pf-update-terragrunt. For more information, see our repository setup guide

  2. This uses terragrunt providers under the hood. terragrunt providers can be used to provide more granular information about the required providers such as their versions.

  3. Note that you must use the get_terragrunt_dir() function to resolve the filepath. This will ensure a stable location regardless of how terragrunt is run.

  4. For submodules, this works via the pf_module_source input provided to your modules by Terragrunt. See the first-party IaC development documentation for more information.