FusionAuth And Terraform

Overview

In this section you’ll learn about the open source FusionAuth Terraform provider, with which you will be able to manage FusionAuth using Terraform, an open source infrastructure as code automation tool.

Since it began as a community project, the provider is not as complete as the client libraries, but it covers the majority of use cases. If you find yourself needing to manage an unsupported resource or configuration, please submit a PR to the GitHub repository. PRs are regularly reviewed and merged.

Types Of Resources

In any FusionAuth system, there are three types of configuration information.

  • Default object with configuration defined by FusionAuth. For example, the FusionAuth admin UI is represented as an Application inside FusionAuth’s default Tenant.
  • Objects managed by the application developer. Examples of these include any Applications other than the admin UI, Groups, Email Templates and more.
  • Self-managed objects. The most common of these is user objects, when users can self-register.

Only the first two are good fits for management by an infrastructure as code tool like Terraform. And the first, since they cannot be created by Terraform, require special handling to import and manage.

Prerequisites

In order to get the most value from this guide, you should have a running FusionAuth instance and the Terraform CLI installed. If you don’t have these set up yet, please review the following documentation:

This document has been tested with Terraform 1.5 but should work with any modern version.

You can download all of the terraform files in this guide in the corresponding GitHub repository.

Initial Setup

In this section, you initialize the Terraform working directory with a FusionAuth API key.

The FusionAuth API Key

First, create an API key as described in Managing API Keys. Create a superuser key by not selecting any endpoint methods for the key. A super user API key has access to all API endpoints. If you want to limit the operations which can be performed by Terraform, limit the API key to specific endpoints. For this document, a super user API key is assumed.

Copy the key value before pressing the Save button.

If you wanted to set up the key automatically, use Kickstart with a predefined API key. Then you can add this key as a secret in your automated deployment.

Example Kickstart File

{
  "apiKeys": [
    {
      "key": "4737ea8520bd454caabb7cb3d36e14bc1832c0d3f70a4189b82598670f11b1bd"
    }
  ]
}

Initializing The Working Directory

With the API key from your FusionAuth instance, you need to initialize the Terraform working directory.

Create a new directory. Any name will do, but for consistency, it is suggested to name it after your FusionAuth instance host name.

mkdir auth.example.com
cd auth.example.com

You’ll use Terraform variables, so create a variables.tf file with the following content to declare all the Input Variables used in this example.

Example Variables File

variable "fusionauth_api_key" {
  description = "The API Key for the FusionAuth instance"
  type        = string
  default     = ""
  sensitive   = true
}

variable "fusionauth_host" {
  description = "Host for FusionAuth instance"
  type        = string
  default     = ""
}

variable "fusionauth_default_tenant_id" {
  description = "The Tenant Id of the Default FusionAuth Tenant"
  type        = string
  default     = ""
}

variable "fusionauth_default_application_id" {
  description = "The Application Id of the Default FusionAuth Application"
  type        = string
  default     = "3c219e58-ed0e-4b18-ad48-f4f92793ae32"
}

For the fusionauth_api_key variable, the configuration option sensitive = true is set. This will prevent sensitive data being added to the Terraform state.

For the variable definitions, create a terraform.tfvars file with the following content and changes:

  • Set the fusionauth_api_key variable to the value of the previously created API key.
  • Set the fusionauth_host variable to the hostname of the FusionAuth instance you want to manage. Make sure this value is the full URL including the protocol of your FusionAuth instance. When using docker, this will typically be http://localhost:9011.
  • The fusionauth_default_tenant_id variable can be omitted for now, but you’ll update it later.

Example Variable Definitions

fusionauth_api_key = "gHPdrfQa4A36JVoFfVDAY4jG4g8FGTpCd98_zZHTfW1KM2BI7an2gUhB"

fusionauth_host = "http://localhost:9011"

The variable definition with terraform.tfvars is only one option for setting these variables. Review the Assigning Values to Root Module Variables documentation for more options.

