Daniel Birowsky Popeski
Daniel Birowsky Popeski

Reputation: 9286

How to get the CloudFront distribution's domain name

The goal is to create a custom domain for my Serverless api: api.example.com. So my approach is to create this Route53 record:

ApiDomainRecord:
  Type: AWS::Route53::RecordSet
  Properties:
    Type: A
    Name: api.example.com
    HostedZoneId: Z2PERRPAZRTJGB
    AliasTarget:
      HostedZoneId: Z2FDTNDATAQYW2
      DNSName:
        Fn::GetAtt: [ --> what do we enter here <--, DomainName ]

But how do I provide the domain name from the CloudFront emitted by Serverless?

Upvotes: 4

Views: 2527

Answers (1)

Mike Patrick
Mike Patrick

Reputation: 11006

Getting the API Gateway URL

The only way I've found to get my hands on the API Gateway URL in my Serverless templates is to piece it together myself. I'm certainly not the first to do it this way.

The API created by serverless is called ApiGatewayRestApi. Since your template is aware of the region and stage, you can piece together the URL like so:

      DNSName:
        Fn::Join:
          - ""
          - - "https://"
            - Ref: ApiGatewayRestApi
            - ".execute-api.${self:provider.region}.amazonaws.com/${self:provider.stage}"

Unfortunately, this won't work

Although I believe API Gateway uses CloudFront under the hood (at least for edge optimized endpoints), plugging the above code into your script will result in an error, complaining that the hosted zone is wrong for this URL. This is unsurprising, since ...execute-api...amazonaws.com is clearly not a subdomain of cloudfront.net.

I think you have to use API Gateway's custom domain feature to pull this off. When you create one, you get a real CloudFront URL back that you can use with the CloudFront hosted zone (Z2FDTNDATAQYW2).

The downside of this is that you get a CloudFront distribution created for your API. This implies that deploying a brand new API is going to take ~20-30 minutes, rather than just a minute or two.


Without a plugin

In your serverless template, this means creating an API Gateway DomainName (and probably a BasePathMapping), in addition to the Route 53 RecordSet:

resources:
  Resources:
    ApiDomainRecord:
      Type: AWS::ApiGateway::DomainName
      Properties:
        CertificateArn: arn:aws:acm:us-east-1:<AWS_ACCOUNT>:certificate/3XXXXXXX-2XXX-4XXX-8XXX-8XXXXXXXXXXX
        DomainName: api.example.com
    ApiDomainMapping:
      Type: AWS::ApiGateway::BasePathMapping
      Properties:
        BasePath: r53
        DomainName: api.example.com
        RestApiId:
          Ref: ApiGatewayRestApi
    ApiDNSRecord:
      Type: AWS::Route53::RecordSet
      Properties:
        Type: A
        Name: api.example.com
        HostedZoneId: ZXXXXXXXXXXXXXA
        AliasTarget:
          HostedZoneId: Z2FDTNDATAQYW2
          DNSName:
            Fn::GetAtt: [ApiDomainRecord, DistributionDomainName]

In this example I hit my API at api.example.com/r53/<STAGE>/<API_RESOURCE>. My certificate is for *.example.com. You can also specify stage in the BasePathMapping, if you don't want it in the URL.


With a plugin

This is a fair amount of boilerplate, and I hate hard coding ARNs into my templates (the certificate, in this case).

The serverless-domain-manager plugin (recommended here) can make this less cumbersome. Not only does it reduce the amount of boilerplate you write, it allows you to specify certificate name, rather than ARN. I had no trouble making it work the first time I tried it.

Using this plugin, the CloudFormation above can be replaced by a short stanza in the custom section of your template:

custom:
  customDomain:
    domainName: api.example.com
    basePath: r53
    certificateName: "*.example.com"
    createRoute53Record: true

Upvotes: 2

Related Questions