> ## Documentation Index
> Fetch the complete documentation index at: https://astronomer.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Install the Astro Private Cloud Control Plane

Use this guide to deploy the Astro Private Cloud (APC) control plane with the Helm-based Astronomer platform charts. The control plane hosts central management services such as the APC UI, Houston API, monitoring coordination, and authentication.

<Info>If your organization needs time to issue TLS certificates, configure DNS, or approve firewall changes, review the data plane installation guide in parallel. You can request the control plane and data plane prerequisites at the same time so the clusters are ready when the infrastructure tickets close.</Info>

## Overview

Astro Private Cloud supports two deployment patterns:

* **Split plane:** Deploy a dedicated control plane using this guide, then provision one or more data planes with [Install a Data Plane](/docs/astro-private-cloud/v-1-x/install-data-plane). This separation keeps management services isolated from workload execution and lets you scale each plane independently.
* **Unified mode:** Run both control plane and data plane services inside a single cluster using [Install in Unified Mode](/docs/astro-private-cloud/v-1-x/install-unified). Unified mode is useful for labs or proofs-of-concept but concentrates failures and resource usage.

Choose the pattern that matches your reliability and compliance requirements. After selecting a pattern, determine how many APC environments you need. An environment refers to the pairing of control plane and its associated data planes. In unified mode, this maps to a single Kubernetes cluster, whereas in split mode, the environment is the combination of the control plane and all the registered data plane Kubernetes clusters.

Each APC environment can host multiple Airflow Deployments, potentially on multiple data planes. Common types include:

* **Sandbox**: The lowest environment that contains no sensitive data, used only by system-administrators to experiment, and not subject to change control.
* **Development**: User-accessible environment that is subject to most of the same restrictions of higher environments, with relaxed change control rules.
* **Staging**: All network, security, and patch versions are maintained at the same level as in the production environment. However, it provides no availability guarantees and includes relaxed change control rules.
* **Production**: The production instance hosts your production Airflow environments. You can choose to host development Airflow environments here or in environments with lower levels of support and restrictions.

Create a project folder for every environment you plan to host to contain its configuration files. For example, if you want to install a development environment, create a folder named `~/astronomer-dev/control-plane`.

<Info>Certain files in the project directory might contain secrets when you set up your sandbox or development environments. For your first install, keep these secrets in a secure place on a suitable machine. As you progress to higher environments, such as staging or production, secure these files separately in a vault and use the remaining project files in your directory to serve as the basis for your CI/CD deployment.</Info>

## Prerequisites

