Reputation: 9286
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
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