Creating a robust infrastructure to serve web assets efficiently is crucial for modern applications. We'll explore how to leverage Pulumi, a powerful infrastructure as code tool, to set up an Amazon S3 bucket and CloudFront distribution. By combining S3's reliable storage with CloudFront's global content delivery network (CDN), we can significantly enhance the performance and availability of our web assets.

Pulumi simplifies the process of provisioning and managing cloud resources, allowing us to define our infrastructure using familiar programming languages. In this guide, we'll walk through the steps to create an S3 bucket for storing our assets and configure a CloudFront distribution to serve them. This approach enables us to take advantage of AWS's scalable and cost-effective services while maintaining full control over our infrastructure.

Setting Up AWS Credentials

reshepe - create s3 bucket and cloudfront distribution using pulumi to serve assets

To use Pulumi with AWS, we need to set up our AWS credentials. This allows Pulumi to authenticate and interact with AWS services on our behalf.

We can configure our AWS credentials in several ways:

  • Using the AWS CLI with aws configure
  • Setting environment variables (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY)
  • Using a shared credentials file (~/.aws/credentials)

For Pulumi, we recommend using the ~/.aws/credentials file. Here's an example of its contents:

[default]
aws_access_key_id = YOUR_ACCESS_KEY_ID
aws_secret_access_key = YOUR_SECRET_ACCESS_KEY

Once we've set up our credentials, Pulumi will automatically detect and use them when we run our infrastructure code.

Remember to keep these credentials secure and never commit them to version control.

Installing and Configuring Pulumi

Pulumi is a powerful infrastructure as code tool that allows us to define cloud resources using familiar programming languages. We'll walk through the essential steps to get Pulumi up and running for our AWS project.

Installing the Pulumi CLI

To begin using Pulumi, we need to install the Pulumi CLI. The installation process varies depending on your operating system.

Install Pulumi

Creating an S3 Bucket with Pulumi

To serve assets using CloudFront, we first need to set up an Amazon S3 bucket. Pulumi makes this process straightforward and allows us to define our infrastructure as code.

We start by importing the necessary Pulumi AWS package. In our Pulumi program, we create an S3 bucket using the aws.s3.Bucket resource

import { Bucket } from '@pulumi/aws/s3';
            
const bucket = new Bucket('cdn.example.com:s3:bucket', {
    bucket:       'cdn.example.com',
    acl:          'private',
    forceDestroy: true,
    
    tags: {
        name: 'cdn.example.com',
    },
});

Configuring S3 Bucket Policies

To ensure proper access to our bucket, we need to set up appropriate policies. We can define a bucket policy using Pulumi:

import { BucketPolicy } from '@pulumi/aws/s3';
import { OriginAccessIdentity } from '@pulumi/aws/cloudfront';

const oai = new OriginAccessIdentity('cdn.example.com:cloudfront:oai', {
    comment: 'cdn.example.com',
});

const bucket_policy = new BucketPolicy('cdn.example.com:s3:bucket:policy', {
    bucket: bucket.bucket,
    policy: interpolate`{
        "Version":   "2012-10-17",
        "Statement": [
            {
                "Effect":    "Allow",
                "Principal": {
                    "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${oai.id}"
                },
                "Action":    "s3:GetObject",
                "Resource":  "arn:aws:s3:::${bucket.bucket}/*"
            }
        ]
    }`,
});

This setup ensures that only CloudFront can access the S3 bucket directly

Setting Up a CloudFront Distribution

CloudFront distributions enable fast and secure content delivery to users worldwide. We'll explore how to define and configure a distribution using Pulumi, set up caching, add custom domains, implement SSL/TLS, and secure access with Origin Access Identity

Implementing SSL/TLS

To secure our distribution with HTTPS, we need an SSL/TLS certificate. We can use AWS Certificate Manager (ACM):

import { Zone } from '@pulumi/aws/route53';