Next, add the FusionAuth Terraform Provider configuration in the directory by:

  • Opening the FusionAuth Terraform Provider Documentation.
  • Clicking the USE PROVIDER button.
  • Copying the Hashicorp Configuration Language (HCL) provided.
  • Creating a Terraform configuration file named main.tf.
  • Pasting the copied HCL into that file.
  • Adding the previously created variable as var.fusionauth_api_key to the provider section
  • Adding the FusionAuth instance you plan to manage with the var.fusionauth_host variable in the provider section

Here’s a screenshot of the page.

Downloading the HCL for the FusionAuth provider.

Here’s what your main.tf file should look like at this point.

Example main.tf File

terraform {
  required_providers {
    fusionauth = {
      source = "FusionAuth/fusionauth"
      version = "0.1.101"
    }
  }
}

provider "fusionauth" {
  api_key = var.fusionauth_api_key
  host = var.fusionauth_host
}

Finally, the following command prepares the current working directory for use with Terraform.

terraform init

It is always safe to run terraform init multiple times, to bring the working directory up to date with changes in the configuration. Though subsequent runs may give errors, this command will never delete your existing configuration or state.

Choosing A Configuration Strategy

Once you initialize the Terraform working directory, review different initial strategies to handle resource creation.

StrategyAdvantagesDisadvantages
importmanage existing resourcessome resources don’t fully support the Terraform lifecycle
data sourcereference existing resources without having to manage themrequires a external process to manage these resources
createalways create and be able to manage all resources through Terraformbigger initial effort and default resources can’t be managed

Importing Default Resources

Before you create, update, and remove resources with Terraform, let’s examine the Import and Data Source functionality.

There are FusionAuth default configuration elements present in every FusionAuth instance. If you want to manage changes to these elements via Terraform, you must tell Terraform about them. Or, if you have existing FusionAuth configuration you want to manage via Terraform, import it.

The most consistent method for managing FusionAuth is to create all resources through Terraform. However, you must use either Import or Data Source to handle default or existing FusionAuth configuration.

The FusionAuth default Tenant and default Application are two configuration elements that you will almost certainly want to manage via Terraform. There are others outlined in the FusionAuth default configuration elements that can be managed using the same methods described below.

Importing Tenants

The default Tenant is created whenever you install FusionAuth. To manage it through Terraform, you must import this resource. The FusionAuth Application, which is the administrative user interface, is always in this Tenant.

While you can use the default Tenant for all your Applications, when using Terraform, it is easier to create your own Tenant and create Applications in that Tenant.

As outlined in the Tenant Terraform Resource documentation, add the following code to main.tf.

Default Tenant Import

import {
  to = fusionauth_tenant.Default
  id = "Replace-This-With-The-Existing-Default-Tenant-Id"
}

resource "fusionauth_tenant" "Default" {
  lifecycle {
    prevent_destroy = true
  }
  name = "Default"
  issuer = "acme.com"
  theme_id = "00000000-0000-0000-0000-000000000000"
  external_identifier_configuration {
    authorization_grant_id_time_to_live_in_seconds = 30
    change_password_id_generator {
      length = 32
      type   = "randomBytes"
    }
    change_password_id_time_to_live_in_seconds = 600
    device_code_time_to_live_in_seconds        = 300
    device_user_code_id_generator {
      length = 6
      type   = "randomAlphaNumeric"
    }
    email_verification_id_generator {
      length = 32
      type   = "randomBytes"
    }
    email_verification_id_time_to_live_in_seconds      = 86400
    email_verification_one_time_code_generator {
      length = 6
      type   = "randomAlphaNumeric"
    }
    external_authentication_id_time_to_live_in_seconds = 300
    one_time_password_time_to_live_in_seconds          = 60
    passwordless_login_generator {
      length = 32
      type   = "randomBytes"
    }
    passwordless_login_time_to_live_in_seconds = 180
    registration_verification_id_generator {
      length = 32
      type   = "randomBytes"
    }
    registration_verification_id_time_to_live_in_seconds = 86400
    registration_verification_one_time_code_generator {
      length = 6
      type   = "randomAlphaNumeric"
    }
    saml_v2_authn_request_id_ttl_seconds = 300
    setup_password_id_generator {
      length = 32
      type   = "randomBytes"
    }
    setup_password_id_time_to_live_in_seconds   = 86400
    two_factor_id_time_to_live_in_seconds       = 300
    two_factor_one_time_code_id_generator {
      length = 6
      type   = "randomDigits"
    }
    two_factor_trust_id_time_to_live_in_seconds = 2592000
  }
  jwt_configuration {
    refresh_token_time_to_live_in_minutes              = 43200
    time_to_live_in_seconds                            = 3600
    refresh_token_revocation_policy_on_login_prevented = true
    refresh_token_revocation_policy_on_password_change = true
    access_token_key_id                                = "00000000-0000-0000-0000-000000000000"
    id_token_key_id                                    = "00000000-0000-0000-0000-000000000000"
  }
  login_configuration {
    require_authentication = true
  }
  email_configuration {
    default_from_email                  = "change-me@example.com"
    default_from_name                   = "FusionAuth"
    host                                = "localhost"
    implicit_email_verification_allowed = true
    port                                = 25
    security                            = "NONE"
    verification_strategy               = "ClickableLink"
    verify_email                        = false
    verify_email_when_changed           = false
  }
}

