Configuring the Custom Domain Operator requires a wildcard CNAME DNS record in your Amazon Route 53 hosted zone. If you do not want to use a wildcard record, you can use the External DNS Operator to create individual entries for routes.

Use this tutorial to deploy and configure the External DNS Operator with a custom domain in Red Hat OpenShift Service on AWS (ROSA).

The External DNS Operator does not support STS using IAM Roles for Service Accounts (IRSA) and uses long-lived Identity Access Management (IAM) credentials instead. This tutorial will be updated when the Operator supports STS.


  • A ROSA cluster

  • A user account with dedicated-admin privileges

  • The OpenShift CLI (oc)

  • The Amazon Web Services (AWS) CLI (aws)

  • A unique domain, such as *.apps.<company_name>.io

  • An Amazon Route 53 public hosted zone for the above domain

Setting up your environment

  1. Configure the following environment variables, replacing CLUSTER_NAME with the name of your cluster:

    $ export DOMAIN=apps.<company_name>.io (1)
    $ export AWS_PAGER=""
    $ export CLUSTER_NAME=$(oc get infrastructure cluster -o=jsonpath="{.status.infrastructureName}"  | sed 's/-[a-z0-9]\{5\}$//')
    $ export REGION=$(oc get infrastructure cluster -o=jsonpath="{.status.platformStatus.aws.region}")
    $ export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
    $ export SCRATCH="/tmp/${CLUSTER_NAME}/external-dns"
    $ mkdir -p ${SCRATCH}
    1 The custom domain.
  2. Ensure all fields output correctly before moving to the next section:

    $ echo "Cluster: ${CLUSTER_NAME}, Region: ${REGION}, AWS Account ID: ${AWS_ACCOUNT_ID}"

Setting up your custom domain

ROSA manages secondary Ingress Controllers using the Custom Domain Operator. Use the following procedure to deploy a secondary Ingress Controller using a custom domain.

  • A unique domain, such as *.apps.<company_name>.io

  • A custom SAN or wildcard certificate, such as CN=*.apps.<company_name>.io

  1. Create a new project:

    $ oc new-project external-dns-operator
  2. Create a new TLS secret from a private key and a public certificate, where fullchain.pem is your full wildcard certificate chain (including any intermediaries) and privkey.pem is your wildcard certificate’s private key:

    $ oc -n external-dns-operator create secret tls external-dns-tls --cert=fullchain.pem --key=privkey.pem
  3. Create a new CustomDomain custom resource (CR):

    Example external-dns-custom-domain.yaml
    apiVersion: managed.openshift.io/v1alpha1
    kind: CustomDomain
      name: external-dns
      domain: apps.<company_name>.io (1)
      scope: External
      loadBalancerType: NLB
        name: external-dns-tls
        namespace: external-dns-operator
    1 The custom domain.
  4. Apply the CR:

    $ oc apply -f external-dns-custom-domain.yaml
  5. Verify that your custom domain Ingress Controller has been deployed and has a Ready status:

    $ oc get customdomains
    Example output
    NAME               ENDPOINT                                                    DOMAIN                       STATUS
    external-dns       xxrywp.<company_name>.cluster-01.opln.s1.openshiftapps.com  *.apps.<company_name>.io     Ready