const zone = new Zone('example.com:route53:zone', {
    name: 'example.com',
});

Once we have the zone, we can create an ACM certificate for it:

import { Certificate, CertificateValidation } from '@pulumi/aws/acm';
import { Record }                             from '@pulumi/aws/route53';

const certificate = new Certificate('cdn.example.com:certificate', {
    domainName:              'cdn.example.com',
    subjectAlternativeNames: [
        'www.cdn.example.com',
    ],
    validationMethod:        'DNS',

    tags: {
        Name: 'cdn.example.com',
    },
});

const validation = new Record('cdn.example.com:certificate:validation:dns', {
    name:    certificate.domainValidationOptions[0].resourceRecordName,
    records: [certificate.domainValidationOptions[0].resourceRecordValue],
    ttl:     60,
    type:    certificate.domainValidationOptions[0].resourceRecordType,
    zoneId:  zone.zoneId,
});

const validation_www = new Record('cdn.example.com:certificate:validation:dns:www', {
    name:    certificate.domainValidationOptions[1].resourceRecordName,
    records: [certificate.domainValidationOptions[1].resourceRecordValue],
    ttl:     60,
    type:    certificate.domainValidationOptions[1].resourceRecordType,
    zoneId:  zone.zoneId,
});

const certificate_validation = new CertificateValidation('cdn.example.com:certificate:validation', {
    certificateArn:        certificate.arn,
    validationRecordFqdns: [
        validation.fqdn,
        validation_www.fqdn,
    ],
});

We can now create a CloudFront distribution using Pulumi:

import { Distribution } from '@pulumi/aws/cloudfront';

const distribution = new Distribution('cdn.example.com:cloudfront:distribution', {
    origins:              [
        {
            domainName:     bucket.bucketRegionalDomainName,
            originId:       bucket.id,
            s3OriginConfig: {
                originAccessIdentity: oai.cloudfrontAccessIdentityPath,
            },
        },
    ],
    defaultCacheBehavior: {
        targetOriginId:       bucket.id,
        viewerProtocolPolicy: 'redirect-to-https',
        allowedMethods:       ['GET', 'HEAD', 'OPTIONS'],
        cachedMethods:        ['GET', 'HEAD', 'OPTIONS'],
        forwardedValues:      {
            cookies:     {
                forward: 'none',
            },
            queryString: false,
        },
        minTtl:               0,
        defaultTtl:           2678400,
        maxTtl:               31536000,
    },
    priceClass:           'PriceClass_100',
    enabled:              true,
    isIpv6Enabled:        true,
    restrictions:         {
        geoRestriction: {
            restrictionType: 'none',
        },
    },
    viewerCertificate:    {
        acmCertificateArn: certificate.arn,
        sslSupportMethod:  'sni-only',
    },
    aliases:              [
        'cdn.example.com',
        'www.cdn.example.com',
    ],

    tags: {
        Name: 'cdn.example.com',
    },
});

This setup ensures that our CloudFront distribution serves our S3 bucket directly, sets up caching, adds custom domains, implements SSL/TLS, and secures access with Origin Access Identity

We can now create records in our DNS provider to point to our CloudFront distribution:

import { Record }       from '@pulumi/aws/route53';

const main = new Record('cdn.example.com:dns:main', {
    name:    'cdn.example.com',
    zoneId:  zone.zoneId,
    type:    'A',
    aliases: [
        {
            evaluateTargetHealth: true,
            name:                 distribution.domainName,
            zoneId:               distribution.hostedZoneId,
        },
    ],
});

const www = new Record('cdn.example.com:dns:www', {
    name:    'www.cdn.example.com',
    zoneId:  zone.zoneId,
    type:    'A',
    aliases: [
        {
            evaluateTargetHealth: true,
            name:                 distribution.domainName,
            zoneId:               distribution.hostedZoneId,
        },
    ],
});

bad performance ends here

get started with reshepe today