Add Azure cloud provider option (#4296)
* docs: added self-host azure container apps option * syntax fix * typo * Rename file to cloud providers * Add info section --------- Co-authored-by: Thomas Hillesøy <thomas.hillesoy@gmail.com> Co-authored-by: Thomas Trompette <thomast@twenty.com>
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: 1-Click Deploy
|
title: 1-Click Deploy
|
||||||
sidebar_position: 2
|
sidebar_position: 1
|
||||||
sidebar_custom_props:
|
sidebar_custom_props:
|
||||||
icon: TbBolt
|
icon: TbBolt
|
||||||
---
|
---
|
||||||
|
|||||||
435
packages/twenty-docs/docs/start/self-hosting/cloud-providers.mdx
Normal file
435
packages/twenty-docs/docs/start/self-hosting/cloud-providers.mdx
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
---
|
||||||
|
title: Cloud providers
|
||||||
|
sidebar_position: 2
|
||||||
|
sidebar_custom_props:
|
||||||
|
icon: TbCloud
|
||||||
|
---
|
||||||
|
|
||||||
|
:::info
|
||||||
|
This document is maintained by the community. It might contain issues.
|
||||||
|
Feel free to join our discord if you need assistance.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Available cloud providers
|
||||||
|
|
||||||
|
- [Azure Container Apps](#azure-container-apps)
|
||||||
|
- [Others](#others)
|
||||||
|
|
||||||
|
## Azure Container Apps
|
||||||
|
|
||||||
|
### 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`
|
||||||
|
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://docs.twenty.com/start/self-hosting/) 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}"
|
||||||
|
}
|
||||||
|
env {
|
||||||
|
name = "REACT_APP_SERVER_AUTH_URL"
|
||||||
|
value = "https://${azurerm_container_app.twenty_server.ingress[0].fqdn}/auth"
|
||||||
|
}
|
||||||
|
env {
|
||||||
|
name = "REACT_APP_SERVER_FILES_URL"
|
||||||
|
value = "https://${azurerm_container_app.twenty_server.ingress[0].fqdn}/files"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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 "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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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.
|
||||||
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Docker Compose
|
title: Docker Compose
|
||||||
sidebar_position: 1
|
sidebar_position: 3
|
||||||
sidebar_custom_props:
|
sidebar_custom_props:
|
||||||
icon: TbBrandDocker
|
icon: TbBrandDocker
|
||||||
---
|
---
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Upgrade guide
|
title: Upgrade guide
|
||||||
sidebar_position: 3
|
sidebar_position: 4
|
||||||
sidebar_class_name: coming-soon
|
sidebar_class_name: coming-soon
|
||||||
sidebar_custom_props:
|
sidebar_custom_props:
|
||||||
icon: TbServer
|
icon: TbServer
|
||||||
|
|||||||
Reference in New Issue
Block a user