To make things easier, you only have to update the first Id in the import section with the default Tenant Id. You can find this Id in the FusionAuth admin UI under “Tenants”.

The dashboard of the FusionAuth default Tenant

Replace the value Replace-This-With-The-Existing-Default-Tenant-Id with the Tenant’s Id, for example bafb4319-b7ca-ed27-fa2f-bbdba9d8ec06.

All other UUID definitions are set to 00000000-0000-0000-0000-000000000000. You’ll find the correct values for the other Ids later. You can use this technique with any resource you want to import into Terraform.

Since you provided a valid Tenant Id, you can let Terraform find the other Ids by searching the plan output for in-place updates. Then update main.tf with those values. Do this by running terraform plan.

terraform plan | grep \~

Because of the way Terraform works, this plan suggests replacing the UUID definitions for configuration with 00000000-0000-0000-0000-000000000000. You don’t want to do that, you just used that value to find the real UUID values. So, copy the UUIDs to main.tf and replace each instance of 00000000-0000-0000-0000-000000000000 with the correct value.

Here is how the output of the grep command might look:

  ~ update in-place
  ~ resource "fusionauth_tenant" "Default" {
      ~ theme_id                           = "75a068fd-e94b-451a-9aeb-3ddb9a3b5987" -> "00000000-0000-0000-0000-000000000000"
      ~ email_configuration {
      ~ jwt_configuration {
          ~ access_token_key_id                                = "a39be146-51cb-0288-7806-6eb6c066aed3" -> "00000000-0000-0000-0000-000000000000"
          ~ id_token_key_id                                    = "092dbedc-30af-4149-9c61-b578f2c72f59" -> "00000000-0000-0000-0000-000000000000"

Make sure you copy all those Id’s from your output to the main.tf file. For example, modify jwt_configuration to look like

jwt_configuration {
    refresh_token_time_to_live_in_minutes              = 43200
    time_to_live_in_seconds                            = 3600
    refresh_token_revocation_policy_on_login_prevented = true
    refresh_token_revocation_policy_on_password_change = true
    access_token_key_id                                = "a39be146-51cb-0288-7806-6eb6c066aed3"
    id_token_key_id                                    = "092dbedc-30af-4149-9c61-b578f2c72f59"
  }

Once appended run terraform plan to check the validity of your configuration. If there is no output, that indicates that the remote FusionAuth configuration and the main.tf file are in sync.

The above configuration is subject to change either due to changes made using the admin UI or version differences. New tenant configuration defaults may be added over time. Carefully check the terraform plan output to make sure the imported configuration matches the values in FusionAuth.

When the plan is valid, run terraform apply.

terraform plan
terraform apply

You can take the same steps with the FusionAuth Application to import its default configuration into Terraform.

Once imported, Terraform tracks the Tenant object in your state file. You can then manage the imported resource in the same way as others.

You can’t destroy the default Tenant or FusionAuth Application. Attempting to do so will break your Terraform state. See the Prevent Destroy section for more details.

Leave the import block in your main.tf file as a record of the resource’s origin. The import block records that Terraform imported the resource and didn’t create it.

Using Data Sources

Instead of importing a resource, you can use a Data Source. The list of supported FusionAuth data sources is in the Terraform Provider documentation.

Data sources are useful if you choose to manage the default Tenant and FusionAuth Application outside of Terraform, either manually or via a script using the client libraries, but you still need to reference the Tenant or Application. Examples of this include:

  • adding Applications in the default Tenant
  • associating a JWT signing key with the FusionAuth Application
  • setting up an IP ACL to limit access to the FusionAuth Application

Review the tenant and application data source documentation to learn more.

Here’s an example of how you might add data sources to your Terraform file:

A Data Source For The default Tenant

data "fusionauth_tenant" "Default" {
  name = "Default"
}

A Data Source For The FusionAuth Application

data "fusionauth_application" "FusionAuth" {
  name = "FusionAuth"
}

If you manage the default Tenant and FusionAuth Application outside of Terraform but want specific actions (scripts, API calls, etc.) integrated and triggered by your Terraform configuration, you could use Provisioners. Terraform includes the concept of provisioners as a measure of pragmatism and last resort since provisioners are non-declarative and potentially unpredictable.

Now that you’ve configured Terraform to handle some of FusionAuth’s default configuration elements, let’s walk through creating, updating, and removing other resources.

Creating Resources

To create a new resource review the list of resources available to you in the FusionAuth Terraform Provider documentation. Pick the resource you’re interested in. Each resource contains information about required and optional arguments.

For this example, you’ll create the following:

  • A new Tenant called Forum.
  • An Application in that Tenant, also called Forum.
  • Two roles for that Application, admin and user.
  • A token signing key for that Application.
  • Email templates for password emails that will be assigned to the Tenant.

Variables

You’ll need to update variables.tf to include additional variables.

Forum Variable

variable "fusionauth_api_key" {
  description = "The API Key for the FusionAuth instance"
  type        = string
  default     = ""
  sensitive   = true
}

variable "fusionauth_host" {
  description = "Host for FusionAuth instance"
  type        = string
  default     = ""
}

variable "fusionauth_default_tenant_id" {
  description = "The Tenant Id of the Default FusionAuth Tenant"
  type        = string
  default     = ""
}

variable "fusionauth_default_application_id" {
  description = "The Application Id of the Default FusionAuth Application"
  type        = string
  default     = "3c219e58-ed0e-4b18-ad48-f4f92793ae32"
}

variable "fusionauth_default_theme_id" {
  description = "The Default Theme Id of the FusionAuth Instance"
  type        = string
  default     = "75a068fd-e94b-451a-9aeb-3ddb9a3b5987"
}

variable "fusionauth_email_configuration_host" {
  description = "The Email Server Host used to send emails from FusionAuth"
  type = string
  default = "localhost"
}

variable "fusionauth_email_configuration_port" {
  description = "The Email Server Port used to send emails from FusionAuth"
  type = string
  default = "25"
}

The fusionauth_default_theme_id is another constant value across all FusionAuth instances. If you imported or added the default Tenant as a data source, you could also reference the theme Id via that object.

Next, update the terraform.tfvars with customizations of those values. It should look similar to the below file, but you’ll need to update it.

Forum Variable Definitions

fusionauth_api_key = "gHPdrfQa4A36JVoFfVDAY4jG4g8FGTpCd98_zZHTfW1KM2BI7an2gUhB"

fusionauth_host = "http://localhost:9011"

fusionauth_default_tenant_id = ""
  • Set the fusionauth_api_key variable to the value of the previously created API key.
  • Set the fusionauth_host variable to the hostname of the FusionAuth instance you want to manage. Make sure this value is the full URL including the protocol of your FusionAuth instance. When using docker, this will typically be http://localhost:9011.
  • The fusionauth_default_tenant_id variable should be the UUID of the default Tenant.

The Terraform File

Next, update the main.tf file.

If you haven’t worked through the prerequisites above, make sure you add the required_providers and provider sections.

main.tf Initial Sections

terraform {
  required_providers {
    fusionauth = {
      source = "FusionAuth/fusionauth"
      version = "0.1.101"
    }
  }
}

provider "fusionauth" {
  api_key = var.fusionauth_api_key
  host = var.fusionauth_host
}

Then, configure the Forum Tenant; this Tenant will contain users and applications.

Creating A Forum Tenant

resource "fusionauth_tenant" "forum" {
  lifecycle {
    prevent_destroy = true
  }
  issuer   = "forum"
  name     = "Forum"
  theme_id = var.fusionauth_default_theme_id
  multi_factor_configuration {
    login_policy = "Disabled"
  }
  login_configuration {
    require_authentication = false
  }
  external_identifier_configuration {
    authorization_grant_id_time_to_live_in_seconds = 30
    change_password_id_generator {
      length = 32
      type   = "randomBytes"
    }
    change_password_id_time_to_live_in_seconds = 600
    device_code_time_to_live_in_seconds        = 300
    device_user_code_id_generator {
      length = 6
      type   = "randomAlphaNumeric"
    }
    email_verification_id_generator {
      length = 32
      type   = "randomBytes"
    }
    email_verification_id_time_to_live_in_seconds = 86400
    email_verification_one_time_code_generator {
      length = 6
      type   = "randomAlphaNumeric"
    }
    external_authentication_id_time_to_live_in_seconds = 300
    one_time_password_time_to_live_in_seconds          = 60
    passwordless_login_generator {
      length = 32
      type   = "randomBytes"
    }
    passwordless_login_time_to_live_in_seconds = 180
    registration_verification_id_generator {
      length = 32
      type   = "randomBytes"
    }
    registration_verification_id_time_to_live_in_seconds = 86400
    registration_verification_one_time_code_generator {
      length = 6
      type   = "randomAlphaNumeric"
    }
    saml_v2_authn_request_id_ttl_seconds = 300
    setup_password_id_generator {
      length = 32
      type   = "randomBytes"
    }
    setup_password_id_time_to_live_in_seconds = 86400
    two_factor_id_time_to_live_in_seconds     = 300
    two_factor_one_time_code_id_generator {
      length = 6
      type   = "randomDigits"
    }
    two_factor_trust_id_time_to_live_in_seconds = 2592000
  }
  jwt_configuration {
    refresh_token_time_to_live_in_minutes = 43200
    time_to_live_in_seconds               = 3600
  }
  email_configuration {
    host = var.fusionauth_email_configuration_host
    port = var.fusionauth_email_configuration_port
    forgot_password_email_template_id = fusionauth_email.forgot-password.id
    set_password_email_template_id = fusionauth_email.setup-password.id
  }
}

Then, configure the signing key. This will be used to sign access tokens for the Forum Application.

Creating A Signing Key

resource "fusionauth_key" "forum-access-token" {
  algorithm = "HS512"
  name      = "Forum Application Access Token Key"
}

Next, set up the email templates. Because email templates can be long, use the file() function. You’ll need to create files in the email_templates directory and give them the same names. Terraform will import these files.

Creating An Email Template

resource "fusionauth_email" "forgot-password" {
  name                  = "Forgot Password"
  default_from_name     = "Forgot Password"
  default_html_template = file("${path.module}/email_templates/Forgot_Password.html.ftl")
  default_subject       = "Forgot Password"
  default_text_template = file("${path.module}/email_templates/Forgot_Password.txt.ftl")
  from_email            = "example@local.fusionauth.io"
}

resource "fusionauth_email" "setup-password" {
  name                  = "Setup Password"
  default_from_name     = "Setup Password"
  default_html_template = file("${path.module}/email_templates/Setup_Password.html.ftl")
  default_subject       = "Setup Password"
  default_text_template = file("${path.module}/email_templates/Setup_Password.txt.ftl")
  from_email            = "example@local.fusionauth.io"
}

While the values of the email templates don’t matter for this guide, you can find sample files in the GitHub repository.

Now, create the Application in the Tenant. This configuration represents what users actually log in to.

Creating A Forum Application In The Forum Tenant

resource "fusionauth_application" "forum" {
  tenant_id = fusionauth_tenant.forum.id
  name      = "forum"
  jwt_configuration {
    access_token_id = fusionauth_key.forum-access-token.id
  }
}

Create the roles for the Application. These roles will be used by the Forum Application.

Creating Roles In The Forum Application

resource "fusionauth_application_role" "forum_admin_role" {
  application_id = fusionauth_application.forum.id
  is_default     = false
  is_super_role  = true
  name           = "admin"
}

resource "fusionauth_application_role" "forum_user_role" {
  application_id = fusionauth_application.forum.id
  is_default     = true
  is_super_role  = false
  name           = "user"
}

Finally, get the plan.

terraform plan

Once you’re happy with your configuration run terraform plan. If you aren’t experiencing errors and are ok with the planned changes, run terraform apply.

Where Are The Users?

As mentioned above, Terraform is great for configuring and tracking changes of relatively slow-changing aspects of systems.

With FusionAuth, users typically self-register or are managed dynamically using API calls. Using Terraform to manage them doesn’t make much sense.

Instead, turn on self-service registration, create them using the User API, or migrate your users.

Updating Resources

Once a resource is managed by Terraform, you can change the main.tf or other Terraform files according to the Terraform Provider Documentation.

You might decide you want to change the time_to_live_in_seconds value for your JWT configuration from 3600 to 1800 seconds. If so, update the configuration element to look something like this:

  jwt_configuration {
    refresh_token_time_to_live_in_minutes = 43200
    time_to_live_in_seconds               = 1800
  }

After you’re done with editing the Terraform file, run terraform plan to check the planned changes. Review and fix any errors. Terraform will show you what it will change.

Terraform will perform the following actions:

  # fusionauth_tenant.forum will be updated in-place
  ~ resource "fusionauth_tenant" "forum" {
        id                                 = "ef1f5235-82de-4a2a-b103-4d130fa5f5f2"
        name                               = "Forum"
        # (7 unchanged attributes hidden)

      ~ jwt_configuration {
          ~ time_to_live_in_seconds                            = 3600 -> 1800

After review, run terraform apply.

If you want to know what already has been defined by Terraform but is not specified in your .tf files you can run terraform show.

The configuration file can get very large. If you want to show specific resources, list all resources with the terraform state list command. Then examine the resource state with terraform state show <resource-name>.

Ignoring Changes

Every time you make changes it is possible that configuration has been updated in your FusionAuth instance manually or via API. While it is best to lock down access so every change runs through Terraform, this isn’t always possible.

You can either align your configuration with the installation or decide to ignore_changes in your configuration.

Here is an example with FusionAuth resources. Imagine a situation where a business web or mobile app adds data to a FusionAuth Application’s data field. For example, the app could update the configuration with a curl command to add a productOwner or other data.

Example Script To Modify An Application

curl \
-X PATCH \
-H 'Content-Type: application/json' \
-H 'X-FusionAuth-TenantId: TENANT-ID-HERE' \
-H 'Authorization: API-KEY-HERE' \
http://localhost:9011/api/application/APPLICATION-ID-HERE \
-d @patch.json

JSON To Patch An Application

{
  "application": {
    "data": {
      "externalApplication": "Acme. Customer Support Forum",
      "productOwner": "john@acme.com",
      "supportHotline": "+1 (650) 933-5102"
    }
  }
}

You will see in the terraform plan output that Terraform wants to revert the Application back to the original state.

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # fusionauth_application.forum will be updated in-place
  ~ resource "fusionauth_application" "forum" {
      ~ data                                       = {
          - "externalApplication" = "Acme. Customer Support Forum" -> null
          - "productOwner"        = "john@acme.com" -> null
          - "supportHotline"      = "+1-636-555-3226" -> null
        }
        id                                         = "7acaec93-edd4-49a1-82db-602fe8dda23f"
        name                                       = "forum"
        # (12 unchanged attributes hidden)

        # (5 unchanged blocks hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

To prevent Terraform from doing this, use ignore_changes to tell Terraform to ignore parts of your configuration.

resource "fusionauth_application" "forum" {
  tenant_id = fusionauth_tenant.forum.id
  name = "forum"
  lifecycle {
    ignore_changes = [
      data
    ]
  }
}

If you’re only interested in creating and destroying resources, rather than updating them, you can ignore all changes:

lifecycle {
  ignore_changes = all
}

Removing Resources

If you want to remove a resource, comment out or delete the lines defining the resource in your Terraform files. Run terraform plan to double check what will be removed. Then run terraform apply, which will destroy the resource.

If you run terraform destroy, be aware that all Terraform managed resources will be destroyed.

Deleting a Tenant removes all the associated Applications, Groups, and Users. If you want to make sure that the Tenant isn’t destroyed, see Prevent Destroy for more details.

Prevent Destroy

If you want to make sure that certain resources aren’t destroyed, specify the [prevent_destroy](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#prevent_destroy lifecycle) meta-argument as a measure of safety against the accidental replacement of objects that may be costly or impossible to reproduce. However, using this argument makes certain configuration changes impossible to apply.

lifecycle {
  prevent_destroy = true
}

A good example of an element to apply prevent_destroy to would be a Tenant currently in use. Deleting a Tenant removes all the associated Applications, Groups, and Users. Below, notice that the Forum Tenant can’t be destroyed.

Creating A Forum Tenant

resource "fusionauth_tenant" "forum" {
  lifecycle {
    prevent_destroy = true
  }
  issuer   = "forum"
  name     = "Forum"
  theme_id = var.fusionauth_default_theme_id
  multi_factor_configuration {
    login_policy = "Disabled"
  }
  login_configuration {
    require_authentication = false
  }
  external_identifier_configuration {
    authorization_grant_id_time_to_live_in_seconds = 30
    change_password_id_generator {
      length = 32
      type   = "randomBytes"
    }
    change_password_id_time_to_live_in_seconds = 600
    device_code_time_to_live_in_seconds        = 300
    device_user_code_id_generator {
      length = 6
      type   = "randomAlphaNumeric"
    }
    email_verification_id_generator {
      length = 32
      type   = "randomBytes"
    }
    email_verification_id_time_to_live_in_seconds = 86400
    email_verification_one_time_code_generator {
      length = 6
      type   = "randomAlphaNumeric"
    }
    external_authentication_id_time_to_live_in_seconds = 300
    one_time_password_time_to_live_in_seconds          = 60
    passwordless_login_generator {
      length = 32
      type   = "randomBytes"
    }
    passwordless_login_time_to_live_in_seconds = 180
    registration_verification_id_generator {
      length = 32
      type   = "randomBytes"
    }
    registration_verification_id_time_to_live_in_seconds = 86400
    registration_verification_one_time_code_generator {
      length = 6
      type   = "randomAlphaNumeric"
    }
    saml_v2_authn_request_id_ttl_seconds = 300
    setup_password_id_generator {
      length = 32
      type   = "randomBytes"
    }
    setup_password_id_time_to_live_in_seconds = 86400
    two_factor_id_time_to_live_in_seconds     = 300
    two_factor_one_time_code_id_generator {
      length = 6
      type   = "randomDigits"
    }
    two_factor_trust_id_time_to_live_in_seconds = 2592000
  }
  jwt_configuration {
    refresh_token_time_to_live_in_minutes = 43200
    time_to_live_in_seconds               = 3600
  }
  email_configuration {
    host = var.fusionauth_email_configuration_host
    port = var.fusionauth_email_configuration_port
    forgot_password_email_template_id = fusionauth_email.forgot-password.id
    set_password_email_template_id = fusionauth_email.setup-password.id
  }
}

You should always apply the prevent_destroy same meta-argument for Terraform managed non-deletable resources like the default Tenant and the FusionAuth Application, since Terraform can’t delete these anyway.

Default Tenant Import

import {
  to = fusionauth_application.FusionAuth
  id = "3c219e58-ed0e-4b18-ad48-f4f92793ae32"
}

resource "fusionauth_application" "FusionAuth" {
  lifecycle {
    prevent_destroy = true
  }
  tenant_id = fusionauth_tenant.Default.id
  name = "FusionAuth"
}

FusionAuth And OpenTofu

The FusionAuth Terraform provider should work fine with OpenTofu as long as OpenTofu remains compatible with Terraform. If you notice any differences, please open a GitHub issue.

Limitations

You must create an API key either manually or via Kickstart. There is no way to create the initial API key using Terraform.

The Terraform implementation is not currently at parity with the client libraries and the REST API. If you run into any discrepancies that affect your usages, please open a GitHub issue.