Let's deploy a HydePHP site on AWS CloudFront using AWS CDK. This setup delivers sub-second load times at minimal cost.
Here is the folder structure we'll start with:
.
├── cdk/ # AWS CDK infrastructure (S3, CloudFront, Route 53)
├── hyde/ # HydePHP static site source & Markdown content
└── compose.yml # Docker Compose for local PHP development
Prerequisites
- You have a domain registered or managed in Route 53
- You have the CDK CLI installed
- You have an AWS profile configured in your terminal
HydePHP
Using HydePHP is a personal preference, so we'll keep this section concise. The same workflow will work with any other static site generator.
It is a Laravel-based framework for generating static site artifacts—great for a portfolio, blog, or documentation site.
Install HydePHP:
composer create-project hyde/hyde
Running php hyde build (in the /hyde folder) compiles the site into _site/. This is what will be synchronized to the S3 bucket that CloudFront distributes.
CDK
We will define our infrastructure using AWS CDK (Infrastructure as Code).
Create the cdk/ directory, navigate into it, and initialize the project:
cdk init app --language typescript .
Infrastructure Code
Hosted Zone
import * as route53 from "aws-cdk-lib/aws-route53";
const zone = route53.HostedZone.fromLookup(this, "HostedZone", {
domainName: "example.com",
});
We're looking up an already set up Hosted Zone (created after purchasing or configuring your domain).
Certificate
import * as certificatemanager from "aws-cdk-lib/aws-certificatemanager";
const certificate = new certificatemanager.Certificate(this, "Certificate", {
domainName: "example.com",
subjectAlternativeNames: ["www.example.com"],
validation: certificatemanager.CertificateValidation.fromDns(zone),
});
We'll need this for HTTPS.
S3 Bucket
import * as s3 from "aws-cdk-lib/aws-s3";
const bucket = new s3.Bucket(this, "SiteBucket", {
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
enforceSSL: true,
});
This is where the site artifacts will be stored. Public access is blocked so users cannot bypass CloudFront and hit the S3 bucket directly, preventing unexpected egress costs.
Distribution
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
const distribution = new cloudfront.Distribution(this, "Distribution", {
defaultRootObject: "index.html",
domainNames: ["example.com", "www.example.com"],
certificate,
priceClass: cloudfront.PriceClass.PRICE_CLASS_100,
defaultBehavior: {
origin: origins.S3BucketOrigin.withOriginAccessControl(bucket, {
originAccessLevels: [
cloudfront.AccessLevel.READ,
cloudfront.AccessLevel.LIST,
],
}),
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD,
cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD,
compress: true,
},
errorResponses: [
{
httpStatus: 404,
responseHttpStatus: 404,
responsePagePath: "/404.html",
ttl: Duration.seconds(10),
},
],
});
This creates a CloudFront distribution that uses the S3 bucket as its origin.
DNS Records (Route 53)
import * as route53 from "aws-cdk-lib/aws-route53";
const target = route53.RecordTarget.fromAlias(
new route53Targets.CloudFrontTarget(distribution),
);
new route53.ARecord(this, "ApexARecord", { zone, target }); // IPv4
new route53.AaaaRecord(this, "ApexAaaaRecord", { zone, target }); // IPv6
new route53.ARecord(this, "WwwARecord", { zone, recordName: "www", target });
new route53.AaaaRecord(this, "WwwAaaaRecord", {
zone,
recordName: "www",
target,
});
These DNS records route apex and subdomain traffic to our CloudFront distribution.
Deployment
Deploy the infrastructure stack first:
cdk bootstrap— Provisions resources required by CDK on AWS (CloudFormation stack, staging assets)cdk deploy— Deploys the infrastructure stack
Next, deploy the static site files using the AWS CLI:
- Build the site:
php hyde build - Sync the built site to S3:
(whereaws s3 sync ./hyde/_site s3://$(BUCKET)/ --delete$(BUCKET)is your S3 bucket name)
First-time deployments don't require clearing the cache, but subsequent updates will require a cache invalidation:
aws cloudfront create-invalidation --distribution-id $(DISTRIBUTION_ID) --paths "/*"
(where $(DISTRIBUTION_ID) is your CloudFront distribution ID)
Conclusion
You now have a production-ready static site globally distributed with AWS CloudFront!
Check out the gist for the entire code.