Custom Domain
Important: These docs are for the outdated Jets 5 versions and below. For the latest Jets docs: docs.rubyonjets.com
Jets can create and associate a route53 custom domain with the API Gateway endpoint. Jets manages the vanity route53 endpoint that points to the API Gateway endpoint. It adjusts the endpoint transparently without you having to update your endpoint if Jets determines that a new API Gateway Rest API needs to be created. The route53 record is also updated. Here’s a table with some example values to explain:
Vanity Endpoint | API Gateway Endpoint | Jets Env Extra |
---|---|---|
demo-dev.coolapp.com | a02oy4fs56.execute-api.us-west-2.amazonaws.com | (not set) |
demo-dev-2.coolapp.com | xyzoabc123.execute-api.us-west-2.amazonaws.com | 2 |
Here’s a diagram also:
Vanity Endpoint
NOTE: If you have already previously set up an API Custom Domain, when Jets tries to add the Custom Domain it will fail. This is because the Custom Domain already exists, CloudFormation sees this, and will not destructively delete existing resources managed outside of its purview. Both the Custom Domain and the Route53 record associated with that domain must be deleted before running jets deploy
. This will occur downtime until the jets deploy
completes. Fortunately, this only needs to be done once and after that Jets manages the vanity endpoint. It is recommended that you set up the Custom Domain as early as possible so you do not run into this down the road.
To create a vanity endpoint edit the config/application.rb
and edit domain.cert_arn
and domain.hosted_zone_name
:
Jets.application.configure do
config.domain.cert_arn = "arn:aws:acm:us-west-2:112233445577:certificate/8d8919ce-a710-4050-976b-b33da991e7e8" # String
config.domain.hosted_zone_name = "coolapp.com" # String
# config.domain.name = "#{Jets.project_namespace}.coolapp.com" # Default is the example convention
# NOTE: Changing the endpoint_configuration can result 10 minutes of downtime if going from REGIONAL to EDGE
# config.domain.endpoint_configuration = { types: ["REGIONAL"] } # EDGE or REGIONAL
end
You can create an AWS Certificate with ACM by following the docs: Request a Public Certificate. The hosted zone name on Route53 is required. Here are the docs to create one: Creating a Public Hosted Zone.
Controlling Domain Name
By default, the domain name is a subdomain with Jets.project_namespace
as the value. Example:
#{Jets.project_namespace}.coolapp.com = demo-dev.coolapp.com
When JETS_EXTRA=1
is set the values looks like this:
#{Jets.project_namespace}.coolapp.com = demo-dev-1.coolapp.com
You can set the domain name yourself and override the default behavior like so:
Jets.application.configure do
config.domain.name = "mysubdomain.coolapp.com"
end
Changing Endpoint Configuration Warning
If you change the API Gateway domain endpoint_type REGIONAL to EDGE and vice versa, this results in downtime while the new endpoint type is being created.
- Going from REGIONAL to EDGE results in about 10 minutes of unavailability. That’s about how long it takes API Gateway to create the CloudFront Edge endpoint.
- Going from EDGE to REGIONAL results in about 30 seconds of unavailability. That’s about how long it takes API Gateway to create the Regional endpoint.
If you need to switch this and avoid downtime, you will need to do a manual blue-green deployment by creating a new environment with JETS_EXTRA
.
Routes Deployment
Jets does what is necessary to deploy route changes. Sometimes this requires replacing the Rest API entirely. Jets detects this and will create a brand new Rest API when needed. Jets does this because CloudFormation is unable to deploy certain API Gateway changes cleanly when:
- The
config.api.binary_media_types
has changed. - A route definition with the same path and method to has been updated. IE:
get "/signin", to: "/users#signin"
toget "/signin", to: "/signups#signin"
- A route variable at the same parent path has changed. IE:
get "/posts/:id", to: "/posts/#show"
toget "/posts/:post_id", to: "/posts/#show"
- Routes have moved to different cloudformation stacks or “pages”. With large apps that have 100+ routes, Jets must generate multiple stacks in order to stay under the CloudFormation resource limits. Reordering routes with these large apps can trigger a change.
Important: When a new API Gateway is deployed, the endpoint will change. This is one of the reasons why a Custom Domain is recommended to be set up, so the endpoint url remains the same. Generally, the route change detection works well. If you need to force the creation of a brand new Rest API, you can use JETS_API_REPLACE=1 jets deploy
.
HostedZoneId
You can also specify a hosted_zone_id
instead of hosted_zone_name
.
Jets.application.configure do
config.domain.cert_arn = "arn:aws:acm:us-west-2:112233445577:certificate/8d8919ce-a710-4050-976b-b33da991e7e8" # String
config.domain.hosted_zone_name = "coolapp.com" # String
config.domain.hosted_zone_id = "/hostedzone/Z2E57RZEXAMPLE"
end
Note, you should still specify the hosted_zone_name
because it is conventionally used for the API Gateway Custom Domain name.
Disable Route53
If config.domain.hosted_zone_name
is set, then config.domain.route53=true
will be the default behavior. It is useful to turn off the Jets managed route53 record if you would like to manage the DNS yourself. Though there may be little point as the DNS must always match the API Custom Domain Name.
Jets.application.configure do
# ...
config.domain.route53 = false # disable route53 from being managed by jets.
Route53 Apex Domain
Jets creates a CNAME route53 record by default. Jets can also create zone apex records or naked domain names. Example:
Jets.application.configure do
config.domain.cert_arn = "arn:aws:acm:us-west-2:112233445577:certificate/8d8919ce-a710-4050-976b-b33da991e7e8" # String
config.domain.hosted_zone_name = "coolapp.com" # String
config.domain.name = "coolapp.com"
config.domain.apex = true # # apex or naked domain. Use AliasTarget with an A record instead of a CNAME
end
Though apex records are supported, it is recommended to put CloudFront of the API Gateway Custom Domain instead.
CloudFront Recommendation
For the most control, it is recommended to create a CloudFront distribution outside of Jets. Then put CloudFront in front of the API Gateway Custom Domain. Example:
This provides you full manual control over the DNS. You can deploy additional extra environments and update which Jets environment CloudFront points to. This type of blue-green deployment can be useful for large feature rollouts. Using CloudFront will also allow you to do things like redirect http to https. A notable drawback is that CloudFront changes can take 5m-20m to deploy.
CloudFront Host
When CloudFront is configured with API Gateway, the Host
header cannot is not passed to API Gateway. So the request.host
your Jets app sees is the API Gateway Custom Domain. This will cause issues for methods like redirect_to
. To set the host you can:
- Set the
config.app.domain
option. - Configure the CloudFront distribution with a Host Header
origin
.
Configuring either of these will set the host the app with “see”. The config.app.domain
option takes the highest precedence. Since it takes the highest precedence, the using the AWS generated APIGW endpoint url_for
and redirect_to
helpers method not have the APIGW state prefixed.
Related Research:
- Forwarding CloudFront Host Header to API Gateway
- How to make CloudFront forward the Host or X-Forwarded-Host header to API Gateway
The links above explain that APIGW will not accept CloudFront will accept the Host
header from CloudFront. It mentions a pass a “Host” is with a CloudFront Lambda@Edge function. It’s a bit of effort to set up, so Jets allows you to use config.app.domain
instead for helpers like redirect_to
to work as expected.
Multiple Custom Domain Base Paths
You can configure the base path in the custom domain by adding the base_path parameter . Example:
Jets.application.configure do
config.domain.cert_arn = "arn:aws:acm:us-west-2:112233445577:certificate/8d8919ce-a710-4050-976b-b33da991e7e8" # String
config.domain.hosted_zone_name = "coolapp.com" # String
config.domain.name = "coolapp.com"
config.domain.base_path = "accounts"
end
If then deploy another app with the same domain.name
but a different domain.base_path = "api"
, then the mapping works like this:
coolapp.com/accounts => root of jets app 1
coolapp.com/api => root of jets app 2
Considerations/Cavaets
When using Custom Domains, Jets manages these resources via CloudFormation: APIGW Custom Domain and the associated Route53 record
- If you make manual changes to the Custom Domain, this can confuse CloudFormation and prevent it from managing or deploying the Custom Resource properly. You can get CloudFormation back in “sync” deleting the Custom Domain setting in the Jets
config/application.rb
, deploying it to delete it on the CloudFormation side. Then you’ll need to also delete the APIGW Custom Domain resource manually. That gets things back in sync. - The same goes for the Route53 Record managed by Jets and CloudFormation. You can use the same trick of deleting it from the Jets
config/application.rb
and manually deleting the route53 record. That syncs back up the state so you can deploy again.