Sitemap

S3 Static Website deployment with AWS CodePipeline pipeline

4 min readJun 30, 2025

--

Image from Freepik

Introduction

This post demonstrates how deployment automation can be achieved for a static website hosted on Amazon S3 using AWS CodePipeline. By integrating a Git-based source repository with a CI/CD pipeline, any change pushed to the repository is automatically propagated to the hosting infrastructure — enabling consistent delivery of static web content.

Project Structure

This tutorial builds upon my earlier post on deploying a static website to S3 with CloudFront and Route 53. In this extended version, a fully automated deployment pipeline is added using AWS CodePipeline.

To support this, the provided CloudFormation template defines and provisions the following components as Infrastructure-as-Code:

  • CodePipeline Pipeline — Defines a CI/CD pipeline with Source and Build stages.
  • S3 Bucket for Artifacts — Used by CodePipeline to store intermediate artifacts such as packaged source files.
  • IAM Roles and Policies — Grants permissions required for CodePipeline and CodeBuild to interact with AWS resources and Git repositories.
  • CodeStar Connection Resource — Enables connectivity between AWS and a Git-based repository provider such as GitLab.
  • CodeBuild Project — Handles syncing static files to the website bucket and triggers a CloudFront invalidation to serve updated content.
Infrastructure schema

Here is the buildspec file configuration:

version: 0.2

phases:
build:
commands:
- echo "Syncing files to S3 bucket $BUCKET_NAME"
- aws s3 sync site_files s3://$BUCKET_NAME --delete
- echo "Invalidating CloudFront cache"
- aws cloudfront create-invalidation --distribution-id $DISTRIBUTION_ID --paths "/*"

Here is the resource configuration in infrastructure/codepipeline_pipeline.yaml CloudFormation template:

AWSTemplateFormatVersion: '2010-09-09'
Description: CodePipeline pipeline for deploying static website from GitLab to S3

Parameters:
StaticWebsiteBucket:
Type: String
Default: ''
CloudFrontDistributionId:
Type: String
Default: ''
ConnectionName:
Type: String
Default: 'Gitlab-to-S3'
FullRepositoryId:
Type: String
Default: 'username/reponame'
BranchName:
Type: String
Default: 'codepipeline'
CodePipelineName:
Type: String
Default: 'DeployToS3StaticWebsite'

Resources:
GitLabConnection:
Type: AWS::CodeConnections::Connection
Properties:
ConnectionName: !Ref ConnectionName
ProviderType: GitLab

PipelineArtifactBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub 'static-site-artifacts-${AWS::AccountId}'

CodeBuildServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: CodeBuildServiceRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: CodeBuildStaticSitePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:ListBucket
Resource:
- !Sub arn:${AWS::Partition}:s3:::${StaticWebsiteBucket}
- !Sub arn:${AWS::Partition}:s3:::${StaticWebsiteBucket}/*
- !Sub arn:${AWS::Partition}:s3:::${PipelineArtifactBucket}
- !Sub arn:${AWS::Partition}:s3:::${PipelineArtifactBucket}/*
- Effect: Allow
Action:
- cloudfront:CreateInvalidation
Resource: !Sub arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistributionId} # "*"
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"

CodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: StaticSiteBuildProject
Source:
Type: CODEPIPELINE
BuildSpec: infrastructure/buildspec.yml
Environment:
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/standard:7.0
Type: LINUX_CONTAINER
EnvironmentVariables:
- Name: BUCKET_NAME
Value: !Ref StaticWebsiteBucket
- Name: DISTRIBUTION_ID
Value: !Ref CloudFrontDistributionId
ServiceRole: !GetAtt CodeBuildServiceRole.Arn
Artifacts:
Type: CODEPIPELINE
LogsConfig:
CloudWatchLogs:
Status: ENABLED

CodePipelineServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: CodePipelineServiceRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codepipeline.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: CodePipelinePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- codebuild:StartBuild
- codebuild:BatchGetBuilds
Resource: !GetAtt CodeBuildProject.Arn
- Effect: Allow
Action:
- codestar-connections:UseConnection
Resource: !GetAtt GitLabConnection.ConnectionArn
- Effect: Allow
Action:
- s3:GetObject
- s3:GetBucketVersioning
- s3:PutObject
Resource:
- !Sub arn:${AWS::Partition}:s3:::${PipelineArtifactBucket}
- !Sub arn:${AWS::Partition}:s3:::${PipelineArtifactBucket}/*

StaticSitePipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
ExecutionMode: QUEUED
Name: !Ref CodePipelineName
PipelineType: V2
RoleArn: !GetAtt CodePipelineServiceRole.Arn
ArtifactStore:
Type: S3
Location: !Ref PipelineArtifactBucket
Stages:
- Name: Source
Actions:
- Name: GitLabSource
ActionTypeId:
Category: Source
Owner: AWS
Provider: CodeStarSourceConnection
Version: 1
OutputArtifacts:
- Name: SourceOutput
Configuration:
ConnectionArn: !Ref GitLabConnection
FullRepositoryId: !Ref FullRepositoryId
BranchName: !Ref BranchName
DetectChanges: true
OutputArtifactFormat: CODE_ZIP
RunOrder: 1
- Name: Build
Actions:
- Name: DeployToS3
ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: 1
InputArtifacts:
- Name: SourceOutput
Configuration:
ProjectName: !Ref CodeBuildProject
RunOrder: 1

Prerequisites

Ensure the following prerequisites are met:

  • An AWS account with necessary permissions.
  • AWS CLI installed and configured locally.
  • A remote Git repository containing the static site files.
  • A deployed static website infrastructure based on S3, CloudFront, and optionally Route 53 (as described in my earlier post).

Deployment

1. Fill in all necessary parameters in the CloudFormation template and create the CloudFormation stack.

aws cloudformation create-stack \
--stack-name static-website-pipeline \
--template-body file://infrastructure/codepipeline_pipeline.yaml \
--capabilities CAPABILITY_NAMED_IAM --disable-rollback

2. Authorise the CodeConnection.
Open the AWS Console, then go to CodePipeline -> Settings -> Connections. Choose the created connection in pending status, update the pending connection, and, depending on the provider, make authorization and grant necessary access to the repository.

3. Add file path to the pipeline trigger filter.
Open the AWS Console, next go: CodePipeline -> Pipelines -> choose the pipeline -> Edit -> Edit triggers -> Edit filter -> add to file paths site_files/** -> Save.

4. Push Static Website changes.
When changes are pushed to the specified directory in the remote repository, the pipeline will automatically:

  • Retrieve and package the updated files.
  • Trigger CodeBuild to upload files to the S3 static site bucket.
  • Invalidate the CloudFront distribution cache to reflect the changes immediately.

If other files are modified outside the specified directory, no action will be taken.

5. Cleanup (Optional).

To delete the artifacts and the CloudFormation stack:

aws s3 rm s3://<artifact-bucket-name> --recursive

aws cloudformation delete-stack \
--stack-name static-website-pipeline

Conclusion

This solution introduces automation into the deployment lifecycle of an S3-hosted static website. By leveraging AWS CodePipeline, CodeBuild, and CodeStar Connections, content updates are applied efficiently and consistently, eliminating the need for manual intervention.

If you found this post helpful and interesting, please click the clap button below to show your support. Feel free to use and share this post. 🙂

--

--

Andrii Shykhov
Andrii Shykhov

Written by Andrii Shykhov

DevOps engineer: AWS, Infrastructure as Code, CI/CD pipelines

No responses yet