diff --git a/packages/twenty-docs/docs/start/self-hosting/1-click-deploy.mdx b/packages/twenty-docs/docs/start/self-hosting/1-click-deploy.mdx index 8c681454c..f561d66f4 100644 --- a/packages/twenty-docs/docs/start/self-hosting/1-click-deploy.mdx +++ b/packages/twenty-docs/docs/start/self-hosting/1-click-deploy.mdx @@ -1,6 +1,6 @@ --- title: 1-Click Deploy -sidebar_position: 2 +sidebar_position: 1 sidebar_custom_props: icon: TbBolt --- diff --git a/packages/twenty-docs/docs/start/self-hosting/cloud-providers.mdx b/packages/twenty-docs/docs/start/self-hosting/cloud-providers.mdx new file mode 100644 index 000000000..8250dfece --- /dev/null +++ b/packages/twenty-docs/docs/start/self-hosting/cloud-providers.mdx @@ -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. \ No newline at end of file diff --git a/packages/twenty-docs/docs/start/self-hosting/docker-compose.mdx b/packages/twenty-docs/docs/start/self-hosting/docker-compose.mdx index 88ce97498..470633281 100644 --- a/packages/twenty-docs/docs/start/self-hosting/docker-compose.mdx +++ b/packages/twenty-docs/docs/start/self-hosting/docker-compose.mdx @@ -1,6 +1,6 @@ --- title: Docker Compose -sidebar_position: 1 +sidebar_position: 3 sidebar_custom_props: icon: TbBrandDocker --- diff --git a/packages/twenty-docs/docs/start/self-hosting/upgrade-guide.mdx b/packages/twenty-docs/docs/start/self-hosting/upgrade-guide.mdx index 23a920f1c..0afcf554d 100644 --- a/packages/twenty-docs/docs/start/self-hosting/upgrade-guide.mdx +++ b/packages/twenty-docs/docs/start/self-hosting/upgrade-guide.mdx @@ -1,6 +1,6 @@ --- title: Upgrade guide -sidebar_position: 3 +sidebar_position: 4 sidebar_class_name: coming-soon sidebar_custom_props: icon: TbServer