Preparing your AWS account

  1. Retrieve the Amazon Route 53 public hosted zone ID:

    $ export ZONE_ID=$(aws route53 list-hosted-zones-by-name --output json \
      --dns-name "${DOMAIN}." --query 'HostedZones[0]'.Id --out text | sed 's/\/hostedzone\///')
  2. Create an AWS IAM Policy document that allows the External DNS Operator to update only the custom domain public hosted zone:

    $ cat << EOF > "${SCRATCH}/external-dns-policy.json"
      "Version": "2012-10-17",
      "Statement": [
          "Effect": "Allow",
          "Action": [
          "Resource": [
          "Effect": "Allow",
          "Action": [
          "Resource": [
  3. Create an AWS IAM policy:

    $ export POLICY_ARN=$(aws iam create-policy --policy-name "${CLUSTER_NAME}-AllowExternalDNSUpdates" \
      --policy-document file://${SCRATCH}/external-dns-policy.json \
      --query 'Policy.Arn' --output text)
  4. Create an AWS IAM user:

    $ aws iam create-user --user-name "${CLUSTER_NAME}-external-dns-operator"
  5. Attach the policy:

    $ aws iam attach-user-policy --user-name "${CLUSTER_NAME}-external-dns-operator" --policy-arn $POLICY_ARN

    This will be changed to STS using IRSA in the future.

  6. Create AWS keys for the IAM user:

    $ SECRET_ACCESS_KEY=$(aws iam create-access-key --user-name "${CLUSTER_NAME}-external-dns-operator")
  7. Create static credentials:

    $ cat << EOF > "${SCRATCH}/credentials"
    aws_access_key_id = $(echo $SECRET_ACCESS_KEY | jq -r '.AccessKey.AccessKeyId')
    aws_secret_access_key = $(echo $SECRET_ACCESS_KEY | jq -r '.AccessKey.SecretAccessKey')

Installing the External DNS Operator

  1. Install the External DNS Operator from OperatorHub:

    $ cat << EOF | oc apply -f -
    apiVersion: operators.coreos.com/v1
    kind: OperatorGroup
      name: external-dns-group
      namespace: external-dns-operator
      - external-dns-operator
    apiVersion: operators.coreos.com/v1alpha1
    kind: Subscription
      name: external-dns-operator
      namespace: external-dns-operator
      channel: stable-v1.1
      installPlanApproval: Automatic
      name: external-dns-operator
      source: redhat-operators
      sourceNamespace: openshift-marketplace
  2. Wait until the External DNS Operator is running:

    $ oc rollout status deploy external-dns-operator --timeout=300s
  3. Create a secret from the AWS IAM user credentials:

    $ oc -n external-dns-operator create secret generic external-dns \
      --from-file "${SCRATCH}/credentials"
  4. Deploy the ExternalDNS controller:

    $ cat << EOF | oc apply -f -
    apiVersion: externaldns.olm.openshift.io/v1beta1
    kind: ExternalDNS
      name: ${DOMAIN}
        - filterType: Include
          matchType: Exact
          name: ${DOMAIN}
            name: external-dns
        type: AWS
          routerName: external-dns
        type: OpenShiftRoute
        - ${ZONE_ID}
  5. Wait until the controller is running:

    $ oc rollout status deploy external-dns-${DOMAIN} --timeout=300s

Deploying a sample application

Now that the ExternalDNS controller is running, you can deploy a sample application to confirm that the custom domain is configured and trusted when you expose a new route.

  1. Create a new project for your sample application:

    $ oc new-project hello-world
  2. Deploy a hello world application:

    $ oc new-app -n hello-world --image=docker.io/openshift/hello-openshift
  3. Create a route for the application specifying your custom domain name:

    $ oc -n hello-world create route edge --service=hello-openshift hello-openshift-tls \
    --hostname hello-openshift.${DOMAIN}
  4. Check if the DNS record was created automatically by ExternalDNS:

    It can take a few minutes for the record to appear in Amazon Route 53.

    $ aws route53 list-resource-record-sets --hosted-zone-id ${ZONE_ID} \
       --query "ResourceRecordSets[?Type == 'CNAME']" | grep hello-openshift
  5. Optional: You can also view the TXT records that indicate they were created by ExternalDNS:

    $ aws route53 list-resource-record-sets --hosted-zone-id ${ZONE_ID} \
       --query "ResourceRecordSets[?Type == 'TXT']" | grep ${DOMAIN}
  6. Navigate to your custom console domain in the browser where you see the OpenShift login:

    $ echo console.${DOMAIN}