---
title: Vendor-Specific Instructions
icon: TbCloud
image: /images/user-guide/notes/notes_header.png
---

<ArticleWarning>
This document is maintained by the community. It might contain issues.
</ArticleWarning>


## Kubernetes via Terraform and Manifests

Community-led documentation for Kubernetes deployment is available [here](https://github.com/twentyhq/twenty/tree/main/packages/twenty-docker/k8s)

## Render

Community-led, might not be up to date

[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/twentyhq/twenty)


## RepoCloud 

Community-led, might not be up to date

[![Deploy on RepoCloud](https://d16t0pc4846x52.cloudfront.net/deploy.png)](https://repocloud.io/details/?app_id=259)


## Azure Container Apps

Community-led, might not be up to date

### About

Hosts Twenty CRM using Azure Container Apps.
The solution provisions file shares, a container apps environment with three containers, and a log analytics workspace.

The file shares are used to store uploaded images and files through the UI, and to store database backups.

### Prerequisites

- Terraform installed https://developer.hashicorp.com/terraform/install
- An Azure subscription with permissions to create resources

### Step by step instructions:

1. Create a new folder and copy all the files from below
2. Run `terraform init`
3. Run `terraform plan -out tfplan`
4. Run `terraform apply tfplan`
5. Connect to server `az containerapp exec --name twenty-server -g twenty-crm-rg`
6. Initialize the database from the server `yarn database:init:prod`
7. Go to https://your-twenty-front-fqdn - located in the portal

#### Production docker containers

This uses the prebuilt images found on [docker hub](https://hub.docker.com/r/twentycrm/).

#### Environment Variables

- Is set in respective tf-files
- See docs [Setup Environment Variables](https://twenty.com/developers/section/self-hosting/self-hosting-var) for usage
- After deployment you could can set `IS_SIGN_UP_DISABLED=true` (and run `terraform plan/apply` again) to disable new workspaces from being created

#### Security and networking

- Container `twenty-db` accepts only ingress TCP traffic from other containers in the environment. No external ingress traffic allowed
- Container `twenty-server` accepts external traffic over HTTPS
- Container `twenty-front` accepts external traffic over HTTPS

It´s highly recommended to enable [built-in authentication](https://learn.microsoft.com/en-us/azure/container-apps/authentication) for `twenty-front` using one of the supported providers.

Use the [custom domain](https://learn.microsoft.com/en-us/azure/container-apps/custom-domains-certificates) feature on the `twenty-front` container if you would like an easier domain name.

#### Files

##### providers.tf

```hcl
# providers.tf

terraform {
  required_providers {
    azapi = {
      source = "Azure/azapi"
    }
  }
}

provider "azapi" {
}

provider "azurerm" {
  features {}
}

provider "azuread" {
}

provider "random" {
}
```

##### main.tf

```hcl
# main.tf

# Create a resource group
resource "azurerm_resource_group" "main" {
  name     = "twenty-crm-rg"
  location = "North Europe"
}

# Variables
locals {
  app_env_name = "twenty"

  server_name = "twenty-server"
  server_tag  = "latest"

  front_app_name = "twenty-front"
  front_tag      = "latest"

  db_app_name = "twenty-postgres"
  db_tag      = "latest"

  db_user     = "twenty"
  db_password = "twenty"

  storage_mount_db_name     = "twentydbstoragemount"
  storage_mount_server_name = "twentyserverstoragemount"

  cpu    = 1.0
  memory = "2Gi"
}

# Set up a Log Analytics workspace
resource "azurerm_log_analytics_workspace" "main" {
  name                = "${local.app_env_name}-law"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  sku                 = "PerGB2018"
  retention_in_days   = 30
}

# Create a storage account
resource "random_pet" "example" {
  length    = 2
  separator = ""
}

resource "azurerm_storage_account" "main" {
  name                     = "twentystorage${random_pet.example.id}"
  resource_group_name      = azurerm_resource_group.main.name
  location                 = azurerm_resource_group.main.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
  large_file_share_enabled = true
}

# Create db file storage
resource "azurerm_storage_share" "db" {
  name                 = "twentydatabaseshare"
  storage_account_name = azurerm_storage_account.main.name
  quota                = 50
  enabled_protocol     = "SMB"
}

# Create backend file storage
resource "azurerm_storage_share" "server" {
  name                 = "twentyservershare"
  storage_account_name = azurerm_storage_account.main.name
  quota                = 50
  enabled_protocol     = "SMB"
}

# Create a Container App Environment
resource "azurerm_container_app_environment" "main" {
  name                       = "${local.app_env_name}-env"
  location                   = azurerm_resource_group.main.location
  resource_group_name        = azurerm_resource_group.main.name
  log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
}

# Connect the db storage share to the container app environment
resource "azurerm_container_app_environment_storage" "db" {
  name                         = local.storage_mount_db_name
  container_app_environment_id = azurerm_container_app_environment.main.id
  account_name                 = azurerm_storage_account.main.name
  share_name                   = azurerm_storage_share.db.name
  access_key                   = azurerm_storage_account.main.primary_access_key
  access_mode                  = "ReadWrite"
}

# Connect the server storage share to the container app environment
resource "azurerm_container_app_environment_storage" "server" {
  name                         = local.storage_mount_server_name
  container_app_environment_id = azurerm_container_app_environment.main.id
  account_name                 = azurerm_storage_account.main.name
  share_name                   = azurerm_storage_share.server.name
  access_key                   = azurerm_storage_account.main.primary_access_key
  access_mode                  = "ReadWrite"
}
```

##### frontend.tf

```hcl
# frontend.tf

resource "azurerm_container_app" "twenty_front" {
  name                         = local.front_app_name
  container_app_environment_id = azurerm_container_app_environment.main.id
  resource_group_name          = azurerm_resource_group.main.name
  revision_mode                = "Single"

  depends_on = [azurerm_container_app.twenty_server]

  ingress {
    allow_insecure_connections = false
    external_enabled           = true
    target_port                = 3000
    transport                  = "http"
    traffic_weight {
      percentage      = 100
      latest_revision = true
    }
  }

  template {
    min_replicas = 1
    # things starts to fail when using more than 1 replica
    max_replicas = 1
    container {
      name   = "twenty-front"
      image  = "docker.io/twentycrm/twenty-front:${local.front_tag}"
      cpu    = local.cpu
      memory = local.memory

      env {
        name  = "REACT_APP_SERVER_BASE_URL"
        value = "https://${azurerm_container_app.twenty_server.ingress[0].fqdn}"
      }
    }
  }
}

# Set CORS rules for frontend app using AzAPI
resource "azapi_update_resource" "cors" {
  type        = "Microsoft.App/containerApps@2023-05-01"
  resource_id = azurerm_container_app.twenty_front.id
  body = jsonencode({
    properties = {
      configuration = {
        ingress = {
          corsPolicy = {
            allowedOrigins = ["*"]
          }
        }
      }
    }
  })
  depends_on = [azurerm_container_app.twenty_front]
}
```

##### backend.tf

```hcl
# backend.tf

# Create three random UUIDs
resource "random_uuid" "access_token_secret" {}
resource "random_uuid" "login_token_secret" {}
resource "random_uuid" "refresh_token_secret" {}
resource "random_uuid" "file_token_secret" {}

resource "azurerm_container_app" "twenty_server" {
  name                         = local.server_name
  container_app_environment_id = azurerm_container_app_environment.main.id
  resource_group_name          = azurerm_resource_group.main.name
  revision_mode                = "Single"

  depends_on = [azurerm_container_app.twenty_db, azurerm_container_app_environment_storage.server]

  ingress {
    allow_insecure_connections = false
    external_enabled           = true
    target_port                = 3000
    transport                  = "http"
    traffic_weight {
      percentage      = 100
      latest_revision = true
    }
  }

  template {
    min_replicas = 1
    max_replicas = 1
    volume {
      name         = "twenty-server-data"
      storage_type = "AzureFile"
      storage_name = local.storage_mount_server_name
    }

    container {
      name   = local.server_name
      image  = "docker.io/twentycrm/twenty-server:${local.server_tag}"
      cpu    = local.cpu
      memory = local.memory

      volume_mounts {
        name = "twenty-server-data"
        path = "/app/packages/twenty-server/.local-storage"
      }

      # Environment variables
      env {
        name  = "IS_SIGN_UP_DISABLED"
        value = false
      }
      env {
        name  = "SIGN_IN_PREFILLED"
        value = false
      }
      env {
        name  = "STORAGE_TYPE"
        value = "local"
      }
      env {
        name  = "STORAGE_LOCAL_PATH"
        value = ".local-storage"
      }
      env {
        name  = "PG_DATABASE_URL"
        value = "postgres://${local.db_user}:
        ${local.db_password}@${local.db_app_name}:5432/default"
      }
      env {
        name  = "FRONT_BASE_URL"
        value = "https://${local.front_app_name}"
      }
      env {
        name  = "ACCESS_TOKEN_SECRET"
        value = random_uuid.access_token_secret.result
      }
      env {
        name  = "LOGIN_TOKEN_SECRET"
        value = random_uuid.login_token_secret.result
      }
      env {
        name  = "REFRESH_TOKEN_SECRET"
        value = random_uuid.refresh_token_secret.result
      }
      env {
        name  = "FILE_TOKEN_SECRET"
        value = random_uuid.file_token_secret.result
      }
    }
  }
}

# Set CORS rules for server app using AzAPI
resource "azapi_update_resource" "server_cors" {
  type        = "Microsoft.App/containerApps@2023-05-01"
  resource_id = azurerm_container_app.twenty_server.id
  body = jsonencode({
    properties = {
      configuration = {
        ingress = {
          corsPolicy = {
            allowedOrigins = ["*"]
          }
        }
      }
    }
  })
  depends_on = [azurerm_container_app.twenty_server]
}
```

##### database.tf

```hcl
# database.tf

resource "azurerm_container_app" "twenty_db" {
  name                         = local.db_app_name
  container_app_environment_id = azurerm_container_app_environment.main.id
  resource_group_name          = azurerm_resource_group.main.name
  revision_mode                = "Single"

  depends_on = [azurerm_container_app_environment_storage.db]

  ingress {
    allow_insecure_connections = false
    external_enabled           = false
    target_port                = 5432
    transport                  = "tcp"
    traffic_weight {
      percentage      = 100
      latest_revision = true
    }
  }

  template {
    min_replicas = 1
    max_replicas = 1
    container {
      name   = local.db_app_name
      image  = "docker.io/twentycrm/twenty-postgres:${local.db_tag}"
      cpu    = local.cpu
      memory = local.memory

      volume_mounts {
        name = "twenty-db-data"
        path = "/var/lib/postgresql/data"
      }

      env {
        name  = "POSTGRES_USER"
        value = "postgres"
      }
      env {
        name  = "POSTGRES_PASSWORD"
        value = "postgres"
      }
      env {
        name  = "POSTGRES_DB"
        value = "default"
      }
    }

    volume {
      name         = "twenty-db-data"
      storage_type = "AzureFile"
      storage_name = local.storage_mount_db_name
    }
  }
}
```

## Others

Please feel free to Open a PR to add more Cloud Provider options.

<ArticleEditContent></ArticleEditContent>