> ## Documentation Index
> Fetch the complete documentation index at: https://qovery-gdubroeucq-qov-2319.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Advanced Patterns

> Advanced Terraform techniques with Qovery

## Overview

This guide covers Terraform patterns for managing Qovery resources at scale: modules, workspaces, remote state, variable files, lists built from variables and conditional resources.

## Reusable Modules

Organize your Terraform code with reusable modules for consistent deployments.

### Module Structure

```
terraform-qovery/
├── main.tf
├── variables.tf
├── outputs.tf
└── modules/
    ├── environment/
    │   ├── main.tf
    │   ├── variables.tf
    │   └── outputs.tf
    ├── application/
    │   ├── main.tf
    │   ├── variables.tf
    │   └── outputs.tf
    └── database/
        ├── main.tf
        ├── variables.tf
        └── outputs.tf
```

### Application Module

This module declares its inputs, the application and its outputs in one file for brevity. A module that uses the Qovery provider declares it in its own `required_providers` block; otherwise Terraform looks for a `hashicorp/qovery` provider.

**modules/application/main.tf**

```hcl theme={null}
terraform {
  required_providers {
    qovery = {
      source  = "qovery/qovery"
      version = "~> 1.0"
    }
  }
}

variable "environment_id" {
  description = "Environment ID"
  type        = string
}

variable "name" {
  description = "Application name"
  type        = string
}

variable "git_url" {
  description = "Git repository URL"
  type        = string
}

variable "git_branch" {
  description = "Git branch"
  type        = string
  default     = "main"
}

variable "cpu" {
  description = "CPU allocation in millicores"
  type        = number
  default     = 500
}

variable "memory" {
  description = "Memory allocation in MB"
  type        = number
  default     = 512
}

variable "port" {
  description = "Internal application port"
  type        = number
  default     = 8080
}

variable "publicly_accessible" {
  description = "Make application publicly accessible"
  type        = bool
  default     = true
}

resource "qovery_application" "app" {
  environment_id = var.environment_id
  name           = var.name

  git_repository = {
    url    = var.git_url
    branch = var.git_branch
  }

  build_mode      = "DOCKER"
  dockerfile_path = "Dockerfile"

  cpu    = var.cpu
  memory = var.memory

  ports = [{
    internal_port       = var.port
    external_port       = var.publicly_accessible ? 443 : null
    protocol            = "HTTP"
    publicly_accessible = var.publicly_accessible
  }]

  healthchecks = {
    readiness_probe = {
      type = {
        tcp = {
          port = var.port
        }
      }
      initial_delay_seconds = 30
      period_seconds        = 10
      timeout_seconds       = 5
      success_threshold     = 1
      failure_threshold     = 3
    }
  }

  auto_deploy = var.git_branch != "main"
}

output "id" {
  value       = qovery_application.app.id
  description = "Application ID"
}

output "external_host" {
  value       = qovery_application.app.external_host
  description = "Application external hostname"
}

output "internal_host" {
  value       = qovery_application.app.internal_host
  description = "Application internal hostname"
}
```

### Using the Module

