# Advanced Orchestrated container deployment (Helm)

{% hint style="info" %}
**Estimated time:** Completing this deployment guide is expected to take approximately **3 hours**. This estimate may vary based on individual familiarity with the technology stack involved and the complexity of your organization's environment.
{% endhint %}

This guide provides step-by-step instructions for deploying CARTO Self-Hosted on a Kubernetes cluster.

## 1. Prerequisites

Before you begin, ensure you have the following tools and assets ready.

To deploy CARTO Self-Hosted on Kubernetes, you need:

* A CARTO Self-Hosted installation package containing your environment configuration and a license key. The package has two YAML files: `carto-secrets.yaml` and `carto-values.yaml` If you don't have it yet, you can ask for it at <support@carto.com>.
* A domain you own, to which you can add a DNS record.
* A Kubernetes cluster. To create a cluster, see documentation on [Google Cloud Platform](https://cloud.google.com/kubernetes-engine/docs/how-to/creating-a-zonal-cluster), [AWS](https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html), and [Azure](https://learn.microsoft.com/en-us/azure/aks/tutorial-kubernetes-deploy-cluster?tabs=azure-cli). This cluster **must fit** our hardware and software [requirements](https://docs.carto.com/key-concepts/deployment-requirements#hardware-requirements) for Kubernetes.
* A working installation of kubectl. To install kubectl, see documentation on [Google Cloud Platform](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl#generate_kubeconfig_entry), [AWS](https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html), and [Azure](https://learn.microsoft.com/en-us/azure/aks/learn/quick-kubernetes-deploy-cli#connect-to-the-cluster).
* [Kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) installed on your CLI.
* A working [installation of Helm v3](https://helm.sh/docs/intro/install/) on version 3.6.0 or later.
* [Troubleshoot.sh ](https://troubleshoot.sh/docs/)installed to run the preflight checks (view [step 4](#id-4.-execute-the-preflight-checks).)

### 1.2 Required Tools

* A working installation of kubectl. To install kubectl, see documentation on [Google Cloud Platform](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl#generate_kubeconfig_entry), [AWS](https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html), and [Azure](https://learn.microsoft.com/en-us/azure/aks/learn/quick-kubernetes-deploy-cli#connect-to-the-cluster).
* [Kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) installed on your CLI.
* A working [installation of Helm v3](https://helm.sh/docs/intro/install/) on version 3.6.0 or later.
* [Troubleshoot.sh ](https://troubleshoot.sh/docs/)installed to run the preflight checks (view [step 4](#id-4.-execute-the-preflight-checks).)

### 1.3 Required assets

* **CARTO Installation Package**: You should have received a package from CARTO support containing two key files:
  * `carto-values.yaml`: Contains the base configuration.
  * `carto-secrets.yaml`: Contains your license and private credentials.
  * If you don't have it yet, you can ask for it at <support@carto.com>.
* **Kubernetes Cluster**: A running cluster that meets CARTO's hardware and software requirements.
  * To create a cluster, see documentation on [Google Cloud Platform](https://cloud.google.com/kubernetes-engine/docs/how-to/creating-a-zonal-cluster), [AWS](https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html), and [Azure](https://learn.microsoft.com/en-us/azure/aks/tutorial-kubernetes-deploy-cluster?tabs=azure-cli). This cluster **must fit** our hardware and software [requirements](https://docs.carto.com/key-concepts/deployment-requirements#hardware-requirements) for Kubernetes.
* **External PostgreSQL Database**: A running PostgreSQL instance accessible from your cluster.
* **Cloud Storage Buckets**: Pre-configured storage buckets on a supported provider (GCS, S3, or Azure Blob). You must visit the guide: [Configure your own buckets (Helm)](https://docs.carto.com/carto-self-hosted/guides/guides-helm/configure-your-own-buckets)
* **DNS name and Certificate**: You'll need a domain you own, to which you can add a DNS record.

## 2. Add your minimal customization

### 2.1 Understanding and reparing `customizations.yaml`

Throughout this guide, you will be asked to configure various settings for your specific environment. All these settings will be placed in a single file that you create called `customizations.yaml`.

#### 2.1.1 Overview

The CARTO Helm chart uses a layered configuration approach:

1. `carto-values.yaml`: Contains the default application settings (provided by CARTO). Do not edit this file.
2. `carto-secrets.yaml`: Contains your license and private credentials (provided by CARTO). Do not edit this file.
3. `customizations.yaml`: Contains your overrides and environment-specific settings. This is the only file you will modify.

When you run the installation, Helm merges these files, with the values in your `customizations.yaml` taking precedence.

#### **2.1.2 Getting started: create empty** `customizations.yaml`

Before you begin, create an empty file in your working directory named `customizations.yaml`. We recommend to have this along side the Customer Packages files (`carto-values.yaml` & `carto-secrets.yaml`)

{% hint style="warning" %}
**As you follow this guide** and complete each prerequisite (e.g., defining your domain, configuring your database, setting up storage buckets), **you will add the corresponding configuration snippets** to this single `customizations.yaml` file.
{% endhint %}

#### 2.1.3 `Customizations.yaml` example:

After completing the "Domain" and "External Database" steps, your `customizations.yaml` file will look like this:

```yaml
# customizations.yaml

# Configuration from the "Domain" step
appConfigValues:
  selfHostedDomain: "my.domain.com"

# Configuration from the "External Database" step
internalPostgresql:
  enabled: false
externalPostgresql:
  host: "your-db-host.rds.amazonaws.com"
  user: "carto_workspace_admin"
  existingSecret: "carto-postgresql"
  existingSecretPasswordKey: "user-password"
  database: "carto_workspace"
  port: "5432"

# You will continue adding more settings to this file...
```

### 2.2 Domain

Add to the <mark style="color:orange;">`customizations.yaml`</mark> file the **domain name** you want to use

```
appConfigValues:
  selfHostedDomain: "my.domain.com"
```

{% hint style="info" %}
A full domain is required. You cannot install CARTO in a domain path like <https://my.domain.com/carto>
{% endhint %}

## 3. External database

### 3.1 Overview

At this point, we are setting up the configuration of the [external database](https://docs.carto.com/key-concepts/deployment-requirements#external-database). You need to provide:

* An existing logical database
* A user with full permissions over the logical database
* The password for the user so that we can connect to the database

{% hint style="info" %}
If you're installing in EKS and you'd like to use EKS Pod Identity to authenticate to your RDS PostgreSQL database, follow [this guide](https://docs.carto.com/carto-self-hosted/guides/guides-helm/use-eks-pod-identity-in-aws). When you setup EKS Pod Identity you can skip this steps, you'll need to follow the details in the specified guide.
{% endhint %}

1. Create the secret with the PostgreSQL user password:

{% hint style="warning" %}
Avoid URI-special characters (`@`, `#`, `%`, `/`, `?`, `&`) in the password. See [Deployment Requirements](https://docs.carto.com/key-concepts/deployment-requirements#external-databases) for details.
{% endhint %}

```sh
kubectl create secret generic \
  carto-postgresql \
  --from-literal=user-password='<userPassword>'
```

2. Edit `customizations.yaml`:
   1. **user**: The carto user that you created.
   2. **user-password**: The password for the user that you created.
   3. **database**: The database that you've created.

```yaml
internalPostgresql:
  # Disable the internal Postgres
  enabled: false
externalPostgresql:
  host: <YourServerIPorDNS>
  user: "carto_worskpace_admin"
  existingSecret: "carto-postgresql"
  existingSecretPasswordKey: "user-password"
  database: "carto_workspace"
  port: "5432"
```

#### 3.2 Optional: Database connection SSL

In some scenarios, an SSL connection to the external database is required. In that case, you should add to `customizations.yaml`:

```yaml
externalPostgresql:
  sslEnabled: true
  # Only applies if your PostgreSQL SSL certificate it's self-signed
    sslCA: |
    -----BEGIN CERTIFICATE-----
    ....
    -----END CERTIFICATE-----
```

{% hint style="danger" %}
Mutual TLS connections between the external database and the APIs are not supported, so client certificates can't be configured on your external database
{% endhint %}

{% hint style="warning" %}
**Azure PostgreSQL Flexible Server**:

* Make sure you add ownership over the carto database and all privileges over the schema.
* Extensions must be allowlisted before creation. Run `az postgres flexible-server parameter set --name azure.extensions --value "pgcrypto"` then create the extension as the admin user. See [Azure docs](https://go.microsoft.com/fwlink/?linkid=2301063).
  {% endhint %}

## 4. Configure your storage buckets

CARTO Self-hosted platform needs access to some storage buckets to save some resources needed by the platform. These buckets are in charge of storing assets such as imported datasets, map snapshots and custom markers.

You can create and use your own storage buckets in any of the following supported storage providers:

* [Google Cloud Storage](https://cloud.google.com/storage)
* [AWS S3](https://aws.amazon.com/s3/)
* [Azure Blob Storage](https://azure.microsoft.com/es-es/products/storage/blobs/)

And in order to configure them, there is a detailed guide ["Configure your Own Buckets (Helm)"](https://docs.carto.com/carto-self-hosted/guides/guides-helm/configure-your-own-buckets) available that you should follow to complete the Self-Hosted configuration process.

## 5. Add Helm repository

At this point you should have your customization.yaml file populated with:

* Your metadata database connection information.
* Your buckets connection attributes

To add the CARTO Helm repo:

1. Type this command to add the helm repository

```bash
# Add the carto repo.
helm repo add carto https://helm.carto.com
```

2. If you already have the repo, update it:

```
helm repo update carto
```

3. Search for the repo to confirm you can access the carto chart.

```
helm search repo carto -l
```

Create a file <mark style="color:orange;">`customizations.yaml`</mark> with the domain name you want to use

* **user**: The carto user that you created.
* **user-password**: The password for the user that you created.
* **database**: The database that you've created.

In some scenarios, an SSL connection to the external database is required. In that case, you should add to `customizations.yaml`:

## 6. Execute the preflight checks

Before installing CARTO Self-Hosted, you can use preflight checks to validate your configuration. Keep iterating until all checks pass—this ensures that your environment is fully prepared for installation.

As mentioned in the Requisites, you need to install the Trobleshoot.sh tools in order to run the Preflight checks. To install this tool using bash, you have to run the following commands:

1. Install the preflight and support-bundle plugins:

```
curl https://krew.sh/support-bundle | bash
curl https://krew.sh/preflight | bash
```

2. Check version

```
kubectl preflight version
kubectl support-bundle version
```

Use the following command to **run the** **preflight checks**:

```bash
helm template \
  carto \
  carto/carto \
  -f carto-values.yaml \
  -f carto-secrets.yaml \
  -f customizations.yaml -n <NAMESPACE> | kubectl preflight -
```

The previous command performs validation checks to ensure that your <mark style="color:orange;">customizations.yaml</mark> file is correctly configured for installing the CARTO platform and that you're infrastructure is ready to host a CARTO Self-Hosted installation.

Once the checks are complete, you will see the following output in your terminal:

If you encounter any issues during the preflight checks, verify the following:

* **PostgreSQL Configuration**: Ensure your PostgreSQL credentials are correctly set up and that the database is accessible from your cluster.
* **External Proxy**: If using an [external proxy](https://docs.carto.com/carto-self-hosted/guides/guides-helm/configure-an-external-proxy), confirm that it is correctly configured for connecting to external domains.
* **Egress Requirements**: Check that your environment meets the necessary [egress requirements](https://docs.carto.com/carto-self-hosted/key-concepts/deployment-requirements) for CARTO Self-Hosted.
* **Storage Configuration**: Verify that your [storage buckets](https://docs.carto.com/carto-self-hosted/guides/guides-helm/configure-your-own-buckets) are correctly set up and that the provided credentials are valid.

By addressing these areas, you can resolve common setup issues and proceed with a smooth installation.

## 7. Install CARTO

Use the following command to install CARTO in your Kubernetes cluster.

```bash
helm install \
  carto \
  carto/carto \
  -f carto-values.yaml \
  -f carto-secrets.yaml \
  -f customizations.yaml -n <NAMESPACE>
```

After installing CARTO, verify you have the required pods running by running the following command (It can take up to 5 minutes to have everything running):

```bash
kubectl get pods
NAME                                          READY   STATUS    RESTARTS   AGE
carto-accounts-www-5778cc7c7f-l42rx           1/1     Running   0          3m6s
carto-cdn-invalidator-sub-bd69b7b96-c6mmt     1/1     Running   0          3m5s
carto-http-cache-864d54db46-56xs2             1/1     Running   0          3m6s
carto-import-api-c845759bd-9m7bd              1/1     Running   0          3m7s
carto-import-worker-5bf456f684-b9mg9          1/1     Running   0          3m7s
carto-lds-api-676dcdd9c8-lqmcf                1/1     Running   0          3m6s
carto-maps-api-5699c99fb6-9dj5s               1/1     Running   0          3m4s
carto-maps-api-5699c99fb6-msfr2               1/1     Running   0          3m4s
carto-notifier-cf8f7576d-5wnm5                1/1     Running   0          3m5s
carto-redis-master-0                          1/1     Running   0          3m6s
carto-router-7578cdd848-mbrxw                 2/2     Running   0          3m5s
carto-sql-worker-64d7598c56-zt5xr             1/1     Running   0          3m6s
carto-workspace-api-66dbbdb69-cqd4p           1/1     Running   0          3m5s
carto-workspace-api-66dbbdb69-z4tj9           1/1     Running   0          3m4s
carto-workspace-subscriber-54f9cbc589-qk94m   1/1     Running   0          3m5s
carto-workspace-www-7bb5f78577-k66rr          1/1     Running   0          3m6s
```

## 8. Optional: Port-forward to localhost

After configuring CARTO App ingress you can manually test you can reach the platform by using forwarding to localhost.

Once all the services are running, verify the installation by port forwarding to `localhost`. Execute the following:

```
export CARTO_HOST_IP=127.0.0.1
export CARTO_HTTP_PORT=80
export CARTO_HTTPS_PORT=443
kubectl port-forward --namespace default svc/carto-router 80:80 443:443
```

Add to your <mark style="color:orange;">`/etc/hosts`</mark> the domain you will use:

`echo "127.0.0.1 my.domain.com" >> /etc/hosts`

## 9. Configure an HTTPS endpoint

The entry point to the CARTO Self-Hosted is through the `router` service. By default, it is configured in `ClusterIP` mode, and it's only accessible in your machine with [kubectl port-forward](https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/)).

The first step here is to make the router accessible using a `LoadBalancer`. This provides an externally-accessible IP address that we will later configure our DNS.

Add to `customizations.yaml` based on the cloud you are using:

{% tabs %}
{% tab title="Google GKE" %}

```yaml
router:
  service:
    type: LoadBalancer
```

{% endtab %}

{% tab title="AWS EKS" %}

```yaml
router:
  service:
    type: LoadBalancer
    annotations:
      # https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/guide/service/nlb/
      # https://kubernetes.io/docs/concepts/services-networking/service/#ssl-support-on-aws
      service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "605"
```

{% endtab %}

{% tab title="Azure AKS" %}

```yaml
router:
  service:
    type: LoadBalancer
    annotations:
      # https://docs.microsoft.com/en-us/azure/aks/load-balancer-standard#additional-customizations-via-kubernetes-annotations
      service.beta.kubernetes.io/azure-load-balancer-tcp-idle-timeout: "10"
```

{% endtab %}
{% endtabs %}

After that, upgrade the cluster:

```bash
helm upgrade carto \
  carto/carto \
  -f carto-values.yaml \
  -f carto-secrets.yaml \
  -f customizations.yaml
```

Get the <mark style="color:orange;">EXTERNAL-IP</mark> of your CARTO Self-Hosted deployment with this command:

```
$ kubectl get svc --namespace default -w carto-router

NAME           TYPE           CLUSTER-IP   EXTERNAL-IP     PORT(S)                      AGE
carto-router   LoadBalancer   10.64.3.11   34.136.204.51   80:32339/TCP,443:32602/TCP   8h
```

At this point, you need to configure a DNS record that points <mark style="color:orange;">my.domain.com</mark> to the EXTERNAL-IP (34.136.204.51).

**Use your own TLS certificate**

By default, the package generates a self-signed certificate with a validity of 365 days.

If you want to add your own certificate you need to create a secret:

```bash
kubectl create secret tls -n <namespace> <certificate name> \
  --cert=path/to/cert/file \
  --key=path/to/key/file
```

Edit `customizations.yaml`:

```
tlsCerts:
  httpsEnabled: true
  autoGenerate: false
  existingSecret:
    name: "<certificate name>"
    keyKey: "tls.key"
    certKey: "tls.crt"
```

Apply the changes:

```bash
helm upgrade carto \
  carto/carto \
  -f carto-values.yaml \
  -f carto-secrets.yaml \
  -f customizations.yaml
```

{% hint style="info" %}
If your TLS certificate key is protected with a passphrase the CARTO Self-hosted installation won't be able to work as expected. You can easily generate a new key file without passphrase protection using the following command:

```bash
openssl rsa -in keyfile_with_passphrase.key -out new_keyfile.key
```

{% endhint %}

## 10. Post-installation checks

In order to verify CARTO Self Hosted was correctly installed, and it's functional, we recommend performing the following checks:

1. Sign in to your Self Hosted, create a user and a new organization.
2. Go to the `Connections` page, in the left-hand menu, create a new connection to one of the available providers.
3. Go to the `Data Explorer` page, click on the `Upload` button right next to the `Connections` panel. Import a dataset from a local file.
4. Go back to the `Maps` page, and create a new map.
5. In this new map, add a new layer from a table using the connection created in step 3.
6. Create a new layer from a SQL Query to the same table. You can use a simple query like:

```
SELECT * FROM <dataset_name.table_name> LIMIT 100;
```

7. Create a new layer from the dataset imported in step 4.
8. Make the map public, copy the sharing URL and open it in a new incognito window.
9. Go back to the `Maps` page, and verify your map appears there, and the map thumbnail represents the latest changes you made to the map.

**Congrats**! Once you've configured your custom buckets, you should have a production-ready deployment of CARTO Self-Hosted at <mark style="color:orange;">`https://my.domain.com`</mark>

{% hint style="info" %}
You may notice that the **onboarding experience** (demo maps, demo workflows...) and the **Data Observatory-automated features** (subscriptions, enrichment...) are disabled by default in your new organization, because the CARTO Data Warehouse is not enabled.

If you'd like to enable the onboarding experience and the Data Observatory features, follow the [guide to enable the CARTO Data Warehouse](https://docs.carto.com/carto-self-hosted/guides/guides/enable-the-carto-data-warehouse) or contact <support@carto.com>.

If you prefer not to enable the CARTO Data Warehouse, you can still use the Data Observatory without the UI features: after getting in touch, our team can deliver the data (both premium and public subscriptions) manually to your data warehouse.
{% endhint %}

## Analytics Toolbox in CARTO Self-Hosted

To fully leverage CARTO's capabilities you need to gain access to the Analytics Toolbox functions. This step is crucial to fully leverage CARTO's capabilities. Please refer to the documentation of your data warehouse provider for detailed instructions:

* [Google BigQuery](https://docs.carto.com/data-and-analysis/analytics-toolbox-for-bigquery/getting-access)
* [Amazon Redshift](https://docs.carto.com/data-and-analysis/analytics-toolbox-for-redshift/getting-access)
* [Snowflake](https://docs.carto.com/data-and-analysis/analytics-toolbox-for-snowflake/getting-access)
* [Databricks](https://docs.carto.com/data-and-analysis/analytics-toolbox-for-databricks)
* [PostgreSQL](https://docs.carto.com/data-and-analysis/analytics-toolbox-for-postgresql/getting-access)

## Troubleshooting

The following standard commands of kubectl could be used to debug possible issues that might arise:

`kubectl logs` and `kubectl describe`

#### Database

The container <mark style="color:orange;">workspace-migrations</mark> (included at the pod carto-workspace-api-\*) will be responsible for creating a new user <mark style="color:orange;">carto\_worskpace\_admin</mark> and a database <mark style="color:orange;">carto\_workspace.</mark>

To debug possible errors with the connection of the external database you might need to check the logs of this container:

```bash
kubectl logs carto-workspace-api-585f4bbd7c-qt65g -c workspace-migrations
```

For further assistance check our [Support](https://docs.carto.com/carto-self-hosted/support) page.
