{"id":22712060,"url":"https://github.com/buildit/website-infra","last_synced_at":"2026-05-17T15:08:42.792Z","repository":{"id":78235781,"uuid":"122500769","full_name":"buildit/website-infra","owner":"buildit","description":"Static website simple infrastructure provisioning (AWS: CloudFront, S3, custom domain, https). Including support for A/B testing","archived":false,"fork":false,"pushed_at":"2018-02-27T15:21:35.000Z","size":40,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-09-13T17:02:28.479Z","etag":null,"topics":["aws","aws-cloudformation","aws-cloudfront","aws-s3"],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/buildit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-02-22T15:57:45.000Z","updated_at":"2018-05-17T18:10:32.000Z","dependencies_parsed_at":"2023-03-19T13:11:20.472Z","dependency_job_id":null,"html_url":"https://github.com/buildit/website-infra","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/buildit/website-infra","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buildit%2Fwebsite-infra","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buildit%2Fwebsite-infra/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buildit%2Fwebsite-infra/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buildit%2Fwebsite-infra/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/buildit","download_url":"https://codeload.github.com/buildit/website-infra/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buildit%2Fwebsite-infra/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33143276,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-17T09:28:26.183Z","status":"ssl_error","status_checked_at":"2026-05-17T09:27:52.702Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","aws-cloudformation","aws-cloudfront","aws-s3"],"created_at":"2024-12-10T13:09:16.474Z","updated_at":"2026-05-17T15:08:42.778Z","avatar_url":"https://github.com/buildit.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Website infrastructure provisioning\n\nCreate a single environment for hosting a static website on S3, served by CloudFront CDN on https with a custom DNS domain.\n\nIt also provides optional support for A/B testing.\n\nThe environment is defined in a configuration file.\n\nBash scripts allows to create/update the infrastructure, enable/disable the custom DNS record pointing the website, enable/disable A/B testing.\n\nCloudFormation is used to manage AWS Resources.\n\n## Prerequisites\n\n### AWS resources prerequisites\n\n- A valid SSL certificate in ACM *Region us-east-1* for the custom domain. Certificates is loaded in any other reagion are not available to CloudFront\n- A custom DNS Zone hosted on Route53. The website DNS record will be added (may be a sub-domain or APEX). The Route53 Zone must be in the same AWS account used for the website.\n\n### Control machine prerequisites\n\nOn the machine running these scripts you need:\n\n- AWS CLI installed\n- Credentials of an AWS user with permissions to manage S3 buckets, CloudFront, Route53, Lambda and using CloudFormation\n\nTo build and deploy Lambda functions for A/B testing you also need:\n- Node.js v6.5.0 or later\n- Serverless.com installed globally [see documentation](https://serverless.com/framework/docs/providers/aws/guide/installation/)\n\nYou don't need Node or Serverless if you are not going to use A/B testing.\n\n\nScripts expect AWS CLI credentials either with `aws configure` or setting  `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` env variables.\n\nThe AWS Region is defined in the environment configuration file (see below). Any default Region is irrelevant ( `AWS_DEFAULT_REGION` or `aws configure`d Default Region are ignored).\n\n\n## Description of the infrastructure\n\n- CloudFront Distribution\n    - Default behaviour pointing to *main* Origin, an S3 bucket\n    - Distribution is https only (http redirects to https)\n    - Custom domain, hosted on Route53\n    - A valid SSL certificate is required for https on the custom domain\n- Optionaly, creates a second *experiment* Origin for A/B testing\n- A S3 bucket hosts website content (*main*)\n    - Static Website Hosting is on\n    - CloudFront uses \"Custom Origin\" (as opposed to \"S3 Origin\") to access the bucket\n- A second, optional S3 bucket for *experiment* website, if you plan to use A/B testing\n    - When A/B testing is not enabled the content of this bucket is ignored\n- Another optional S3 bucket for CloudFront logs\n- A DNS record (an *Alias* Route53 record) pointing to the CloudFront Distribution endpoint. \n    - The Zone must be hosted under the same AWS account\n    - The Hostname may be any subdomain of and Hosted Zone, including the APEX (root). \n    - The DNS record is created with a separate operation. This allows switching the traffic when everything is set up and settled down.\n- A/B testing uses a single Lambda function, deployed and attached to the CloudFront Distribution as *Origin Request* handler.\n    - A/B testing is enabled/disabled with a separate operation, when required\n\n## Provision infrastructure for a single environment\n\nA configuration file defines a single website environment (e.g. \"Production\" or \"Staging\").\n\nBash scripts contol infrastructure provisioning and configuration.\nThe actual provisioning is controlled by CloudFormation, asynchronously, but scripts wait until changes are completed (or failed).\n\n## Environment configuration file\n\nAn environment config file completely defines one environment of one website.\n\nSample config files are available in `./config`.\n\nA configuration file must define all the following:\n\n- `ENV_NAME`: Name if the environment used as base name for the CloudFormation stacks. Alphanumeric an hyphens only.\n- `RESOURCES_REGION`: All S3 buckets and CloudFormation template go into this Region. Other resources are global or are always deployed in `us-east-1`\n- `SITE_BUCKET`: Name of the S3 bucket that will host the (main version of) the website. Must be a valid S3 bucket name (globally unique, valid DNS name)\n- `LOGS_BUCKET` (optional): Name of the bucket that will store CloudFront logs. Must be a valid S3 bucket name (globally unique, valid DNS name)\n- `WEBISTE_DNS_NAME`: FQ DNS name of the website. Must be in the specified domain, but may be the APEX\n- `WEBSITE_DOMAIN`: Route53 Hosted Zone\n- `CERTIFICATE_ARN`: ARN of an SSL certificate already loaded in ACM **`us-east-1` Region**, for the website DNS name\n- `CDN_PRICE_CLASS` ('PriceClass_All', 'PriceClass_200' or 'PriceClass_100'): CloudFront CDN price class. See [CloudFront API Reference](https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_CreateDistribution.html#cloudfront-CreateDistribution-request-PriceClass)\n\nA configuration file is bound to a single webiste instance, globally (you cannot reuse the same configuration in another Region or AWS account).\n\n### CloudFormation stack names\n\nNames of the CloudFormation stacks are derived from `ENV_NAME`.\n\n- Infrastructure stack: *website-infra-\u003cENV_NAME\u003e* (in Region `RESOURCES_REGION`)\n- DNS stack: *website-dns-\u003cENV_NAME\u003e*  (in Region `RESOURCES_REGION`)\n- A/B testing Lambda stack: *website-lambda-\u003cENV_NAME\u003e (in Region `us-east-1`)\n\nCloudFormation Stack names must be unique in a AWS Account and Region. \n\nS3 bucket names must be globally unique across AWS.\n\n\n## Scripts\n\nAll commands expects the path to a valid environment configuration file as single parameter: `\u003ccommand\u003e \u003cpath-to-conf-file\u003e`\n\ne.g.\n```\n$ ./apply-infra.sh config/justatest.conf\n```\n\nSome of the scripts take a while to complete (30-60 mins), as they modify CDN configuration and wait for the change to propagate (unfortunately, AWS CLI provides no simple way of showing whether it is waiting or is dead. So commands freeze until completed or failed).\n\nCommands:\n- `apply-infra.sh \u003cconfig-file\u003e`: Create/Update all resources except the DNS record. When the infra is ready the website is reachable throght the CloudFront endpoint. \n- `add-dns-record.sh \u003cconfig-file\u003e`: Requires an up-and-ready Infra stack. Creates a DNS record in the Route53 Hosted Zone pointing to the CloudFront distribution.\n- `delete-all-contents.sh \u003cconfig-file\u003e`: Clears all buckets, including logs.\n- `remove-dns-record.sh \u003cconfig-file\u003e`: Remove the DNS record pointing to CloudFront Distirbution, but  doesn't touch the infrastructure.\n- `remove-infra.sh \u003cconfig-file\u003e`: Completely remove the infrastructure. If any bucket contains any content (including the loging bucket) removal will fail. Use `delete-all-contents.sh` to get rid of them, before removing the infra. \n\nOther scripts:\n- `show-status.sh \u003cconfig-file\u003e`: Show status and outputs of all stacks\n- `invalidate-cache.sh \u003cconfig-file\u003e`: Invalidate CloudFront Distibution cache\n\n### Adding DNS record\n\nThe DNS record is handled separately from the infrastructure, to allow switching real traffic when everything is set up, distributed and tested.\n\nWithout the DNS record the website is accessible from the CloudFront endpoint (https).\n\n\n## A/B Testing\n\n`AB_EXPERIMENT_BUCKET` must be defined in the configuration file, to be able to switch A/B testing on.\nThis creates an additional bucket to host an experimental version of the website. This content is not served when A/B testing is switched off.\n\nThe experimental version of the website must be loaded into the *Experiment* bucket before turning A/B testing on.\n\n### Enabling A/B testing\n\n```\n$ enable-ab-testing.sh \u003cconfig-file\u003e\n```\n\nThis creates a Lambda function and attaches it to the CloudFront Distribution.\n\nWhen the CDN modification is completely propagated, CloudFront serves either versions randomly, 50-50%.\n\nThe version served to a client is kept stable for the duration of a session, using a cookie (`X-Source`).\n\nEnabling A/B testing does three things:\n1. Deploys the Lambda function after replacing the name and region of the experiment bucket in the code (Lamba@Edge cannot use env parameters). If the function already exists, it overwrites it and publish a new numbered versions.\n2. Modifies the CloudFront Distribution attaching the function to the Origin Request event. This takes a while to propagate, as any change to a Distribution\n3. Invalidate CloudFront Distribution cache\n\n### Disabling A/B testing\n\n```\n$ disable-ab-testing.sh \u003cconfig-file\u003e\n```\n\nDisabling A/B testing does two things:\n1. Modifies the CloudFront Distribution detaching the function (...takes a while)\n2. Invalidates Distribution cache\n\nWhen A/B testing is turned off, the `X-Source` cookie is ignored and the website version in *Main* bucket is served by default.\n\nDisabling A/B testing **does not** remove the Lambda function, due to an intrinsic limitation of Lambda@Edge.\nAny function that has been attached to a Distribution cannot be immediately deleted after being detached.\nThe function CDN replica are dropped very asynchronous process that cannot be monitored and may take hours.\n\nAnyhow, a deployed but unused Lambda function if free. It will be overwritten when you enable A/B testing again.\n\n\n## Known issues\n\n### Do not immediately serve real (production) content from a newly created S3 bucket\n\nThere is a [known issue](https://forums.aws.amazon.com/thread.jspa?threadID=216814) when CloudFront serves content from a brand new S3 bucket. \nClient requests may be temporarely redirected to the bucket endpoint, causing an error.\nThis is due to an incomplete replication of the bucket across Regions.\n\nWhen you create a brand new environment, wait a while before redirecting any real traffic to it.\n\nIf you try to access the content too early you get redirected to `https://website-\u003cenv\u003e.site.s3-\u003cregion\u003e.amazonaws.com/index.html`.\nThe browser complains the SSL certificate is not validate, but even if you bypass validation you get an *Access Denied* error, as the bucket is not directly accessible.\n\nUnfortunately, there is no AWS CLI command to signal when the bucket is settled down.\nJust try until it works.\nIt may take tenths of minutes.\n\n\n### CloudFormation error when \"No change is requried\"\n\nIf you run `apply-infra.sh` when no actual change is requried, CloudFormation explodes with an error:\n```\nAn error occurred (ValidationError) when calling the UpdateStack operation: No updates are to be performed.\nFailed update-stack of website-justatest-infra stack.\n```\n\nThis CloudFormation behaviour is utterly stupid but unavoidable.\nAFAIK there is no way of having CloudFormation gently telling you \"no change required\" nor the AWS CLI returns a specific status for this error. No easy way of intercepting this condition and exiting gently (maybe grepping stdout but... :facepalm:)\n\n### CloudFormation Stack stuck in `ROLLBACK_FAILED`\n\nIf the CloudFormation stack ends up in `ROLLBACK_FAILED` there is no other way than deleting the stack with `remove-infra.sh` and retry. \n\n## Useful AWS CLI commands\n\n### List SSL Certificates\n\n```\n$ aws acm list-certificates --region us-east-1 --query 'CertificateSummaryList[*].{Domains:DomainName,ARN:CertificateArn}' --output text\n```\n\nShows certificates loaded in `us-east-1` and available to CloudFront.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbuildit%2Fwebsite-infra","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbuildit%2Fwebsite-infra","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbuildit%2Fwebsite-infra/lists"}