If you want to know how to deploy a static web in Cloudfront, using S3 to store your files and with your own domain this is your site
AWS Services and Tools
First of all, let’s do a little recap of the services that we will use
AWS S3
Amazon Simple Storage Service (Amazon S3) is an object storage service that offers industry-leading scalability, data availability, security, and performance
AWS S3 page – https://aws.amazon.com/s3/
This service offers buckets where you can put files. We will use this service to storage our web files
AWS Cloudfront
Amazon CloudFront is a fast content delivery network (CDN) service that securely delivers data, videos, applications, and APIs to customers globally with low latency, high transfer speeds, all within a developer-friendly environment
AWS Cloudfront page – https://aws.amazon.com/cloudfront/
With Cloudfront we can distribute our files all over the world. We will use this service to create and endpoint connected to our S3 bucket to serve our web
AWS Certificate Manager
AWS Certificate Manager is a service that lets you easily provision, manage, and deploy public and private Secure Sockets Layer/Transport Layer Security (SSL/TLS) certificates for use with AWS services and your internal connected resources
AWS Certificate Manager page – https://aws.amazon.com/certificate-manager/
With Certificate Manager you can create SSL Certificates to serve our web with https and with a nice url
AWS CDK
The AWS Cloud Development Kit (AWS CDK) is an open source software development framework to model and provision your cloud application resources using familiar programming languages
AWS CDK page – https://aws.amazon.com/cdk/
With the Cloud development Kit developed by AWS you can define your infrastructure in several programming languages with handy features like loops, conditionals, etc…
Prerequisites
- An AWS account: If you don’t have one you can check this page to create a new one
- AWS credentials created: You must setup your AWS credentials in your account. See this link to get more information on how to create them. Then use this link to know how to config your credentials in your environment
- AWS CDK installed: You can see how to install the CDK in this page
- An internet domain: You can buy a domain in many pages. Use your favorite one
Let’s start
Create a new directory and config your environment. In this tutorial we will use Python as the language
mkdir static-example
cd static-example
cdk init --language python
python3 -m venv .env
source .env/bin/activate
pip install -r requirements.txt
With this command we will have the environment with the python modules installed and with an empty cdk project like this:
.
├── README.md
├── app.py
├── cdk.json
├── requirements.txt
├── setup.py
└── static_example
├── __init__.py
└── static_example_stack.py
Let me point the important files:
- app.py: Entrypoint to our infrastructure. See Apps on the CDK Documentation for more information
- static_example/static_example_stack.py: Our stack. See Stacks on the CDK Documentation for more information
- setup.py: Setup configurations of the project
In the static_example.py
file we will define our AWS resources
Creating the S3 bucket
We need to install the aws-cdk.aws-s3
module. Edit setup.py
and add the module in the install_requires
variable
install_requires=[
"aws-cdk.core==1.23.0",
"aws-cdk.aws-s3==1.23.0"
]
Install the new modules from the console
pip install -r requirements.txt
Modify static_example_stack.py
from aws_cdk import (
core,
aws_s3 as s3
)
class StaticExampleStack(core.Stack):
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
bucket = s3.Bucket(self, "MyBucket",
access_control=s3.BucketAccessControl.PUBLIC_READ,
website_index_document="index.html"
)
bucket.grant_public_access()
In line 3 we are importing the s3 module
In line 11 we create our bucket. We must set PUBLIC_READ in order to let anyone from internet access the bucket
In line 15 we grant public access to make this bucket public
Synthesize an AWS Cloudformation template by running
cdk synth
This command will create an AWS Cloudformation and store it in a new directory cdk.out
.
├── README.md
├── app.py
├── cdk.context.json
├── cdk.json
├── cdk.out
│ ├── cdk.out
│ ├── manifest.json
│ ├── static-example.template.json
│ └── tree.json
├── requirements.txt
├── setup.py
└── static_example
├── __init__.py
└── static_example_stack.py
You can see the template opening the file static-example.template.json
Now we can deploy our stack to AWS
cdk deploy
Congratulations you’ve created your first bucket with CDK
Create the certificate
We need to create a certificate in order to serve our page over https with our own domain
There is a caveat when creating the certificate. If you want to use it with cloudformation you must create the certificate in the us-east-1
region. If you’re deploying your website to this region there is no problem, but if you’re trying to deploy in other region you have to do a little trick in order to make the stack works
First we will need the aws-cdk.aws-ssm
and aws-cdk.aws-certificatemanager
modules. Edit the setup.py
file and add them to the install_requires
variable
install_requires=[
"aws-cdk.core==1.23.0",
"aws-cdk.aws-s3==1.23.0",
"aws-cdk.aws-certificatemanager==1.23.0",
"aws-cdk.aws-ssm==1.23.0"
]
Install the new requirements
pip install -r requirements.txt
Create a file called certificate_stack.py
in the static_example
directory like this
from aws_cdk import (
core,
aws_certificatemanager as cm,
aws_ssm as ssm,
)
class CertificateStack(core.Stack):
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
certificate = cm.Certificate(self, "MyCertificate",
domain_name="static.rubenjgarcia.es",
validation_method=cm.ValidationMethod.DNS
)
ssm.StringParameter(self, "MyCertificateARNParameter",
parameter_name="certificate-arn",
string_value=certificate.certificate_arn
)
In line 12 we create the certificate. Put your domain name like I’ve done in line 13
In line 17 we create a parameter in the AWS Systems Manager Parameter Store. We need to create this parameter to store the ARN of our certificate because we can’t access to the information of one stack from another in other region in Cloudformation
Now we have to edit the app.py file to create another stack. We have to do this because we are creating this stack in other region so we have to change the environment of the stack
#!/usr/bin/env python3
from aws_cdk import core
from static_example.static_example_stack import StaticExampleStack
from static_example.certificate_stack import CertificateStack
app = core.App()
CertificateStack(app, "certificate", env=core.Environment(region="us-east-1"))
StaticExampleStack(app, "static-example")
app.synth()
In line 6 we import our new stack
In line 9 we create the new stack with a new environment pointing to us-east-1
region
Now we can deploy our new stack
cdk deploy certificate
AWS must validate that you are the owner of the domain so when the stack will be deploying you have to add a DNS in your server. You will see something like this while the stack is deploying
Content of DNS Record is: {Name: _xxx.static.rubenjgarcia.es.,Type: CNAME,Value: _xxx.acm-validations.aws.}
Add the CNAME in your server. After a few seconds the deployment will continue
Create Cloudfront distribution
The last step is create the Cloudfront distribution. We need to add 2 more modules in setup.py
, aws-cdk.aws-cloudfront
and aws-cdk.custom-resources
install_requires=[
"aws-cdk.core==1.23.0",
"aws-cdk.aws-s3==1.23.0",
"aws-cdk.aws-certificatemanager==1.23.0",
"aws-cdk.aws-ssm==1.23.0",
"aws-cdk.aws-cloudfront==1.23.0",
"aws-cdk.custom-resources==1.23.0"
]
Install the new requirements again
pip install -r requirements.txt
Modify the static_example_stack.py
like this
import time
from aws_cdk import (
core,
aws_s3 as s3,
aws_cloudfront as cf,
custom_resources as cr
)
class StaticExampleStack(core.Stack):
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
bucket = s3.Bucket(self, "MyBucket",
access_control=s3.BucketAccessControl.PUBLIC_READ,
website_index_document="index.html"
)
bucket.grant_public_access()
parameter = cr.AwsCustomResource(self, "GetCertificateArn",
on_update=cr.AwsSdkCall(
service="SSM",
action="getParameter",
parameters={
"Name": "certificate-arn"
},
region="us-east-1",
physical_resource_id=str(time.time)
)
)
cf.CloudFrontWebDistribution(self, "CDN",
price_class=cf.PriceClass.PRICE_CLASS_100,
alias_configuration=cf.AliasConfiguration(
names=["static.rubenjgarcia.es"],
acm_cert_ref=parameter.get_data_string("Parameter.Value"),
ssl_method=cf.SSLMethod.SNI,
security_policy=cf.SecurityPolicyProtocol.TLS_V1_1_2016
),
origin_configs=[
cf.SourceConfiguration(
behaviors=[
cf.Behavior(
is_default_behavior=True)
],
s3_origin_source=cf.S3OriginConfig(
s3_bucket_source=bucket
)
)
]
)
In line 21 we get the parameter that stores the ARN of the certificate using a Custom Resource
In line 33 we create the Cloudfront distribution pointing to our bucket as you can see in line 48
As we are using an agnostic environment to deploy our stack we have to bootstrap it in order to use custom resources
cdk bootstrap
Now you can deploy the static-example
to AWS
cdk deploy static-example
Once is deployed we need to create another CNAME pointing our web to the Cloudfront endpoint
Open the manifest.json
file in the cdk.out
directory. We need the logicalId of our cloudfront distribution. We will find it under the artifacts > static-example > metadata > /static-example/Cloudfront/CFDistribution > data
path. You will see something like CloudfrontCFDistribution3AF3BD0B
Now use the AWS CLI to get the resource information from Cloudformation
aws cloudformation describe-stack-resource --stack-name static-example --logical-resource-id CloudfrontCFDistribution3AF3BD0B
This is the output that you will get
{
"StackResourceDetail": {
"StackName": "static-example",
"StackId": "arn:aws:cloudformation:eu-west-1:637673531073:stack/static-example/29e92d90-4aa8-11ea-a6d4-0297e5fc50ee",
"LogicalResourceId": "CloudfrontCFDistribution3AF3BD0B",
"PhysicalResourceId": "D3BO1RFLKIX8XA",
"ResourceType": "AWS::CloudFront::Distribution",
"LastUpdatedTimestamp": "2020-01-01T00:00:00.000Z",
"ResourceStatus": "CREATE_COMPLETE",
"Metadata": "{\"aws:cdk:path\":\"static-example/Cloudfront/CFDistribution\"}",
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
}
}
}
The value that we are looking for is under the StackResourceDetail > PhysicalResourceId
path
Use the CLI to get the domain name of the Cloudfront distribution
aws cloudfront get-distribution --id D3BO1RFLKIX8XA
The value that you are looking is under the path Distribution > DomainName
Create a CNAME of your domain pointing to the value of the DomainName
Upload content to your bucket
Now that we have everything up and running we can upload our files to the bucket and see it in our web browser
First create an index.html
file
<!DOCTYPE html>
<html>
<body>
<h1>I'm a static web page served from Cloudfront. I live in a S3 bucket</h1>
</body>
</html>
We need the physical name of our bucket. Use the CLI to get that information like we did with the Cloudfront distribution. The path in the manifest.json
file is artifacts > static-example > metadata > /static-example/MyBucket/Resource > data
Once you get the resource details, use the PhysicalResourceId
in the path that you pass to the CLI to upload the index.html
file
aws s3 cp index.html s3://static-example-mybucketa78f4fb1-1utievfrkntla
And that’s all. You can navigate to your web and you will see the content in the browser