<Tabs>
  <Tab title="EKS on AWS">
    <Info>The following prerequisites apply when running Astro Private Cloud on Amazon EKS. See the **Other** tab if you run a different version of Kubernetes on AWS.</Info>

    * An EKS Kubernetes cluster, running a version of Kubernetes certified as compatible on the [Kubernetes Version Compatibility Reference](/docs/astro-private-cloud/v-1-x/kubernetes-version-support) that provides the following components:
      * The [Amazon EBS CSI driver](https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html) (or an alternative CSI) must be installed on the Kubernetes Cluster.
      * An AWS Load Balancer Controller for the IP target type is required for all private Network Load Balancers (NLBs). See [Installing the AWS Load Balancer Controller add-on](https://docs.aws.amazon.com/eks/latest/userguide/aws-load-balancer-controller.html).
    * A PostgreSQL instance, accessible from your Kubernetes cluster, and running a version of Postgres certified as compatible on the [Version Compatibility Reference](/docs/astro-private-cloud/v-1-x/version-compatibility-reference).
    * PostgreSQL superuser permissions.
    * Permission to create and modify resources on AWS.
    * Permission to generate a certificate that covers a defined set of subdomains.
    * An SMTP service and credentials. For example, Mailgun or Sendgrid.
    * A machine meeting the following criteria with access to the Kubernetes API Server:
      * The [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html).
      * (Optional) [`eksctl`](https://eksctl.io/) for creating and managing your Astronomer cluster on EKS.
      * Network access to the Kubernetes API Server - either direct access or VPN.
      * Network access to load-balancer resources that are created when Astro Private Cloud is installed later in the procedure - either direct access or VPN.
      * Configured to use the DNS servers where Astro Private Cloud DNS records can be created.
      * [Helm (minimum v3.6)](https://helm.sh/docs/intro/install).
      * The [Kubernetes CLI (kubectl)](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
    * (Situational) The [OpenSSL CLI](https://www.openssl.org/docs/man1.0.2/man1/openssl.html) might be required to troubleshoot certain certificate-related conditions.
  </Tab>

  <Tab title="GKE on GCP">
    <Info>The following prerequisites apply when running Astro Private Cloud on Google GKE. See the **Other** tab if you run a different version of Kubernetes on GCP.</Info>

    * A GKE Kubernetes cluster, running a version of Kubernetes listed as compatible on the [Kubernetes Version Compatibility Reference](/docs/astro-private-cloud/v-1-x/kubernetes-version-support).
    * A PostgreSQL instance, accessible from your Kubernetes cluster, and running a version of Postgres certified as compatible on the [Version Compatibility Reference](/docs/astro-private-cloud/v-1-x/version-compatibility-reference).
    * PostgreSQL superuser permissions.
    * Permission to create and modify resources on Google Cloud Platform.
    * Permission to generate a certificate that covers a defined set of subdomains.
    * An SMTP service and credentials. For example, Mailgun or Sendgrid.
    * A machine that meets the following criteria with access to the Kubernetes API Server:
      * [Google Cloud SDK](https://cloud.google.com/sdk/install).
      * Network access to the Kubernetes API Server - either direct access or VPN.
      * Network access to load-balancer resources that are created when Astro Private Cloud is installed later in the procedure - either direct access or VPN.
      * Configured to use the DNS servers where Astro Private Cloud DNS records can be created.
      * [Helm with minimum version 3.6](https://helm.sh/docs/intro/install).
      * The [Kubernetes CLI (kubectl)](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
    * (Situational) The [OpenSSL CLI](https://www.openssl.org/docs/man1.0.2/man1/openssl.html) might be required to troubleshoot certain certificate-related conditions.
  </Tab>

  <Tab title="AKS on Azure">
    <Info>The following prerequisites apply when running Astro Private Cloud on Azure AKS. See the **Other** tab if you run a different version of Kubernetes on Azure.</Info>

    * A Kubernetes cluster, running a version of Kubernetes listed as compatible on the [Kubernetes Version Compatibility Reference](/docs/astro-private-cloud/v-1-x/kubernetes-version-support).
    * A PostgreSQL instance, accessible from your Kubernetes cluster, and running a version of Postgres certified as compatible on the [Version Compatibility Reference](/docs/astro-private-cloud/v-1-x/version-compatibility-reference).
      * If your organization uses Azure Database for PostgreSQL as the database backend, you need to enable the `pg_trgm` extension using the Azure portal or the Azure CLI before you install Astro Private Cloud. If you don't enable the `pg_trgm` extension, the install fails. For more information about enabling the `pg_trgm` extension, see [PostgreSQL extensions in Azure Database for PostgreSQL - Flexible Server](https://docs.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-extensions).
    * PostgreSQL superuser permissions.
    * Permission to create and modify resources on Azure.
    * Permission to generate a certificate that covers a defined set of subdomains.
    * An SMTP service and credentials. For example, Mailgun or Sendgrid.
    * A machine meeting the following criteria with access to the Kubernetes API Server:
      * The [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest).
      * Network access to the Kubernetes API Server - either direct access or VPN.
      * Network access to load-balancer resources created when Astro Private Cloud is installed later in the procedure - either direct access or VPN.
      * Configured to use the DNS servers where Astro Private Cloud DNS records will be created.
      * [Helm (minimum v3.6)](https://helm.sh/docs/intro/install).
      * The [Kubernetes CLI (kubectl)](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
    * (Situational) The [OpenSSL CLI](https://www.openssl.org/docs/man1.0.2/man1/openssl.html) might be required to trouble-shoot certain certificate-related conditions.
  </Tab>

  <Tab title="Other">
    The following prerequisites apply when running Astro Private Cloud on Kubernetes.

    * A Kubernetes cluster. For versioning considerations, see [Kubernetes Version Compatibility Reference](/docs/astro-private-cloud/v-1-x/kubernetes-version-support).
    * A PostgreSQL instance accessible from your Kubernetes cluster. For versioning considerations, see [Version Compatibility Reference](/docs/astro-private-cloud/v-1-x/version-compatibility-reference).
    * PostgreSQL superuser permissions.
    * An SMTP service and credentials. For example, Mailgun or Sendgrid.
    * Permission to generate a certificate that covers a defined set of subdomains.
    * PostgreSQL superuser permissions.
    * The ability to create DNS records.
    * A machine with access to the Kubernetes API Server meeting the following criteria:
      * Network access to the Kubernetes API Server - either direct access or VPN.
      * Network access to load-balancer resources created when Astro Private Cloud is installed later in the procedure - either direct access or VPN.
      * Configured to use the DNS servers where Astro Private Cloud DNS records will be created.
      * [Helm (minimum v3.6)](https://helm.sh/docs/intro/install).
      * The [Kubernetes CLI (kubectl)](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
    * (Situational) The [OpenSSL CLI](https://www.openssl.org/docs/man1.0.2/man1/openssl.html) might be required to trouble-shoot certain certificate-related conditions.
  </Tab>
</Tabs>

<a id="decide-ingress-controller" />

### Ingress controller considerations

Astro Private Cloud requires a Kubernetes Ingress controller to function and provides an integrated Ingress controller by default.
Before installing, decide whether to use a third-party ingress controller or use Astronomer's integrated ingress controller.

Astronomer generally recommends you use the integrated Ingress controller, but Astro Private Cloud also supports certain third-party [ingress-controllers](/docs/astro-private-cloud/v-1-x/third-party-ingress-controllers).

Ingress controllers typically need elevated permissions, including a `ClusterRole`, to function. Specifically, the Astro Private Cloud Ingress controller requires the ability to:

* List all namespaces in the cluster.
* View ingresses in the namespaces.
* Retrieve secrets in the namespaces to locate and use private TLS certificates that service the ingresses.

If you have complex regulatory requirements, you might need to use an Ingress controller that's approved by your organization and disable Astronomer's integrated controller. You configure the Ingress controller during the installation.

<a id="create-valuesyaml" />

## Step 1: Create `values.yaml` from a template

Astro Private Cloud uses Helm to apply platform-level configurations. Choose your cloud provider tab below to copy a ready-to-use `values.yaml`. In this guide, you will customize the template to your requirements.

<Warning>
  As you work with the template configuration, keep the following in mind.

  * Do not make any changes to this file until instructed to do so in later steps.
  * **Do not** run `helm upgrade` or `upgrade.sh` until instructed to do so in later steps.
  * Fully complete the installation in this guide before following any configuration instructions on other Astronomer documentation pages.
</Warning>

<Tabs>
  <Tab title="EKS on AWS">
    ```yaml expandable wrap theme={null}
    ###########################################
    ### Astronomer global configuration for EKS
    ###########################################
    global:
      # Installation mode for the control plane
      plane:
        mode: control

      # Base domain for all control plane subdomains exposed through ingress
      baseDomain: env.astronomer.your.domain

      # For development or proof-of-concept, you can use an in-cluster database.
      # This NOT supported in production.
      # postgresqlEnabled: true

      # Name of secret containing TLS certificate, change if not using "astronomer-tls"
      # tlsSecret: astronomer-tls

      # List of secrets containing the cert.pem of trusted private certification authorities
      # Example command: `kubectl -n astronomer create secret generic private-root-ca --from-file=cert.pem=./private-root-ca.pem`
      # privateCaCerts:
      # - private-root-ca

      # Expose Postgres metrics for Prometheus to scrape
      # prometheusPostgresExporterEnabled: true

      # Enable sidecar logging by default
      loggingSidecar:
        enabled: true

      # Database SSL configuration
      ssl:
        # Enable SSL connection to Postgres -- must be false if using in-cluster database
        enabled: true

    #########################
    ### Ingress configuration
    #########################
    # nginx:
      # Static IP address the nginx ingress should bind to
      # loadBalancerIP: ~

      # Set privateLoadbalancer to 'false' to make nginx request a LoadBalancer on a public vnet
      # privateLoadBalancer: true

      # Dictionary of arbitrary annotations to add to the nginx ingress.
      # For full configuration options, see https://docs.nginx.com/nginx-ingress-controller/configuration/ingress-resources/advanced-configuration-with-annotations/
      # Change to 'elb' if your node group is private and doesn't utilize a NAT gateway
      # ingressAnnotations: {service.beta.kubernetes.io/aws-load-balancer-type: nlb}

      # If all subnets are private, auto-discovery may fail.
      # You must enter the subnet IDs manually in the annotation below.
      # service.beta.kubernetes.io/aws-load-balancer-subnets: subnet-id-1,subnet-id-2

    ################################
    ### Astronomer app configuration
    ################################
    astronomer:
      houston:
        upgradeDeployments:
          enabled: false

        # secret:
        # - envName: "EMAIL__SMTP_URL"  # Reference to the Kubernetes secret for SMTP credentials. Only required if email is used.
        #   secretName: "astronomer-smtp"
        #   secretKey: "connection"

        # Application configuration for Houston
        config:
          publicSignups: true  ## set to false immediately after initial system admin user created

          # Allowed user email domains for system level roles
          # allowedSystemLevelDomains: []

          # Default configuration for deployments.
          # Can be overridden on a per-data-plane basis.
          deployments:
            # Enable Airflow 3 deployments for clusters
            airflowV3:
              enabled: true

            # Allow deletions to immediately remove the database and namespace
            # hardDeleteDeployment: true

            # Allows you to set your release names
            # manualReleaseNames: true

            # Flag to enable using IAM roles (don't enter a specific role)
            # serviceAccountAnnotationKey: eks.amazonaws.com/role-arn

            # Required if dagOnlyDeployment is enabled
            # configureDagDeployment: true

            # Enables the API for updating deployments
            # enableUpdateDeploymentImageEndpoint: true
            # upsertDeploymentEnabled: true

          # email:
          #   enabled: false
          #   reply: noreply@your.domain

          # User authentication mechanism. One of the following should be enabled.
          auth:
            github:
              # Allow users authenticate with Github, enabled by default
              enabled: false
            # local:
            #   # Allow users and passwords in the Houston database, disabled by default
            #   enabled: false
            openidConnect:
              # okta:
              #   enabled: false
              # microsoft:
              #   enabled: false
              # adfs:
              #   enabled: false
              # custom:
              #   enabled: false
              google:
                # Allow users to authenticate with Google, enabled by default
                enabled: false
    ```
  </Tab>

  <Tab title="GKE on GCP">
    ```yaml expandable wrap theme={null}
    ###########################################
    ### Astronomer global configuration for GKE
    ###########################################
    global:
      # Installation mode for the control plane
      plane:
        mode: control

      # Base domain for all control plane subdomains exposed through ingress
      baseDomain: env.astronomer.your.domain

      # For development or proof-of-concept, you can use an in-cluster database.
      # This NOT supported in production.
      # postgresqlEnabled: true

      # Name of secret containing TLS certificate, change if not using "astronomer-tls"
      # tlsSecret: astronomer-tls

      # List of secrets containing the cert.pem of trusted private certification authorities
      # Example command: `kubectl -n astronomer create secret generic private-root-ca --from-file=cert.pem=./private-root-ca.pem`
      # privateCaCerts:
      # - private-root-ca

      # Expose Postgres metrics for Prometheus to scrape
      # prometheusPostgresExporterEnabled: true

      # Enable sidecar logging by default
      loggingSidecar:
        enabled: true

      # Database SSL configuration
      ssl:
        # Enable SSL connection to Postgres -- must be false if using in-cluster database
        enabled: true

    #########################
    ### Ingress configuration
    #########################
    nginx:
      # Static IP address the nginx ingress should bind to
      # loadBalancerIP: ~

      # Set privateLoadbalancer to 'false' to make nginx request a LoadBalancer on a public vnet
      # privateLoadBalancer: true

      # Dictionary of arbitrary annotations to add to the nginx ingress.
      # For full configuration options, see https://docs.nginx.com/nginx-ingress-controller/configuration/ingress-resources/advanced-configuration-with-annotations/
      # required for azure load balancer post Kubernetes 1.24
      ingressAnnotations:
          service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path: "/healthz"

    ################################
    ### Astronomer app configuration
    ################################
    astronomer:
      houston:
        upgradeDeployments:
          enabled: false

        # Application configuration for Houston
        config:
          publicSignups: true  ## set to false immediately after initial system admin user created

          # Allowed user email domains for system level roles
          # allowedSystemLevelDomains: []

          # Default configuration for deployments.
          deployments:
            # Enable Airflow 3 deployments for clusters
            airflowV3:
              enabled: true

            # Allow deletions to immediately remove the database and namespace
            # hardDeleteDeployment: true

            # Allows you to set your release names
            # manualReleaseNames: true

            # Flag to enable using IAM roles (don't enter a specific role)
            # serviceAccountAnnotationKey: iam.gke.io/gcp-service-account

            # Required if dagOnlyDeployment is enabled
            # configureDagDeployment: true

            # Enables the API for updating deployments
            # enableUpdateDeploymentImageEndpoint: true
            # upsertDeploymentEnabled: true

          # email:
          #   enabled: false
          #   reply: noreply@your.domain

          # secret:
          # - envName: "EMAIL__SMTP_URL"  # Reference to the Kubernetes secret for SMTP credentials. Only required if email is used.
          #   secretName: "astronomer-smtp"
          #   secretKey: "connection"

          # User authentication mechanism. One of the following should be enabled.
          auth:
            github:
              # Allow users authenticate with Github, enabled by default
              enabled: false
            # local:
            #   # Allow users and passwords in the Houston database, disabled by default
            #   enabled: false
            openidConnect:
              # okta:
              #   enabled: false
              # microsoft:
              #   enabled: false
              # adfs:
              #   enabled: false
              # custom:
              #   enabled: false
              google:
                # Allow users to authenticate with Google, enabled by default
                enabled: false
    ```
  </Tab>

  <Tab title="AKS on Azure">
    ```yaml expandable wrap theme={null}
    ###########################################
    ### Astronomer global configuration for AKS
    ###########################################
    global:
      # Installation mode for the control plane
      plane:
        mode: control

      # Base domain for all control plane subdomains exposed through ingress
      baseDomain: env.astronomer.your.domain

      # For development or proof-of-concept, you can use an in-cluster database.
      # This NOT supported in production.
      # postgresqlEnabled: true

      # Name of secret containing TLS certificate, change if not using "astronomer-tls"
      # tlsSecret: astronomer-tls

      # List of secrets containing the cert.pem of trusted private certification authorities
      # Example command: `kubectl -n astronomer create secret generic private-root-ca --from-file=cert.pem=./private-root-ca.pem`
      # privateCaCerts:
      # - private-root-ca

      # Expose Postgres metrics for Prometheus to scrape
      # prometheusPostgresExporterEnabled: true

      # Enable sidecar logging by default
      loggingSidecar:
        enabled: true

      # Database SSL configuration
      ssl:
        # Enable SSL connection to Postgres -- must be false if using in-cluster database
        enabled: true

    #########################
    ### Ingress configuration
    #########################
    # nginx:
      # Static IP address the nginx ingress should bind to
      # loadBalancerIP: ~

      # Set privateLoadbalancer to 'false' to make nginx request a LoadBalancer on a public vnet
      # privateLoadBalancer: true

      # Dictionary of arbitrary annotations to add to the nginx ingress.
      # For full configuration options, see https://docs.nginx.com/nginx-ingress-controller/configuration/ingress-resources/advanced-configuration-with-annotations/
      # ingressAnnotations: {}

    ################################
    ### Astronomer app configuration
    ################################
    astronomer:
      houston:
        upgradeDeployments:
          enabled: false

        # Application configuration for Houston
        config:
          publicSignups: true  ## set to false immediately after initial system admin user created

          # Allowed user email domains for system level roles
          # allowedSystemLevelDomains: []

          # Default configuration for deployments.
          deployments:
            # Enable Airflow 3 deployments for clusters
            airflowV3:
              enabled: true

            # Allow deletions to immediately remove the database and namespace
            # hardDeleteDeployment: true

            # Allows you to set your release names
            # manualReleaseNames: true

            # Flag to enable using IAM roles (don't enter a specific role)
            # serviceAccountAnnotationKey: iam.gke.io/gcp-service-account

            # Required if dagOnlyDeployment is enabled
            # configureDagDeployment: true

            # Enables the API for updating deployments
            # enableUpdateDeploymentImageEndpoint: true
            # upsertDeploymentEnabled: true

          # email:
          #   enabled: false
          #   reply: noreply@your.domain

          # secret:
          # - envName: "EMAIL__SMTP_URL"  # Reference to the Kubernetes secret for SMTP credentials. Only required if email is used.
          #   secretName: "astronomer-smtp"
          #   secretKey: "connection"

          # User authentication mechanism. One of the following should be enabled.
          auth:
            github:
              # Allow users authenticate with Github, enabled by default
              enabled: false
            # local:
            #   # Allow users and passwords in the Houston database, disabled by default
            #   enabled: false
            openidConnect:
              # okta:
              #   enabled: false
              # microsoft:
              #   enabled: false
              # adfs:
              #   enabled: false
              # custom:
              #   enabled: false
              google:
                # Allow users to authenticate with Google, enabled by default
                enabled: false
    ```
  </Tab>

  <Tab title="Other">
    ```yaml expandable wrap theme={null}
    #################################################################
    ### Astronomer global configuration for other types of Kubernetes
    #################################################################
    global:
      # Installation mode for the control plane
      plane:
        mode: control

      # Base domain for all control plane subdomains exposed through ingress
      baseDomain: env.astronomer.your.domain

      # For development or proof-of-concept, you can use an in-cluster database.
      # This NOT supported in production.
      # postgresqlEnabled: true

      # Name of secret containing TLS certificate, change if not using "astronomer-tls"
      # tlsSecret: astronomer-tls

      # List of secrets containing the cert.pem of trusted private certification authorities
      # Example command: `kubectl -n astronomer create secret generic private-root-ca --from-file=cert.pem=./private-root-ca.pem`
      # privateCaCerts:
      # - private-root-ca

      # Expose Postgres metrics for Prometheus to scrape
      # prometheusPostgresExporterEnabled: true

      # Enable sidecar logging by default
      loggingSidecar:
        enabled: true

      # Database SSL configuration
      ssl:
        # Enable SSL connection to Postgres -- must be false if using in-cluster database
        enabled: true

    #########################
    ### Ingress configuration
    #########################
    # nginx:
      # Static IP address the nginx ingress should bind to
      # loadBalancerIP: ~

      # Set privateLoadbalancer to 'false' to make nginx request a LoadBalancer on a public vnet
      # privateLoadBalancer: true

      # Dictionary of arbitrary annotations to add to the nginx ingress.
      # For full configuration options, see https://docs.nginx.com/nginx-ingress-controller/configuration/ingress-resources/advanced-configuration-with-annotations/
      # ingressAnnotations: {}

    ################################
    ### Astronomer app configuration
    ################################
    astronomer:
      houston:
        upgradeDeployments:
          enabled: false

        # Application configuration for Houston
        config:
          publicSignups: true  ## set to false immediately after initial system admin user created

          # Allowed user email domains for system level roles
          # allowedSystemLevelDomains: []

          # Default configuration for deployments.
          deployments:
            # Enable Airflow 3 deployments for clusters
            airflowV3:
              enabled: true

            # Allow deletions to immediately remove the database and namespace
            # hardDeleteDeployment: true

            # Allows you to set your release names
            # manualReleaseNames: true

            # Flag to enable using IAM roles (don't enter a specific role)
            # serviceAccountAnnotationKey: iam.gke.io/gcp-service-account

            # Required if dagOnlyDeployment is enabled
            # configureDagDeployment: true

            # Enables the API for updating deployments
            # enableUpdateDeploymentImageEndpoint: true
            # upsertDeploymentEnabled: true

          # email:
          #   enabled: false
          #   reply: noreply@your.domain

          # secret:
          # - envName: "EMAIL__SMTP_URL"  # Reference to the Kubernetes secret for SMTP credentials. Only required if email is used.
          #   secretName: "astronomer-smtp"
          #   secretKey: "connection"

          # User authentication mechanism. One of the following should be enabled.
          auth:
            github:
              # Allow users authenticate with Github, enabled by default
              enabled: false
            # local:
            #   # Allow users and passwords in the Houston database, disabled by default
            #   enabled: false
            openidConnect:
              # okta:
              #   enabled: false
              # microsoft:
              #   enabled: false
              # adfs:
              #   enabled: false
              # custom:
              #   enabled: false
              google:
                # Allow users to authenticate with Google, enabled by default
                enabled: false
    ```
  </Tab>
</Tabs>

<Info>Email delivery is disabled by default. If you want to enable it, you can configure it in a later step: [Configure outbound SMTP email](#configure-outbound-smtp-email).</Info>

<Info>The snippets in this section leave `astronomer.houston.config.publicSignups: true` so you can create the initial administrator account. You will lock down account creation in [Disable anonymous account creation](#disable-anonymous-account-creation).</Info>
<Warning>The snippets in this section do not enable any authentication mechanisms. You need to enable at least one mechanism to login as the first admin user.</Warning>

<a id="choose-base-domain" />

## Step 2: Choose and configure a base domain

When you install Astro Private Cloud, it creates a variety of services that your users access to manage, monitor, and run Airflow.

Choose a base domain such as `astronomer.example.com`, `astro-sandbox.example.com`, `astro-prod.example.internal` for which:

* You have the ability to create and edit DNS records
* You have the ability to issue TLS certificates
* The following hostnames are used by the Control Plane components:
  * `app.<base-domain>`
  * `houston.<base-domain>`
  * `alertmanager.<base-domain>`
  * `prometheus.<base-domain>`

The base domain itself does not need to be available and can point to another service not associated with Astronomer or Airflow.

When choosing a base domain, consider the following:

* The name you choose must be resolvable by both your users and Kubernetes itself.
* All data planes in the environment must be hosted as a sub-domain under this common base domain, e.g. `dp-01.<base-domain>`, so ensure you can create DNS records and issue TLS certificates for subdomains of this base domain.
* You need to have or obtain a TLS certificate that is recognized as valid by your users. If your organization hosts a registry for APC images, ensure the TLS certificate is trusted by Kubernetes as well.
* Wildcard certificates are only valid one level deep. For example, an ingress controller that uses a certificate called `*.example.com` can provide service for `app.example.com` but not `app.astronomer-dev.example.com`.
* The bottom-level sub-domains, such as `app` and `prometheus`, are fixed and cannot be changed.

The base domain is visible to end users. They can view the base domain in the following scenarios:

* When users access the Astro Private Cloud UI. For example, `https://app.sandbox-astro.example.com`.
* When users authenticate to the Astro CLI. For example, `astro login sandbox-astro.example.com`.

<Info>If you install Astro Private Cloud on OpenShift and also want to use OpenShift's integrated ingress controller, you can use the hostname of the default OpenShift ingress controller as your base domain, such as `app.apps.<OpenShift-domain>`. Doing this requires permission to reconfigure the route admission policy for the standard ingress controller to `InterNamespaceAllowed`. See [Third Party Ingress Controller - Configuration notes for OpenShift](/docs/astro-private-cloud/v-1-x/third-party-ingress-controllers#required-environment-configuration-openshift) for additional information and options.</Info>

### Configure the base domain

Locate the `global.baseDomain` in your `values.yaml` file and change it to your base domain as shown in the following example:

```yaml wrap theme={null}
global:
  # Base domain for all subdomains exposed through ingress
  baseDomain: sandbox-astro.example.com
```

<a id="create-astronomer-namespace" />

## Step 3: Create the Astro Private Cloud platform namespace

In your Kubernetes cluster, create a [Kubernetes namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) to contain the Astro Private Cloud platform. This guide refers to this namespace as `<apc platform namespace>` below. For example, if you chose `apc-cp` you would create the namespace as follows:

```sh wrap theme={null}
kubectl create namespace apc-cp
```

<a id="astronomer-tls-certificate" />

## Step 4: Request and validate an Astronomer TLS certificate

To install Astro Private Cloud, you need a TLS certificate that is valid for several domains. One of the domains is the primary name on the certificate, also known as the common name (CN). The additional domains are equally valid, supplementary domains known as Subject Alternative Names (SANs).

Astro Private Cloud requires a private certificate to be present in the Astro Private Cloud platform namespace, even if you use a third-party ingress controller that doesn't otherwise require it.

<a id="request-a-certificate-bundle" />

### Request an ingress controller TLS certificate

Request a TLS certificate for the control plane from your security team for Astro Private Cloud. In your request, include the following:

* Your chosen base domain as the Common Name (CN). If your certificate authority will not issue certificates for the bare base domain, use `app.<base-domain>` as the CN instead.
* *Either* request a wildcard SAN of `*.<base-domain>` (plus an explicit SAN for `<base-domain>`) *or* list each hostname individually:
  * `app.<base-domain>` (omit if already used as the Common Name)
  * `houston.<base-domain>`
  * `prometheus.<base-domain>`
  * `alertmanager.<base-domain>` (required if you keep the integrated Alertmanager enabled)

<Warning>Wildcards only cover a single DNS segment. You cannot reuse a data plane wildcard such as `*.<domainPrefix>.<base-domain>` for the control-plane hosts (`app.<base-domain>`, `houston.<base-domain>`, and so on); request a certificate that explicitly matches the control-plane names listed above.</Warning>

* Request the following return format:
  * A `key.pem` containing the private key in pem format
  * **Either** a `full-chain.pem` (containing the public certificate and additional certificates required to validate it, in pem format) **or** a bare `cert.pem` and explicit affirmation that there are no intermediate certificates and that the public certificate is the full chain.
  * **Either** the `private-root-ca.pem` in pem format of the private Certificate Authority used to create your certificate or a statement that the certificate is signed by a public Certificate Authority.

### Validate the received certificate and associated items

Ensure that you received each of the following three items:

* A `key.pem` containing the private key in pem format.
* **Either** a `full-chain.pem`, in pem format, that contains the public certificate and additional certificates required to validate it **or** a bare `cert.pem` and explicit affirmation that there are no intermediate certificates and that the public certificate is the full chain.
* **Either** the `private-root-ca.pem` in pem format of the private Certificate Authority used to create your certificate **or** a statement that the certificate is signed by public Certificate Authority.

To validate that your security team generated the correct certificate, run the following command using the `openssl` CLI:

```sh wrap theme={null}
openssl x509 -in  <your-certificate-filepath> -text -noout
```

This command will generate a report. If the `X509v3 Subject Alternative Name` section of this report includes either a single `*.<base-domain>` wildcard domain or all subdomains, then the certificate creation was successful.

Confirm that your full-chain certificate chain is ordered correctly. To determine your certificate chain order, run the following command using the `openssl` CLI:

```sh wrap theme={null}
openssl crl2pkcs7 -nocrl -certfile <your-full-chain-certificate-filepath> | openssl pkcs7 -print_certs -noout
```

The command generates a report of all certificates. Verify that the certificates are in the following order:

* Domain
* Intermediate (optional)
* Root

<a id="public-tls-full-chain-certificate" />

## Step 5: Store and configure the ingress controller TLS certificate

Determine whether or not your certificate was issued by an intermediate certificate-authority. If you do not know, assume you use an intermediate certificate and attempt to obtain a `full-chain.pem` bundle from your certificate authority.

Certificates issued by operators of root certificate authorities, including but not limited to LetsEncrypt, are frequently issued from intermediate certificate authorities associated with a trusted root CA.

<Warning>Astro Private Cloud backend services have stricter trust requirements than most web-browsers. Web Browsers might auto-complete the chain and consider your certificate valid, even if you don't provide the intermediate certificate-authority's public certificate. Astro Private Cloud backend services can reject the same certificate, and cause Dag and image deploys to fail.</Warning>

If, and only if, your certificate was issued directly by the root Certificate Authority of a universally trusted certificate authority, and not from one of their intermediaries, then the `server.crt` is also the full-chain certificate bundle.

Identify your full-chain public certificate `.pem` file and use it while storing and configuring the ingress controller TLS certificate.

Run the following command to store the public full-chain certificate in the Astro Private Cloud Platform Namespace in a `tls`-type Kubernetes secret. You can create a custom name for this secret. The following example uses the default name, `astronomer-tls`.

<Warning>The `--cert` parameter must reference your `full-chain.pem`, which includes the server certificate *and* any intermediate certificates, if any. Using the server cert directly causes Dag and image deploys to fail.</Warning>

```sh wrap theme={null}
kubectl -n <apc platform namespace> create secret tls astronomer-tls --cert <fullchain-pem-filepath> --key <your-private-key-filepath>
```

Naming the secret `astronomer-tls` with no substitutions is recommended when using a third-party ingress controller. If you use another name for the secret, you must uncomment and update the `tlsSecret` in your `values.yaml` file.

<a id="configure-third-party-ingress-controller" />

## Step 6: (Optional) Configure a third-party ingress controller

Skip this step if the control plane will keep using Astronomer’s built-in ingress controller. Configure a custom ingress only when this cluster must integrate with your organization’s ingress stack. The data plane guide includes its own instructions for data plane ingress changes.

If you need a third-party controller, follow the provider-specific guidance in [Third-party Ingress-Controllers](/docs/astro-private-cloud/v-1-x/third-party-ingress-controllers) for the control plane cluster, then return here before continuing.

<a id="configure-private-certificate-authority" />

## Step 7: (Optional) Configure a private certificate authority

Skip this step if you don't use a private Certificate Authority (private CA) to sign the certificate used by your ingress-controller. Or, if you don't use a private CA for any of the following services that the Astro Private Cloud platform interacts with.

Astro Private Cloud trusts public Certificate Authorities automatically.

Astro Private Cloud must be configured to trust any private Certificate Authorities issuing certificates for systems Astro Private Cloud interacts with, including but not limited-to:

* ingress controller
* email server, unless disabled
* any container registries that Kubernetes pulls from
* if using OAUTH, the OAUTH provider
* if using external elasticsearch, any external elasticsearch instances
* if using external Elasticsearch, any external Elasticsearch instances
* if using external Prometheus, any external Prometheus instances

Perform the procedure described in [Configuring private CAs](/docs/astro-private-cloud/v-1-x/configure-private-cas) for each certificate authority used to sign TLS certificates. After creating the trust secret (for example `astronomer-ca`), add it to `global.privateCaCerts` in `values.yaml` so platform components trust the issuer.

<Info>Astro CLI users must also configure both their operating system and container solution, [Docker Desktop or Podman](/docs/astro-private-cloud/v-1-x/configure-desktop-container-solution-extra-cas), to trust the private certificate Authority that was used to create the certificate used by the Astro Private Cloud ingress controller and any third-party container registries.</Info>

<a id="private-cas-for-kubernetes" />

## Step 8: (Optional) Confirm your Kubernetes cluster trusts required CAs

Skip this step unless the Astro Private Cloud control plane will pull [platform container images](#configure-a-private-docker-registry-platform) from an external container registry that uses a certificate signed by a private CA.

Kubernetes must be able to pull images from one or more container registries for Astro Private Cloud to function. By default, Kubernetes only trusts publicly signed certificates. This means that by default, Kubernetes does not honor the list of certificates [trusted by the Astro Private Cloud platform](/docs/astro-private-cloud/v-1-x/configure-private-cas).

Many enterprises configure Kubernetes to trust additional certificate authorities as part of their standard cluster creation procedure. Contact your Kubernetes Administrator to find out what, if any, private certificates are currently trusted by your Kubernetes Cluster. Then, consult your Kubernetes administrator and Kubernetes provider's documentation for instructions on configuring Kubernetes to trust additional CAs.

Follow procedures for your Kubernetes provider to configure Kubernetes to trust each CA associated with your container registries.

Certain clusters do not provide a mechanism to configure the list of certificates trusted by Kubernetes.

While configuring the Kubernetes list of cluster certificates is a customer responsibility, Astro Private Cloud includes an optional component that can, for certain Kubernetes cluster configurations, add certificates defined in `global.privateCaCerts` to the list of certificates trusted by Kubernetes. This can be enabled by setting `global.privateCaCertsAddToHost.enabled` and `global.privateCaCertsAddToHost.addToContainerd` to `true` in your `values.yaml` file and setting `global.privateCaCertsAddToHost.containerdConfigToml` to:

```text wrap theme={null}
[host."https://<image registry hostname>"]
  ca = "/etc/containerd/certs.d/<image registry hostname>/<secret name>.pem"
```

For example, if your registry lives at `my-registry.example.com` and you store the CA certificate in a secret named `my-private-ca`, the `global.privateCaCertsAddToHost` section would be:

```yaml wrap theme={null}
  global:
    privateCaCertsAddToHost:
      enabled: true
      addToContainerd: true
      hostDirectory: /etc/containerd/certs.d
      containerdConfigToml: |-
        [host."https://my-registry.example.com"]
          ca = "/etc/containerd/certs.d/my-registry.example.com/my-private-ca.pem"
```

<a id="configure-outbound-smtp-email" />

## Step 9: Configure outbound SMTP email

Astro Private Cloud requires the ability to send email to:

* Notify users of errors with their Airflow Deployments.
* Send emails to invite new users to Astro Private Cloud.
* Send certain platform alerts, enabled by default but can be configured.

Astro Private Cloud sends all outbound email using SMTP.

<Info>If SMTP is not available in the environment where you're installing Astro Private Cloud, follow instructions in [configure Astro Private Cloud to not send outbound email](/docs/astro-private-cloud/v-1-x/disable-outbound-email), and then skip the rest of this section.</Info>

1. Obtain a set of SMTP credentials from your email administrator for you to use to send email from Astro Private Cloud. When you request an email address and display name, remember that these emails are not designed for users to reply directly to them. Request all the following information:
   * Email address
   * Email display name requirements. Some email servers require a **From** line of: `Do Not Reply <donotreply@example.com>`.
   * SMTP username. This is usually the same as the email address.
   * SMTP password
   * SMTP hostname
   * SMTP port
   * Whether or not the connection supports TLS

<Info>If there is a `/` or any other escape character in your username or password, you may need to [URL encode](https://www.urlencoder.org/) those characters.</Info>

2. Ensure that your Kubernetes cluster has access to send outbound email to the SMTP server.

3. Change the configuration in `values.yaml` from `noreply@your.domain` to an email address that is valid to use with your SMTP credentials.

4. Construct an email connection string and store it in a secret in the Astro Private Cloud platform namespace. The following example shows how to store the connection in a secret called `astronomer-smtp` for a user `my@user` with a password `my@pass`. Make sure to *url-encode* the username and password if they contain special characters.

   ```sh wrap theme={null}
   kubectl -n <apc platform namespace> create secret generic astronomer-smtp --from-literal connection="smtp://my%40user:my%40pass@smtp.email.internal/?requireTLS=true"
   ```

   In general, an SMTP URI is formatted as `smtps://USERNAME:PASSWORD@HOST/?pool=true`. The following table contains examples of the URI for some of the most popular SMTP services:

   | Provider          | Example SMTP URL                                                                                 |
   | ----------------- | ------------------------------------------------------------------------------------------------ |
   | AWS SES           | `smtp://AWS_SMTP_Username:AWS_SMTP_Password@email-smtp.us-east-1.amazonaws.com/?requireTLS=true` |
   | SendGrid          | `smtps://apikey:SG.sometoken@smtp.sendgrid.net:465/?pool=true`                                   |
   | Mailgun           | `smtps://xyz%40example.com:password@smtp.mailgun.org/?pool=true`                                 |
   | Office365         | `smtp://xyz%40example.com:password@smtp.office365.com:587/?requireTLS=true`                      |
   | Custom SMTP-relay | `smtp://smtp-relay.example.com:25/?ignoreTLS=true`                                               |

   If your SMTP provider is not listed, refer to the provider's documentation for information on creating an SMTP URI.

5. Ensure this secret is referenced in the `values.yaml` file via an entry in the `astronomer.houston.secret` list. For example:

   ```yaml wrap theme={null}
   astronomer:
     houston:
       secret:
       - envName: "EMAIL__SMTP_URL"  # Reference to the Kubernetes secret for SMTP credentials. Only required if email is used.
         secretName: "astronomer-smtp"
         secretKey: "connection"
   ```

## Step 10: Configure volume storage classes

Skip this step if a single default storage class is sufficient for every control plane component. Otherwise, set the fields below to point at the storage classes you want to use. Astronomer recommends solid-state storage for all volumes.

Key fields to review in `values.yaml`:

* `global.storageClass`: Fallback storage class for control plane components.
* `postgresql.persistence.storageClass`: Only required if you enable the bundled Postgres database (not recommended outside of testing environments).
* `prometheus.persistence.storageClassName`: Used by the control plane Prometheus when retaining metrics locally.
* `alertmanager.persistence.storageClassName`: Required if Alertmanager should keep state on disk.
* `nats.jetstream.fileStorage.storageClassName`: Only relevant if you enable JetStream persistence; most control plane deployments leave JetStream stateless.

Example: to point Prometheus at a custom storage class called `fast-storage`, add:

```yaml wrap theme={null}
prometheus:
  persistence:
    storageClassName: fast-storage
```

When you have the desired values, merge them into `values.yaml` manually or by using a YAML merge tool of your choosing.

<a id="configure-the-database" />

## Step 11: Configure the database

Astro Private Cloud requires a central Postgres database that acts as the backend for Astro Private Cloud's Houston API.

<Check>
  If, while evaluating Astro Private Cloud, you need to create a temporary environment where Postgres is not available, locate the `global.postgresqlEnabled` option already present in your `values.yaml` and set it to `true`, then skip the remainder of this step.

  Note that `global.postgresqlEnabled` to `true` is an unsupported configuration, and should never be used on any development, staging, or production environment.
</Check>

<Info>
  If you use Azure Database for either PostgreSQL or another Postgres instance that does not enable the `pg_trgm` by default, you must enable the `pg_trgm` extension prior to installing Astro Private Cloud. If `pg_trgm` is not enabled, the install will fail. `pg_tgrm` is enabled by default on Amazon RDS and Google Cloud SQL for PostgresQL.

  For instructions on enabling the `pg_trgm` extension for Azure Flexible Server, see [PostgreSQL extensions in Azure Database for PostgreSQL - Flexible Server](https://docs.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-extensions).
</Info>

Additional requirements apply to the following databases:

* AWS RDS:
  * [t2 medium](https://aws.amazon.com/rds/instance-types/) is the minimum RDS instance size you can use.
* Azure Flexible Server:
  * You must enable the `pg_trgm` extension as per the advisory earlier in this section.
  * Set `global.ssl.mode`to `prefer` in your `values.yaml` file.

Create a Kubernetes Secret in the namespace chosen for the install, named `astronomer-bootstrap`, that points to your database. You must URL encode any special characters in your Postgres password.

<Warning>The in-cluster Postgres option (`global.postgresqlEnabled: true`) should only be used for short-lived testing. Always rely on an external Postgres instance for any persistent environment.</Warning>

<Warning>PostgreSQL usernames must be lowercase.</Warning>

To create this secret, run the following command replacing the APC platform namespace, username, password, database hostname, and database port with their respective values:

```sh wrap theme={null}
kubectl -n <apc platform namespace> create secret generic astronomer-bootstrap \
  --from-literal connection="postgres://<url-encoded username>:<url-encoded password>@<database hostname>:<database port>"
```

For example, for a username named `bob` with password `abc@abc` for the database `dbname` at hostname `some.host.internal`, you would run:

```sh wrap theme={null}
kubectl -n astronomer create secret generic astronomer-bootstrap \
  --from-literal connection="postgres://bob:abc%40abc@some.host.internal:5432/dbname"
```

<Warning>This secret must be named `astronomer-bootstrap` and must be present in the APC platform namespace before you install Astro Private Cloud.</Warning>

<a id="configure-a-private-docker-registry-platform" />

## Step 12: Configure the Docker registry used for platform images

Skip this step if you are installing Astro Private Cloud onto a Kubernetes cluster that can pull container images from public image repositories and you don't want to mirror these images locally.

<Info>
  Docker registry secrets will also need to be created in any data planes you register with this environment, which will be covered in the [data plane installation guide](/docs/astro-private-cloud/v-1-x/install-data-plane).
</Info>

<Tabs>
  <Tab title="Anonymous">
    If your registry can be reached without credentials, ensure the endpoint is restricted to trusted networks (for example private subnets or VPN access). Avoid exposing the platform image registry directly to the public internet. No additional APC configuration is required beyond setting the repository locations later in this step.
  </Tab>

  <Tab title="Amazon ECR">
    1. Grant your worker nodes or IRSA service accounts the IAM permissions required to pull images from the target ECR repository. At minimum, allow `ecr:GetAuthorizationToken`, `ecr:BatchCheckLayerAvailability`, `ecr:GetDownloadUrlForLayer`, and `ecr:BatchGetImage`.
    2. Ensure network access from the cluster to the appropriate ECR endpoints (for example, VPC endpoints or public ECR endpoints).
    3. Set the platform repository prefix in `values.yaml`. For example:

       ```yaml wrap theme={null}
       global:
         privateRegistry:
           enabled: true
           repository: <account-id>.dkr.ecr.<region>.amazonaws.com/<platform-prefix>
       astronomer:
         houston:
           config:
             deployments:
               helm:
                 runtimeImages:
                   airflow:
                     repository: <account-id>.dkr.ecr.<region>.amazonaws.com/<platform-prefix>/astro-runtime
                 runtimeImagesV3:
                   airflow:
                     repository: <account-id>.dkr.ecr.<region>.amazonaws.com/<platform-prefix>/runtime
                 airflow:
                   defaultAirflowRepository: <account-id>.dkr.ecr.<region>.amazonaws.com/<platform-prefix>/ap-airflow
                   defaultRuntimeRepository: <account-id>.dkr.ecr.<region>.amazonaws.com/<platform-prefix>/astro-runtime
       ```

       When you rely on IAM-based authentication, `global.privateRegistry.secretName` is not required. If you use static credentials, create the matching Docker registry secret following the AWS ECR documentation and set `secretName` accordingly.
  </Tab>

  <Tab title="Other registries">
    1. Create a Docker registry secret in the APC platform namespace:

       ```bash wrap theme={null}
       kubectl -n <apc platform namespace> create secret docker-registry <secret-name> \
         --docker-server=<registry-host> \
         --docker-username=<username> \
         --docker-password=<password> \
         --docker-email=<email>
       ```

    2. Update `values.yaml` so the platform charts reference your registry and credentials:

       ```yaml wrap theme={null}
       global:
         privateRegistry:
           enabled: true
           repository: <custom-platform-repo-prefix>
           secretName: <secret-name>
       astronomer:
         houston:
           config:
             deployments:
               helm:
                 runtimeImages:
                   airflow:
                     repository: <custom-platform-repo-prefix>/astro-runtime
                 runtimeImagesV3:
                   airflow:
                     repository: <account-id>.dkr.ecr.<region>.amazonaws.com/<platform-prefix>/runtime
                 airflow:
                   defaultAirflowRepository: <custom-platform-repo-prefix>/ap-airflow
                   defaultRuntimeRepository: <custom-platform-repo-prefix>/astro-runtime
       ```
  </Tab>
</Tabs>

For additional examples (including per-deployment registry settings and air gapped workflows), see [Configure a custom registry for Deployment images](/docs/astro-private-cloud/v-1-x/custom-image-registry).

<a id="determine-version-of-astronomer" />

## Step 13: Determine which version of Astro Private Cloud to install

Astronomer recommends new Astro Private Cloud installations use the most recent version available in either the Stable or Long Term Support (LTS) release-channel. Keep this version number available for the following steps. For a separate control plane and data plane topology, at least version 1.0.0 of Astro Private Cloud is required.

See Astro Private Cloud's [lifecycle policy](/docs/astro-private-cloud/v-1-x/release-lifecycle-policy) and [version compatibility reference](/docs/astro-private-cloud/v-1-x/version-compatibility-reference) for more information.

<a id="fetch-airflow-helm-charts" />

## Step 14: Fetch Airflow Helm charts

If you have internet access to `https://helm.astronomer.io`, run the following command on the machine where you want to install Astro Private Cloud:

```sh wrap theme={null}
helm repo add astronomer https://helm.astronomer.io/
helm repo update
```

If you don't have internet access to `https://helm.astronomer.io`, download the Astro Private Cloud Platform Helm chart file corresponding to the version of Astro Private Cloud you are installing or upgrading to from `https://helm.astronomer.io/astronomer-<version number>.tgz`. For example, for Astro Private Cloud v1.0.0 you would download `https://helm.astronomer.io/astronomer-1.0.0.tgz`. This file does not need to be uploaded to an internal chart repository.

<a id="create-and-customize-upgrades" />

## Step 15: Create and customize `upgrade.sh`

Create a file named `upgrade.sh` in your platform deployment project directory containing the following script. Specify the following values at the beginning of the script:

* `CHART_VERSION`: Your Astro Private Cloud version, including patch and a `v` prefix. For example, `v1.0.0`.
* `RELEASE_NAME`: Your Helm release name. `astronomer` is strongly recommended.
* `NAMESPACE`: The namespace to install platform components into. `astronomer` is strongly recommended.
* `CHART_NAME`: Set to `astronomer/astronomer` if fetching platform images from the internet. Otherwise, specify the filename if you're installing from a file (for example `astronomer-1.0.0.tgz`).

<Warning> Do not run this script after you create it. Your installation uses this script later, when you run your final upgrades and install processes.</Warning>

```sh wrap theme={null}
#!/bin/bash
set -xe

# typically astronomer
RELEASE_NAME=<astronomer-platform-release-name>
# typically astronomer
NAMESPACE=<astronomer-platform-namespace>
# typically astronomer/astronomer
CHART_NAME=<chart name>
# format is v<major>.<minor>.<path> e.g. v1.0.0
CHART_VERSION=<v-prefixed version of the Astro Private Cloud platform chart>
# ensure all the above environment variables have been set

helm repo add --force-update astronomer https://helm.astronomer.io
helm repo update

# upgradeDeployments false ensures that Airflow charts are not upgraded when this script is run
# If you deployed a config change that is intended to reconfigure something inside Airflow,
# then you may set this value to "true" instead. When it is "true", then each Airflow chart will
# restart. Note that some stable version upgrades require setting this value to true regardless of your own configuration.
helm upgrade --install --namespace $NAMESPACE \
            -f ./values.yaml \
            --reset-values \
            --version $CHART_VERSION \
            --debug \
            --set astronomer.houston.upgradeDeployments.enabled=false \
            $RELEASE_NAME \
            $CHART_NAME $@
```

<a id="mirror-platform-images" />

## Step 16: Mirror platform images

<Info>This step is optional but strongly recommended for production environments so your cluster can pull platform images from a registry you control.</Info>

1. Gather the list of required platform images using one of the following methods:

<Tabs>
  <Tab title="Shell">
    Mac and Linux users with `jq` installed can set `CHART_VERSION` in the following snippet and run it to produce a list of images.

    ```sh wrap theme={null}
    CHART_VERSION=<v-prefixed version of the Astro Private Cloud platform chart>
    UNPREFIXED_CHART_VERSION=${CHART_VERSION#v}
    curl -s https://updates.astronomer.io/astronomer-software/releases/astronomer-${UNPREFIXED_CHART_VERSION}.json | jq -r '(.astronomer.images, .airflow.images) | to_entries[] | "\(.value.repository):\(.value.tag)"'| sort
    ```
  </Tab>

  <Tab title="Windows Powershell">
    Windows PowerShell users can set `CHART_VERSION` in the following snippet and run it to produce a list of images.

    ```powershell wrap theme={null}
    $CHART_VERSION = "<v-prefixed version>"
    $UNPREFIXED_CHART_VERSION = $CHART_VERSION.TrimStart('v')
    $jsonUrl     = "https://updates.astronomer.io/astronomer-software/releases/astronomer-$UNPREFIXED_CHART_VERSION.json"
    $jsonContent = Invoke-WebRequest $jsonUrl -UseBasicParsing
    $json        = $jsonContent.Content | ConvertFrom-Json
    $astronomerImages = $json.astronomer.images.PSObject.Properties.Value
    $airflowImages    = $json.airflow.images.PSObject.Properties.Value

    $images = $astronomerImages + $airflowImages

    $images |
        ForEach-Object { "$($_.repository):$($_.tag)" } |
        Sort-Object
    ```
  </Tab>

  <Tab title="Other">
    Visit the [release metadata](https://updates.astronomer.io/astronomer-software/releases/index.html) page and download the json-formatted release metadata corresponding to the version of Astro Private Cloud you are installing and use another method of your choice to extract the list of images from beneath the `astronomer.images` and `airflow.images` keys.
  </Tab>
</Tabs>

2. Copy the above images to the container registry using the naming scheme you configured [when you set up a custom image registry](#configure-a-private-docker-registry-platform).

<a id="fetch-runtime-updates" />

## Step 17: Fetch Astro Runtime updates

If you are installing Astro Private Cloud into an egress-controlled or air gapped environment, perform the following steps.

By default, Astro Private Cloud checks for Airflow updates, which are included in the Astro Runtime, once per day at midnight by querying `https://updates.astronomer.io/astronomer-runtime`. This returns a JSON file with details about the latest available Astro Runtime versions.

In an egress-controlled or air gapped environment, you need to store the JSON file in the cluster itself, avoiding the external check. To store the JSON file in the cluster, complete the following steps:

1. Download the JSON files and store them in a Kubernetes configmap by running the following commands:

```sh wrap theme={null}
curl -XGET https://updates.astronomer.io/astronomer-runtime -o astro_runtime_releases.json

kubectl -n <apc platform namespace> create configmap astro-runtime-base-images --from-file=astro_runtime_releases.json
```

2. Add your configmap name, `astro-runtime-base-images` to your Houston configuration using the `runtimeReleasesConfigMapName` configuration:

```yaml wrap theme={null}
astronomer:
  houston:
    runtimeReleasesConfigMapName: astro-runtime-base-images
    config:
      airgapped:
        enabled: true
```

<a id="openshift-configuration" />

## Step 18: (OpenShift only) Apply OpenShift-specific configuration

If you're not installing Astro Private Cloud into an OpenShift Kubernetes cluster, skip this step.

Add the following values into `values.yaml`. You can do this manually or by using a YAML merge tool of your choosing.

```yaml wrap theme={null}
global:
  openshiftEnabled: true
  sccEnabled: false
  extraAnnotations:
    kubernetes.io/ingress.class: openshift-default
    route.openshift.io/termination: "edge"
  authSidecar:
    enabled: true
  dagOnlyDeployment:
    securityContext:
      fsGroup: ""
  vectorEnabled: false
  loggingSidecar:
    enabled: true
    name: sidecar-log-consumer
elasticsearch:
  sysctlInitContainer:
    enabled: false

# bundled postgresql not a supported option, only for use in proof-of-concepts
postgresql:
  securityContext:
    enabled: false
  volumePermissions:
    enabled: false
```

<Info>
  Only Ingress objects with the annotation `route.openshift.io/termination: "edge"` are supported for generating routes in OpenShift 4.11 and later.
  Other termination types are no longer supported for automatic route generation.

  If you're on an older version of OpenShift, route creation should be done manually.
</Info>

Astro Private Cloud on OpenShift is only supported when using [a third-party ingress-controller](#configure-third-party-ingress-controller) and using the [logging sidecar](#openshift-configuration) feature of Astro Private Cloud. The above configuration enables both of these items.

<a id="integrate-an-external-identity-provider" />

## Step 19: (Optional) Integrate an external identity provider

Astro Private Cloud includes integrations for several of the most popular OAUTH2 identity providers (IdPs), such as Okta and Microsoft Entra ID. Configuring an external IdP allows you to automatically provision and manage users in accordance with your organization's security requirements. See [Integrate an auth system](/docs/astro-private-cloud/v-1-x/integrate-auth-system) to configure the identity provider of your choice in your `values.yaml` file.

<a id="install-astronomer-using-helm" />

## Step 20: Install Astro Private Cloud using Helm

Deploy the control plane using the `upgrade.sh` script you created earlier. Confirm `RELEASE_NAME`, `NAMESPACE`, and `CHART_VERSION` reflect your environment, then execute:

```bash wrap theme={null}
./upgrade.sh
```

To review manifests before applying them, run `./upgrade.sh --dry-run` or use `helm template` with the same flags defined in the script.

<a id="configure-dns-ingress-controller" />

## Step 21: Configure DNS to point to the ingress controller

Whether you use Astronomer's integrated ingress controller or a third-party controller, publish the same set of DNS records so users can reach control plane services.

* If you use the integrated controller, get the load balancer address directly:

  ```sh wrap theme={null}
  kubectl -n <apc platform namespace> get svc astronomer-cp-nginx
  ```

* If you use a third-party controller, ask your ingress administrator for the hostname or IP address that should front the Astronomer routes (refer back to [Configure a third-party ingress controller](#configure-third-party-ingress-controller)).

Create either a wildcard record such as `*.sandbox-astro.example.com` or individual CNAME records for the following hostnames so that traffic routes through the chosen load balancer:

* `app.<base-domain>` (required)
* `houston.<base-domain>` (required)
* `prometheus.<base-domain>` (required)
* `alertmanager.<base-domain>` (required if you keep the integrated Alertmanager enabled)
* `<base-domain>` (optional but recommended, provides a vanity redirect to `app.<base-domain>`)

Astronomer generally recommends pointing the zone apex (`@`) directly to the load balancer address and mapping the remaining hostnames as CNAMEs to that apex. In lower environments, you can safely use a low TTL (for example 60 seconds) to speed up troubleshooting during the initial rollout.

After your DNS provider propagates the records, verify them with tools like `dig <hostname>` or `getent hosts <hostname>`. You can complete this DNS work after verifying the platform pods—Astronomer services stay healthy without external DNS, but end users need these records to sign in.

<Info>
  Upgrades from 0.x to 1.0 rename the control plane ingress Service from `astronomer-nginx` to `astronomer-cp-nginx`. This provisions a new LoadBalancer with a new public IP/hostname. If you're upgrading, update DNS and firewall/allowlists and re-issue TLS/SSL certificates if they reference the previous LoadBalancer hostname. See the [upgrade guide](/docs/astro-private-cloud/v-1-x/upgrade-037-to-1).
</Info>

<a id="verify-ui" />

## Step 22: Verify you can access the UI

Visit `https://app.<base-domain>` in your web-browser to view Astro Private Cloud's web interface. If any components are not ready, consult the [debugging guide](/docs/astro-private-cloud/v-1-x/debug-install) or contact [Astronomer support](https://support.astronomer.io) with the relevant logs and events.

Congratulations, you have configured and installed an Astronomer for Private Cloud platform instance - your new Airflow control plane!

From the Astro Private Cloud UI, you'll be able to both invite and manage users as well as create and monitor Airflow Deployments on the platform.

<a id="disable-anonymous-account-creation" />

## Step 23: Disable anonymous account creation

Leave `astronomer.houston.config.publicSignups: true` only long enough to create your first administrator. Afterwards, secure the platform as follows:

1. If you keep public signups enabled, turn on outbound email (`astronomer.houston.config.email.enabled: true`), specify a trusted domain list under `astronomer.houston.config.allowedSystemLevelDomains`, and verify that users can only join through an approved identity provider.
2. Otherwise, set `astronomer.houston.config.publicSignups: false` so new accounts require an invitation.
3. Apply the updated configuration with `helm upgrade` targeting the control plane release.

## Additional customization

The following topics include optional information about one or multiple topics in the installation guide:

* [Configure a private Certificate Authority](/docs/astro-private-cloud/v-1-x/configure-private-cas)
* [Disable outbound emails](/docs/astro-private-cloud/v-1-x/disable-outbound-email)
* Add trusted CAs to [Docker Desktop](/docs/astro-private-cloud/v-1-x/configure-desktop-container-solution-extra-cas)

## Next steps

<a id="register-data-plane" />

### Register the data planes with the control plane

Add the data planes to the control plane to begin creating Airflow Deployments. See [Register a data plane with the APC control plane](/docs/astro-private-cloud/v-1-x/register-data-plane) for instructions on exchanging tokens, approving connectivity, and assigning deployments.
