Skip to main content

Search the Dev Center

Search tutorials, templates, examples, and more.

Secure static website using Amazon S3, CloudFront, Route53, and Certificate Manager

Static website example

This example lives in the pulumi/examples repository. Check out just this directory to use it:

Get started with this example
git clone --filter=blob:none --sparse https://github.com/pulumi/examples pulumi-examples
git -C pulumi-examples sparse-checkout set aws-py-static-website
cd pulumi-examples/aws-py-static-website

This example serves a static website using Python and AWS.

This sample uses the following AWS products:

This program requires several configuration settings:

  • targetDomain - The domain to serve the website at (e.g. www.example.com). It is assumed that the parent domain (example.com) is a Route53 Hosted Zone in the AWS account you are running the Pulumi program in.
  • pathToWebsiteContents - Directory of the website’s contents, e.g. the ./www folder.
  • certificateArn - (Optional) ACM certificate ARN for the target domain; must be in the us-east-1 region. If omitted, a certificate will be created.

Note: some values in this example will be different from run to run. These values are indicated with ***.

Prerequisites#

  1. Install Pulumi
  2. Configure AWS credentials
  3. Install Python

Deploying the example#

  1. Create a new stack:

    Terminal window
    pulumi stack init website-testing
  2. Set the AWS region and the website configuration:

    Terminal window
    pulumi config set aws:region us-west-2
    pulumi config set targetDomain www.example.com
    pulumi config set pathToWebsiteContents ./www
  3. Install dependencies:

    Terminal window
    python3 -m venv venv
    source venv/bin/activate
    pip install -r requirements.txt
  4. Run pulumi up to preview and deploy changes. After the preview is shown you will be prompted whether to continue or not:

    Terminal window
    pulumi up
    Previewing update (example):
    Type Name Plan
    + pulumi:pulumi:Stack static-website-example create
    + ├─ pulumi:providers:aws east create
    + ├─ aws:s3:Bucket requestLogs create
    + ├─ aws:s3:Bucket contentBucket create
    + │ ├─ aws:s3:BucketObject 404.html create
    + │ └─ aws:s3:BucketObject index.html create
    + ├─ aws:acm:Certificate certificate create
    + ├─ aws:route53:Record ***-validation create
    + ├─ aws:acm:CertificateValidation certificateValidation create
    + ├─ aws:cloudfront:Distribution cdn create
    + └─ aws:route53:Record *** create
  5. To see the resources that were created, run pulumi stack output:

    Terminal window
    pulumi stack output
    Current stack outputs (4):
    OUTPUT VALUE
    cloudfront_domain ***.cloudfront.net
    content_bucket_url s3://***
    content_bucket_website_endpoint ***.s3-website-us-west-2.amazonaws.com
    target_domain_endpoint https://***/
  6. To see that the S3 objects exist, use either the AWS Console or the AWS CLI:

    Terminal window
    aws s3 ls $(pulumi stack output content_bucket_url)
    2020-02-21 16:58:48 262 404.html
    2020-02-21 16:58:48 394 index.html
  7. Open a browser to the target domain endpoint from above to see your beautiful static website. (Since we don’t wait for the CloudFront distribution to completely sync, you may have to wait a few minutes.)

Cleaning up#

Once you’re finished experimenting, destroy your stack and remove it to avoid incurring any additional cost:

Terminal window
pulumi destroy
pulumi stack rm

Troubleshooting#

Scary HTTPS warning#

When you create an S3 bucket and CloudFront distribution shortly after one another, you’ll see what looks to be HTTPS configuration issues. This has to do with the replication delay between S3, CloudFront, and the world-wide DNS system.

Just wait 15 minutes or so, and the error will go away. Be sure to refresh in an incognito window, which will avoid any local caches your browser might have.

“PreconditionFailed: The request failed because it didn’t meet the preconditions”#

Sometimes updating the CloudFront distribution will fail with:

"PreconditionFailed: The request failed because it didn't meet the preconditions in one or more
request-header fields."

This is caused by CloudFront confirming the ETag of the resource before applying any updates. ETag is essentially a “version”, and AWS is rejecting any requests that are trying to update any version but the “latest”.

This error will occur when the state of the ETag gets out of sync between Pulumi Cloud and AWS. (This can happen when inspecting the CloudFront distribution in the AWS console.)

You can fix this by running pulumi refresh to pickup the newer ETag values.

Deployment speed#

This example creates an aws.S3.BucketObject for every file served from the website. When deploying large websites, that can lead to very long updates as every individual file is checked for any changes.

It may be more efficient to not manage individual files using Pulumi and instead just use the AWS CLI to sync local files with the S3 bucket directly.

Remove the call to crawlDirectory and run pulumi up. Pulumi will then delete the contents of the S3 bucket, and no longer manage their contents. Then do a bulk upload outside of Pulumi using the AWS CLI.

Terminal window
aws s3 sync ./www/ s3://example-bucket/

Preview behavior and apply checklist#

This repo includes Pulumi.preview.yaml with safe preview defaults so you can run pulumi preview without a real domain. During preview, some values are stubbed and DNS lookups may be skipped. Before running pulumi up, set real config values:

Terminal window
pulumi config set aws-py-static-website:targetDomain <your-domain>
pulumi config set aws:region us-west-2

If you don’t manage the domain in Route53, remove or skip creating DNS records.

Related

The infrastructure as code platform for any cloud.