(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[75976],{387453:function(a,b){"use strict";b.Z="/_next/static/media/lambda-delete-error.c3a1429767acab7ee54f2fe7abc46f37.png"},588232:function(a,b){"use strict";b.Z="/_next/static/media/new-tenant.f3734a77daffaf0239fd6b6f85e0a2ad.png"},768768:function(a,b){"use strict";b.Z="/_next/static/media/root-tenant-settings.b45695702e284db0d37234d888255e4c.png"},344551:function(a,b){"use strict";b.Z="/_next/static/media/tenant-settings-dialog.995faf3385cd223ae0e23667d75bbdc4.png"},254742:function(a,b,c){(window.__NEXT_P=window.__NEXT_P||[]).push(["/docs/5.36.x/enterprise/multi-tenancy",function(){return c(310085)}])},310085:function(a,b,c){"use strict";c.r(b),c.d(b,{"default":function(){return t}}),c(667294);var d=c(603905),e=c(741664),f=c(387453),g=c(588232),h=c(344551),i=c(768768),j=c(605679),k=c(915761),l=c(892681),m=c(123851),n=c(409845),o=l.A,p=function(a){return function(b){return console.warn("Component "+a+" was not imported, exported, or provided by MDXProvider as global scope"),(0,d.kt)("div",Object.assign({},b))}},q=p("Editor"),r=p("Image"),s={Layout:o,pageData:{title:"Setup Multi-Tenancy",description:"Learn how to configure multi-tenancy in your Webiny project",type:"docs",showPageHeader:!0,fullWidth:!1,version:"5.36.x",canonicalVersion:"5.41.x"},docsearch:{type:"docs",weight:100,versions:["5.36.x"]},navigation:m,versions:n,tableOfContents:[{title:"Overview",slug:"overview",children:[]},{title:"1) Prepare the Project",slug:"1-prepare-the-project",children:[]},{title:"2) Connect Your Project With Webiny Control Panel",slug:"2-connect-your-project-with-webiny-control-panel",children:[]},{title:"3) Create a Certificate in AWS ACM",slug:"3-create-a-certificate-in-aws-acm",children:[]},{title:"4) Configure website Infrastructure",slug:"4-configure-website-infrastructure",children:[]},{title:"5) Deploy Your Project",slug:"5-deploy-your-project",children:[]},{title:"6) Configure Custom Domains for Tenants",slug:"6-configure-custom-domains-for-tenants",children:[]},{title:"FAQ",slug:"faq",children:[{title:"I'm Receiving a Pulumi Error Saying Something About CloudFront Request Headers.",slug:"i-m-receiving-a-pulumi-error-saying-something-about-cloud-front-request-headers",children:[]},{title:"Pulumi Throws an Error While Deleting My Lambda@Edge Function",slug:"pulumi-throws-an-error-while-deleting-my-lambda-edge-function",children:[]},{title:"Pulumi Throws an Error When Creating the Website Cloudfront Distribution",slug:"pulumi-throws-an-error-when-creating-the-website-cloudfront-distribution",children:[]},{title:"What if I Want to Serve All Tenants From a Wildcard Domain or From a Custom Domain, Without Needing to Make Infrastructure Changes or Redeploy My Project?",slug:"what-if-i-want-to-serve-all-tenants-from-a-wildcard-domain-or-from-a-custom-domain-without-needing-to-make-infrastructure-changes-or-redeploy-my-project",children:[]}]}]};function t(a){var b=a.components,c=function(a,b){if(null==a)return{};var c,d,e=function(a,b){if(null==a)return{};var c,d,e={},f=Object.keys(a);for(d=0;d<f.length;d++)c=f[d],b.indexOf(c)>=0||(e[c]=a[c]);return e}(a,b);if(Object.getOwnPropertySymbols){var f=Object.getOwnPropertySymbols(a);for(d=0;d<f.length;d++)c=f[d],!(b.indexOf(c)>=0)&&Object.prototype.propertyIsEnumerable.call(a,c)&&(e[c]=a[c])}return e}(a,["components"]);return(0,d.kt)("wrapper",Object.assign({},s,c,{components:b,mdxType:"MDXLayout"}),(0,d.kt)(k.b,{type:"info",title:"Can I use this?",mdxType:"Alert"},(0,d.kt)("p",null,(0,d.kt)("strong",{parentName:"p"}," A Webiny Control Panel account is required to use this feature."))),(0,d.kt)(k.b,{type:"success",title:"What you'll learn",mdxType:"Alert"},(0,d.kt)("ul",null,(0,d.kt)("li",{parentName:"ul"},"how to enable multi-tenancy in the existing Webiny project"),(0,d.kt)("li",{parentName:"ul"},"how to assign custom domains to each tenant"))),(0,d.kt)(j.X,{level:2,id:"overview",nextElement:{type:"paragraph"}},"Overview"),(0,d.kt)("p",null,"There are a few steps involved in enabling multi-tenancy:"),(0,d.kt)("ul",null,(0,d.kt)("li",{parentName:"ul"},"connecting your project to Webiny Control Panel"),(0,d.kt)("li",{parentName:"ul"},"creating a certificate in AWS ACM, and linking it with CloudFront distribution")),(0,d.kt)("p",null,"Cloudfront will be used for TLS termination, and AWS ACM for certificate management. Customers may use other 3rd party CDNs for those steps. If that’s the case, please get in touch, so we can support you through the setup."),(0,d.kt)("p",null,"The following sections guide you through the process in a step-by-step manner."),(0,d.kt)(j.X,{level:2,id:"1-prepare-the-project",nextElement:{type:"paragraph"}},"1) Prepare the Project"),(0,d.kt)("p",null,"Your project needs to be created starting with version ",(0,d.kt)("inlineCode",{parentName:"p"},"5.29.0")," to use this feature.\nYou can use an existing project, or create a new one by running:"),(0,d.kt)(q,{title:"",lang:"shell",mdxType:"Editor"},"npx create-webiny-project my-project"),(0,d.kt)(j.X,{level:2,id:"2-connect-your-project-with-webiny-control-panel",nextElement:{type:"paragraph"}},"2) Connect Your Project With Webiny Control Panel"),(0,d.kt)("p",null,"First, you will need a Webiny Control Panel account. If you don’t have one, please contact us at ",(0,d.kt)("inlineCode",{parentName:"p"},"sales@webiny.com"),"."),(0,d.kt)("p",null,"Once you have your account, connect your project by running the following command:"),(0,d.kt)(q,{title:"",lang:"shell",mdxType:"Editor"},"yarn webiny login"),(0,d.kt)("p",null,"From here, follow the onscreen instructions."),(0,d.kt)(j.X,{level:2,id:"3-create-a-certificate-in-aws-acm",nextElement:{type:"jsx"}},"3) Create a Certificate in AWS ACM"),(0,d.kt)(k.b,{type:"info",title:"Please note",mdxType:"Alert"},(0,d.kt)("p",null,"Configuration of a custom domain is optional. If you just want to have a multi-tenant GraphQL API,\na custom domain is not required. If that’s what you’re looking for, you can skip to ",(0,d.kt)("a",Object.assign({parentName:"p"},{href:"#5-deploy-your-project"}),"project\ndeployment"),"."),(0,d.kt)("p",null,"However, if you want to build websites for each tenant using the ",(0,d.kt)(e.default,{href:"/docs/5.36.x/overview/applications/page-builder",passHref:!0,legacyBehavior:!0},(0,d.kt)("a",null,"Page Builder")),", you will have\nto setup custom domains. If so, please read on.")),(0,d.kt)("p",null,"Before we modify the infrastructure setup, and deploy the project, we’ll need to have a valid certificate in AWS ACM. Go to ",(0,d.kt)("a",Object.assign({parentName:"p"},{href:"https://console.aws.amazon.com/acm/home?region=us-east-1#/certificates/request"}),"https://console.aws.amazon.com/acm/home?region=us-east-1#/certificates/request")," and request a new certificate, or import an existing one."),(0,d.kt)("p",null,"Once the domains in the certificate are verified, we can proceed to Webiny configuration. For more information on creating certificates and validating hostnames, please go to ",(0,d.kt)("a",Object.assign({parentName:"p"},{href:"https://docs.aws.amazon.com/acm/latest/userguide/acm-overview.html"}),"What Is AWS Certificate Manager? - AWS Certificate Manager")),(0,d.kt)(j.X,{level:2,id:"4-configure-website-infrastructure",nextElement:{type:"paragraph"}},"4) Configure",(0,d.kt)("code",null,"website"),"Infrastructure"),(0,d.kt)("p",null,"To configure the ",(0,d.kt)("inlineCode",{parentName:"p"},"website")," app with your custom domain(s), navigate to ",(0,d.kt)("inlineCode",{parentName:"p"},"apps/website/webiny.application.ts")," and modify it to look like this:"),(0,d.kt)(q,{title:"apps/website/webiny.application.ts",lang:"ts",mdxType:"Editor"},"import { createWebsiteApp } from \"@webiny/serverless-cms-aws\";\n\nexport default createWebsiteApp({\n  pulumiResourceNamePrefix: \"wby-\",\n  domains() {\n    return {\n      domains: [\"my.domain.com\"],\n      sslSupportMethod: \"sni-only\",\n      acmCertificateArn:\n        \"arn:aws:acm:us-east-1:111111111111:certificate/5931d8b4-a39b-4a3a-a4e7-f5bbdd78d599\"\n    };\n  }\n});"),(0,d.kt)("p",null,"For the ",(0,d.kt)("inlineCode",{parentName:"p"},"acmCertificateArn"),", use the ARN of the certificate you created in step 3)."),(0,d.kt)("p",null,"For ",(0,d.kt)("inlineCode",{parentName:"p"},"domains"),", enter the custom domains supported by your certificate. You can use wildcards to support multiple subdomains."),(0,d.kt)(k.b,{type:"info",title:"Recommendation",mdxType:"Alert"},(0,d.kt)("p",null,"Every tenant ",(0,d.kt)("em",{parentName:"p"},"must")," have a corresponding custom domain to enable Webiny to route website requests correctly. Usually we recommend having a wildcard domain, like ",(0,d.kt)("inlineCode",{parentName:"p"},"*.domain.com"),", and have each tenant use a subdomain, like ",(0,d.kt)("inlineCode",{parentName:"p"},"tenant1.domain.com"),". This setup is then easily managed using, for example, CloudFlare, to route real tenant domains to your internal ones.")),(0,d.kt)(j.X,{level:2,id:"5-deploy-your-project",nextElement:{type:"paragraph"}},"5) Deploy Your Project"),(0,d.kt)("p",null,"Now it’s time to deploy the entire project. We need to deploy everything: ",(0,d.kt)("inlineCode",{parentName:"p"},"api"),", ",(0,d.kt)("inlineCode",{parentName:"p"},"admin"),", and ",(0,d.kt)("inlineCode",{parentName:"p"},"website"),". The easiest way to do all 3 at once, is by running the following:"),(0,d.kt)(q,{title:"",lang:"shell",mdxType:"Editor"},"yarn webiny deploy --env=dev"),(0,d.kt)(j.X,{level:2,id:"6-configure-custom-domains-for-tenants",nextElement:{type:"paragraph"}},"6) Configure Custom Domains for Tenants"),(0,d.kt)("p",null,"Once your project is deployed, open your ",(0,d.kt)("inlineCode",{parentName:"p"},"admin")," app. To configure a custom domain for the root tenant, hover over the ",(0,d.kt)("inlineCode",{parentName:"p"},"Root Tenant")," label in the top app bar, and click it. This will open a settings dialog, where you can configure custom domains for your root tenant."),(0,d.kt)(r,{src:i.Z,alt:"Open Root Tenant Settings Dialog.",mdxType:"Image"}),(0,d.kt)(r,{src:h.Z,alt:"Configure Root Tenant Custom Domains.",mdxType:"Image"}),(0,d.kt)(k.b,{type:"danger",title:"Important",mdxType:"Alert"},(0,d.kt)("p",null,"If you don’t have a custom domain for your root tenant, you should enter your CloudFront CDN domain here. Otherwise, the Lambda@Edge router will not be able to route the request to your root tenant website."),(0,d.kt)("p",null,"To find your CloudFront website domain, run ",(0,d.kt)("inlineCode",{parentName:"p"},"yarn webiny info --env=dev")," and look for the ",(0,d.kt)("inlineCode",{parentName:"p"},"Website URL"),".")),(0,d.kt)("p",null,"For your sub-tenants, domain configuration is located in the tenant form:"),(0,d.kt)(r,{src:g.Z,alt:"Domain Settings For Sub-Tenants.",mdxType:"Image"}),(0,d.kt)(j.X,{level:2,id:"faq",nextElement:{type:"heading",depth:3}},"FAQ"),(0,d.kt)(j.X,{level:3,id:"i-m-receiving-a-pulumi-error-saying-something-about-cloud-front-request-headers",nextElement:{type:"paragraph"}},"I'm Receiving a Pulumi Error Saying Something About CloudFront Request Headers."),(0,d.kt)("p",null,"You may occasionally run into an error that goes something like this:"),(0,d.kt)(q,{title:"",lang:"null",mdxType:"Editor"},"error: deleting urn:pulumi:dev::website::aws:cloudfront/distribution:Distribution::delivery: 1 error occurred:\n* CloudFront Distribution E36AOIOBR5JHMQ cannot be deleted: PreconditionFailed: The request failed because it didn't meet the preconditions in one or more request-header fields.\nstatus code: 412"),(0,d.kt)("p",null,"This one is usually resolved by refreshing the Pulumi state of the ",(0,d.kt)("inlineCode",{parentName:"p"},"website")," project application:"),(0,d.kt)(q,{title:"",lang:"shell",mdxType:"Editor"}," yarn webiny pulumi apps/website --env=dev -- refresh --yes"),(0,d.kt)("blockquote",null,(0,d.kt)("p",{parentName:"blockquote"},"NOTE: there’s a space between ",(0,d.kt)("inlineCode",{parentName:"p"},"--")," and ",(0,d.kt)("inlineCode",{parentName:"p"},"refresh"),".")),(0,d.kt)("p",null,"After this, running the ",(0,d.kt)("inlineCode",{parentName:"p"},"deploy")," command goes back to normal."),(0,d.kt)(j.X,{level:3,id:"pulumi-throws-an-error-while-deleting-my-lambda-edge-function",nextElement:{type:"paragraph"}},"Pulumi Throws an Error While Deleting My Lambda@Edge Function"),(0,d.kt)("p",null,"You’re probably seeing the following error:"),(0,d.kt)(r,{src:f.Z,alt:"Error Deleting Lambda@Edge Function.",mdxType:"Image"}),(0,d.kt)("p",null,"Lambda@Edge functions are replicated to all AWS Edge locations. This means that this particular function will not be deleted until AWS removes it from all the Edge locations. Even thought this error looks terrible, your deploy went just fine. Give it a couple of minutes, and re-deploy. You’ll see that, after some time, your old Lambda@Edge function will be deleted successfully."),(0,d.kt)(j.X,{level:3,id:"pulumi-throws-an-error-when-creating-the-website-cloudfront-distribution",nextElement:{type:"paragraph"}},"Pulumi Throws an Error When Creating the Website Cloudfront Distribution"),(0,d.kt)("p",null,"You can’t have the same FQDN configured in more than 1 CF distribution under the same account. If you do, then you will see the following error."),(0,d.kt)(q,{title:"",lang:"shell",mdxType:"Editor"}," +  aws:cloudfront:Distribution delivery **creating failed** error: 1 error occurred:\n +  pulumi:pulumi:Stack website-dev creating error: update failed\n +  pulumi:pulumi:Stack website-dev **creating failed** 1 error\n\nDiagnostics:\n  pulumi:pulumi:Stack (website-dev):\n    error: update failed\n\n  aws:cloudfront:Distribution (delivery):\n    error: 1 error occurred:\n    \t* error creating CloudFront Distribution: CNAMEAlreadyExists: One or more of the CNAMEs you provided are already associated with a different resource.\n    \tstatus code: 409, request id: 0ad55a32-0a28-4411-abee-326808393fb9"),(0,d.kt)("p",null,"Go back to the CF distribution where that hostname is configured, remove it - if it’s safe to do so - and re-run the webiny deploy command."),(0,d.kt)(j.X,{level:3,id:"what-if-i-want-to-serve-all-tenants-from-a-wildcard-domain-or-from-a-custom-domain-without-needing-to-make-infrastructure-changes-or-redeploy-my-project",nextElement:{type:"paragraph"}},"What if I Want to Serve All Tenants From a Wildcard Domain or From a Custom Domain, Without Needing to Make Infrastructure Changes or Redeploy My Project?"),(0,d.kt)("p",null,"Say you have the Webiny ",(0,d.kt)("inlineCode",{parentName:"p"},"website")," app installed on ",(0,d.kt)("inlineCode",{parentName:"p"},"x.cloudfront.com"),". To that CloudFront you need to add a wildcard certificate with a wildcard domain, say ",(0,d.kt)("inlineCode",{parentName:"p"},"*.example.com"),".\nWhen you create a tenant inside Webiny, you need to give them an FQDN. Add one and make sure it matches the wildcard. Example: ",(0,d.kt)("inlineCode",{parentName:"p"},"foo.example.com"),".\nThis way when you create a new tenant with a custom domain, you don’t need to make any updates to CloudFront."),(0,d.kt)("p",null,"Say that the same tenant has a custom domain of ",(0,d.kt)("inlineCode",{parentName:"p"},"www.bar.com"),". To support fully custom FQDN without any CloudFront changes, you need to do the next step."),(0,d.kt)("p",null,"On the DNS of ",(0,d.kt)("inlineCode",{parentName:"p"},"bar.com")," add a CNAME to ",(0,d.kt)("inlineCode",{parentName:"p"},"foo.example.com")," (has to match the wildcard domain on CloudFront) for the ",(0,d.kt)("inlineCode",{parentName:"p"},"www")," entry. On the same CDN make sure to overwrite the forward host header so that the CDN sends ",(0,d.kt)("inlineCode",{parentName:"p"},"foo.example.com")," as the host header to CloudFront when it goes forward. This way the Lambda@Edge function will receive ",(0,d.kt)("inlineCode",{parentName:"p"},"foo.example.com")," as the hostname name and will be able to resolve it to the correct tenant, although the client requested ",(0,d.kt)("inlineCode",{parentName:"p"},"www.bar.com"),"."),(0,d.kt)("p",null,(0,d.kt)("strong",{parentName:"p"},"Note:")," You will need to use a more advanced CDN for this, such as Fastly, Akamai, or a paid version of CloudFlaire as the ability to overwrite the forward host header is not supported in may other CDNs."))}t.isMDXComponent=!0,t.layoutProps=s}},function(a){a.O(0,[35711,7326,65309,49774,92888,40179],function(){return a(a.s=254742)}),_N_E=a.O()}])