Table of Contents
Why Automate Frontend Deployment?
Manual deployments are error-prone and time-consuming. Every time you push a frontend change, you'd need to: build the app, upload files to S3, and invalidate CloudFront cache. That's 3 steps that should happen automatically on every merge to main.
With Bitbucket Pipelines, a single git push triggers the entire deployment pipeline — build, deploy, and cache invalidation — in under 2 minutes.
Architecture Overview
The pipeline follows this flow:
1. Developer pushes to the main branch on Bitbucket 2. Bitbucket Pipeline triggers automatically 3. Pipeline builds the frontend (npm run build) 4. Built assets are synced to an S3 bucket 5. CloudFront cache is invalidated so users see the latest version
The S3 bucket serves as origin for CloudFront, which distributes your app across 200+ edge locations globally. Users always hit the nearest edge, getting sub-100ms load times.
Setting Up the S3 Bucket
Create an S3 bucket configured for static website hosting. The key settings are:
- Block all public access (CloudFront will access via OAI/OAC) - Enable static website hosting with index.html as the index document - Set error document to index.html for SPA routing - Add a bucket policy allowing CloudFront's Origin Access Identity to read objects
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowCloudFront",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity XXXXXX"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bucket-name/*"
}]
}Bitbucket Pipeline Configuration
The pipeline is defined in bitbucket-pipelines.yml at the root of your repository. It uses AWS CLI to sync files and invalidate the CloudFront distribution.
# bitbucket-pipelines.yml
image: node:18
pipelines:
branches:
main:
- step:
name: Build and Deploy
caches:
- node
script:
- npm ci
- npm run build
- pipe: atlassian/aws-s3-deploy:1.1.0
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: $AWS_REGION
S3_BUCKET: $S3_BUCKET
LOCAL_PATH: "out"
DELETE_FLAG: "true"
CACHE_CONTROL: "max-age=31536000"
- pipe: atlassian/aws-cloudfront-invalidate:0.6.0
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: $AWS_REGION
DISTRIBUTION_ID: $CF_DISTRIBUTION_IDCloudFront Cache Strategy
Smart caching is critical for performance. Use different cache strategies for different file types:
- HTML files: no-cache (always check for updates, CloudFront still caches with ETag validation) - JS/CSS with hashes: max-age=31536000, immutable (cached forever, filename changes on content change) - Images/fonts: max-age=86400 (1 day cache)
This ensures users always get the latest HTML while maximizing cache hits for static assets.
CloudFront invalidation costs $0 for the first 1,000 paths/month. After that, $0.005 per path. Using /* counts as one path.
Key Takeaways
Bitbucket Pipelines automates build → S3 deploy → CloudFront invalidation on every push
S3 + CloudFront gives global CDN distribution at ~$1/month for static sites
Use Origin Access Identity to keep S3 bucket private — only CloudFront can access it
Set different cache-control headers per file type for optimal performance
Pipeline runs in ~90 seconds — faster than manual deployment
Written by Surya Kanagaraj
Senior Fullstack Developer & AWS Cloud Engineer. Building production serverless apps on AWS. Available for freelancing.