The root configuration takes the IDs of an existing project and cluster as input variables. See [Find resource IDs](/terraform-provider/overview#find-resource-ids).

**main.tf**

```hcl theme={null}
terraform {
  required_providers {
    qovery = {
      source  = "qovery/qovery"
      version = "~> 1.0"
    }
  }
}

# Reads the API token from the QOVERY_API_TOKEN environment variable.
provider "qovery" {}

variable "project_id" {
  description = "ID of an existing project"
  type        = string
}

variable "cluster_id" {
  description = "ID of the cluster that runs the environment"
  type        = string
}

resource "qovery_environment" "prod" {
  project_id = var.project_id
  cluster_id = var.cluster_id
  name       = "production"
  mode       = "PRODUCTION"
}

# Deploy API using module
module "api" {
  source = "./modules/application"

  environment_id = qovery_environment.prod.id
  name           = "api"
  git_url        = "https://github.com/my-org/api.git"
  git_branch     = "main"
  cpu            = 1000
  memory         = 1024
  port           = 3000
}

# Deploy frontend using module
module "frontend" {
  source = "./modules/application"

  environment_id = qovery_environment.prod.id
  name           = "frontend"
  git_url        = "https://github.com/my-org/frontend.git"
  git_branch     = "main"
  cpu            = 500
  memory         = 512
  port           = 8080
}

# Deploy admin using module
module "admin" {
  source = "./modules/application"

  environment_id      = qovery_environment.prod.id
  name                = "admin"
  git_url             = "https://github.com/my-org/admin.git"
  git_branch          = "main"
  cpu                 = 250
  memory              = 256
  port                = 8080
  publicly_accessible = false # Internal only
}

# Creating the services does not deploy them: this resource deploys the environment.
resource "qovery_deployment" "prod" {
  environment_id = qovery_environment.prod.id
  desired_state  = "RUNNING"

  depends_on = [module.api, module.frontend, module.admin]
}
```

## Terraform Workspaces

Use workspaces to manage multiple environments with a single configuration: each workspace has its own state. The configuration reads the workspace name from `terraform.workspace` and looks up the settings of that environment.

The [multi-environment example](/terraform-provider/multi-environment) uses the same kind of settings map with `for_each` and shows the complete application. With workspaces, you index the map with the workspace name instead.

### Workspace Configuration

```hcl theme={null}
locals {
  # Get current workspace name
  environment = terraform.workspace

  # Environment-specific configuration
  config = {
    dev = {
      mode                   = "DEVELOPMENT"
      database_mode          = "CONTAINER"
      database_instance_type = null
      database_storage       = 10
    }
    staging = {
      mode                   = "STAGING"
      database_mode          = "CONTAINER"
      database_instance_type = null
      database_storage       = 20
    }
    production = {
      mode                   = "PRODUCTION"
      database_mode          = "MANAGED"
      database_instance_type = "db.t3.medium"
      database_storage       = 50
    }
  }

  # Get current environment config
  current_config = local.config[local.environment]
}

resource "qovery_environment" "env" {
  project_id = var.project_id
  cluster_id = var.cluster_id
  name       = local.environment
  mode       = local.current_config.mode
}

resource "qovery_database" "db" {
  environment_id = qovery_environment.env.id
  name           = "database"
  type           = "POSTGRESQL"
  version        = "16"
  mode           = local.current_config.database_mode
  instance_type  = local.current_config.database_instance_type
  accessibility  = "PRIVATE"
  storage        = local.current_config.database_storage
}

# Declare the services the same way, with values from local.current_config.
```

A `MANAGED` database requires `instance_type`. `db.t3.medium` is an AWS RDS instance type: use one that your cloud provider offers.

### Using Workspaces

The configuration only knows the `dev`, `staging` and `production` workspaces: create them before you apply, and do not apply in the `default` workspace.

```bash theme={null}
# Create workspaces
terraform workspace new dev
terraform workspace new staging
terraform workspace new production

# List workspaces
terraform workspace list

# Deploy to development
terraform workspace select dev
terraform apply

# Deploy to staging
terraform workspace select staging
terraform apply

# Deploy to production
terraform workspace select production
terraform apply

# Show current workspace
terraform workspace show
```

## Remote State Management

Store Terraform state remotely for team collaboration.

### S3 Backend

```hcl theme={null}
terraform {
  backend "s3" {
    bucket       = "my-terraform-state"
    key          = "qovery/terraform.tfstate"
    region       = "us-east-1"
    encrypt      = true
    use_lockfile = true
  }

  required_providers {
    qovery = {
      source  = "qovery/qovery"
      version = "~> 1.0"
    }
  }
}
```

`use_lockfile = true` locks the state with a lock file in the bucket, so you do not need a DynamoDB table. Enable versioning on the bucket to recover an earlier version of the state.

### Terraform Cloud

```hcl theme={null}
terraform {
  cloud {
    organization = "my-org"

    workspaces {
      name = "qovery-production"
    }
  }

  required_providers {
    qovery = {
      source  = "qovery/qovery"
      version = "~> 1.0"
    }
  }
}
```

### Initialize Backend

```bash theme={null}
# Initialize backend
terraform init

# Migrate existing state
terraform init -migrate-state
```

## Variable Files

Organize variables per environment using `.tfvars` files.

### Directory Structure

```
terraform/
├── main.tf
├── variables.tf
├── outputs.tf
└── environments/
    ├── dev.tfvars
    ├── staging.tfvars
    └── production.tfvars
```

### variables.tf

```hcl theme={null}
variable "qovery_api_token" {
  description = "Qovery API token"
  type        = string
  sensitive   = true
}

variable "environment_name" {
  description = "Environment name"
  type        = string
}

variable "environment_mode" {
  description = "Environment mode"
  type        = string
}

variable "cpu" {
  description = "CPU allocation"
  type        = number
}

variable "memory" {
  description = "Memory allocation"
  type        = number
}

variable "replicas" {
  description = "Number of replicas"
  type        = number
}
```

### environments/production.tfvars

```hcl theme={null}
environment_name = "production"
environment_mode = "PRODUCTION"
cpu              = 1000
memory           = 1024
replicas         = 3
```

### environments/dev.tfvars

```hcl theme={null}
environment_name = "dev"
environment_mode = "DEVELOPMENT"
cpu              = 250
memory           = 256
replicas         = 1
```

Keep the API token out of these files: set it with the `TF_VAR_qovery_api_token` environment variable.

### Deploy with Variable Files

```bash theme={null}
# Deploy to development
terraform apply -var-file="environments/dev.tfvars"

# Deploy to production
terraform apply -var-file="environments/production.tfvars"
```

Both commands use the same state unless each environment has its own backend configuration or workspace.

## Lists from Variables

`ports`, `environment_variables` and the other list attributes of the Qovery resources are nested attributes, not blocks, so `dynamic` blocks do not apply to them. Assign a list directly, or build one with a `for` expression.

```hcl theme={null}
variable "ports" {
  description = "Application ports"
  type = list(object({
    internal_port       = number
    external_port       = number
    protocol            = string
    publicly_accessible = bool
  }))
  default = [
    {
      internal_port       = 8080
      external_port       = 443
      protocol            = "HTTP"
      publicly_accessible = true
    }
  ]
}

variable "environment_variables" {
  description = "Environment variables of the application"
  type        = map(string)
  default = {
    LOG_LEVEL = "info"
  }
}

resource "qovery_application" "app" {
  environment_id = qovery_environment.prod.id
  name           = "my-app"

  git_repository = {
    url    = "https://github.com/my-org/app.git"
    branch = "main"
  }

  build_mode      = "DOCKER"
  dockerfile_path = "Dockerfile"

  # A list from a variable
  ports = var.ports

  # A list built from a map with a for expression
  environment_variables = [
    for key, value in var.environment_variables : {
      key   = key
      value = value
    }
  ]

  healthchecks = {
    readiness_probe = {
      type = {
        tcp = {
          port = var.ports[0].internal_port
        }
      }
      initial_delay_seconds = 30
      period_seconds        = 10
      timeout_seconds       = 5
      success_threshold     = 1
      failure_threshold     = 3
    }
  }
}
```

## Conditional Resources

Create resources conditionally based on variables.

```hcl theme={null}
variable "enable_database" {
  description = "Enable database deployment"
  type        = bool
  default     = true
}

variable "enable_redis" {
  description = "Enable Redis deployment"
  type        = bool
  default     = false
}

resource "qovery_database" "postgres" {
  count = var.enable_database ? 1 : 0

  environment_id = qovery_environment.prod.id
  name           = "postgres"
  type           = "POSTGRESQL"
  version        = "16"
  mode           = "MANAGED"
  instance_type  = "db.t3.micro"
  accessibility  = "PRIVATE"
  storage        = 20
}

resource "qovery_database" "redis" {
  count = var.enable_redis ? 1 : 0

  environment_id = qovery_environment.prod.id
  name           = "redis"
  type           = "REDIS"
  version        = "7.0"
  mode           = "MANAGED"
  instance_type  = "cache.t3.micro"
  accessibility  = "PRIVATE"
  storage        = 10
}
```

`MANAGED` databases require `instance_type`. `db.t3.micro` and `cache.t3.micro` are AWS instance types: use ones that your cloud provider offers.

## Best Practices

<AccordionGroup>
  <Accordion title="Use Version Constraints" icon="lock">
    Pin the provider major version, so that a new major release cannot introduce breaking changes without an explicit upgrade. The provider is tested against Terraform 1.15; earlier versions are expected to work but are not tested.

    ```hcl theme={null}
    terraform {
      required_providers {
        qovery = {
          source  = "qovery/qovery"
          version = "~> 1.0" # Allows 1.x updates, not 2.0
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="Separate State Per Environment" icon="layer-group">
    Use different state files for each environment to prevent accidental changes:

    * Different S3 keys: `env/dev/terraform.tfstate`, `env/prod/terraform.tfstate`
    * Different Terraform Cloud workspaces
    * Different backend configurations
  </Accordion>

  <Accordion title="Use Data Sources" icon="database">
    Read an existing resource by its ID instead of copying its attributes into the configuration. Data sources look up resources by `id`, not by name:

    ```hcl theme={null}
    data "qovery_project" "existing" {
      id = var.project_id
    }

    output "project_name" {
      value = data.qovery_project.existing.name
    }
    ```
  </Accordion>

  <Accordion title="Protect Production" icon="shield">
    Add lifecycle rules to prevent accidental deletion:

    ```hcl theme={null}
    resource "qovery_database" "prod" {
      # ... configuration ...

      lifecycle {
        prevent_destroy = true
      }
    }
    ```
  </Accordion>

  <Accordion title="Share Labels with a Labels Group" icon="tags">
    Declare common Kubernetes labels once in a labels group, then attach the group to each service with `labels_group_ids`:

    ```hcl theme={null}
    resource "qovery_labels_group" "common" {
      organization_id = var.organization_id
      name            = "my-project"

      labels = [
        {
          key                         = "project"
          value                       = "my-project"
          propagate_to_cloud_provider = false
        },
        {
          key                         = "managed-by"
          value                       = "terraform"
          propagate_to_cloud_provider = false
        }
      ]
    }

    # On each service:
    #   labels_group_ids = [qovery_labels_group.common.id]
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Basic Application" icon="rocket" href="/terraform-provider/basic-application">
    Start with a simple application
  </Card>

  <Card title="Multi-Environment" icon="layer-group" href="/terraform-provider/multi-environment">
    Deploy to multiple environments
  </Card>

  <Card title="Provider Documentation" icon="book" href="https://registry.terraform.io/providers/qovery/qovery/latest/docs">
    Complete provider reference
  </Card>

  <Card title="Terraform Documentation" icon="book" href="https://developer.hashicorp.com/terraform/docs">
    Official Terraform documentation
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.