Skip to main content

AWS Static Website Hosting - CDK

Last Updated: February 13, 2020

Infrastructure setup with CDK

S3 / CloudFront

const bucketName = `website`;

// Bucket setup
const siteBucket = new s3.Bucket(this, 'SiteBucket', {
bucketName,
enforceSSL: true,
accessControl: s3.BucketAccessControl.PRIVATE,
removalPolicy: RemovalPolicy.DESTROY,
objectOwnership: s3.ObjectOwnership.BUCKET_OWNER_PREFERRED,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
});

// CloudFront setup
const originAccessIdentity = new cloudfront.OriginAccessIdentity(this, 'OriginAccessIdentity');
siteBucket.grantRead(originAccessIdentity);

const redirectFunctionCode = `
function handler(event) {
const request = event.request;
const uri = request.uri;

// Check whether the URI is missing a file name.
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
// Check whether the URI is missing a file extension.
else if (!uri.includes('.')) {
request.uri += '/index.html';
}

return request;
}
`
const indexRedirectFunction = new cloudfront.Function(this, 'IndexRedirect', {
functionName: `index-redirect-fn`,
code: cloudfront.FunctionCode.fromInline(redirectFunctionCode),
});

new cloudfront.Distribution(this, 'SiteDistribution', {
defaultRootObject: 'index.html',
defaultBehavior: {
origin: new S3Origin(siteBucket, {originAccessIdentity}),
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
functionAssociations: [
{
function: indexRedirectFunction,
eventType: cloudfront.FunctionEventType.VIEWER_REQUEST
}
]
},
});

CodeBuild

// CodeBuild setup
const logsAccess = new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
resources: ['arn:aws:logs:*:*:*'],
actions: [
'logs:CreateLogGroup',
'logs:CreateLogStream',
'logs:PutLogEvents'
],
effect: iam.Effect.ALLOW
})
]
});

const s3Access = new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
resources: [
`${siteBucket.bucketArn}`,
`${siteBucket.bucketArn}/*`
],
actions: [
's3:DeleteObject',
's3:GetBucketLocation',
's3:GetObject',
's3:ListBucket',
's3:PutObject'
],
effect: iam.Effect.ALLOW
})
]
});

const buildRole = new iam.Role(this, 'SiteCIServiceRole', {
assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'),
roleName: `site-ci-service-role`,
inlinePolicies: {
'CodeBuild-Cloudwatch-access': logsAccess,
'CodeBuild-S3-access': s3Access
}
});

const webhookFilters = [
codebuild.FilterGroup.inEventOf(codebuild.EventAction.PUSH)
.andBranchIs('main')
];

const gitHubSource = codebuild.Source.gitHub({
owner: 'techstormpc',
repo: 'wiki',
cloneDepth: 1,
reportBuildStatus: true,
webhook: true,
webhookFilters
});

const buildLogs = new logs.LogGroup(this, 'WebsiteBuildLogs', {
logGroupName: `website-build-logs`,
retention: logs.RetentionDays.ONE_WEEK,
removalPolicy: RemovalPolicy.DESTROY
});

new codebuild.Project(this, 'WebsiteBuildCI', {
projectName: `website-build`,
description: 'Build and deploy website',
source: gitHubSource,
role: buildRole,
environment: {
computeType: codebuild.ComputeType.SMALL,
privileged: true,
buildImage: codebuild.LinuxBuildImage.STANDARD_5_0,
environmentVariables: {
'SITE_BUCKET': {
type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
value: siteBucket.bucketName
}
}
},
buildSpec: codebuild.BuildSpec.fromSourceFilename('buildspec.yml'),
timeout: Duration.minutes(5),
logging: {
cloudWatch: {
enabled: true,
logGroup: buildLogs
}
}
});

Buildspec

build:
commands:
- npm run build

post_build:
commands:
- aws s3 sync "dist/" "s3://${BUCKET}" --delete