Automating Deployments with GitHub CI/CD
Part 5 of the AWS Cloud Resume Challenge
After deploying my website to Amazon S3 and securing it with CloudFront and HTTPS, the site was publicly accessible and performing well. However, the deployment process itself was still manual. Every time I updated my resume, fixed a typo, or changed a line of CSS, I had to manually upload files through the AWS Console.
While this works for small experiments, it quickly becomes inefficient and introduces unnecessary risk. Manual uploads can easily overwrite the wrong files, forget updates, or create inconsistencies between local code and the deployed version.
To solve this problem, I implemented an automated CI/CD pipeline with GitHub Actions that builds the Vite site and deploys it to Amazon S3, then invalidates CloudFront. Authentication uses OIDC federation into AWS—no long-lived access keys stored in GitHub secrets.
This approach reflects how modern cloud environments operate. Instead of manually updating infrastructure or applications, deployments are automated through version-controlled pipelines.
Step 1 - Moving the Website into Version Control
The first step was placing the website source code under version control using GitHub. I created a repository and uploaded all of the files used to build the site, including:
- HTML pages
- CSS stylesheets
- JavaScript files
- Images and static assets
By storing the project in GitHub, the repository becomes the single source of truth for the website. Every change is tracked through commits, making it easy to review the history of the project and revert changes if necessary.
Version control is also an essential component of CI/CD pipelines because deployment systems need a centralized location from which to pull source code.
GitHub repository showing the website source code and project file structure.
Step 2 - Creating the GitHub Actions Workflow
Once the repository was established, I added a workflow under .github/workflows/deploy.yml. On every push to main (and on manual workflow_dispatch), the job:
- Checks out the repository
- Installs Node.js 20 and runs
npm ci - Builds the production site with
npm run build(Vite →dist/) - Assumes an AWS IAM role through GitHub OIDC
- Syncs
dist/to the S3 bucket and invalidates CloudFront
The workflow requests id-token: write so GitHub can mint a short-lived OIDC token that AWS trusts. That replaces storing AWS_ACCESS_KEY_ID / secret keys as repository secrets. Bucket name, CloudFront distribution ID, and role ARN are supplied through GitHub Actions secrets.
Deploy workflow in .github/workflows/deploy.yml.
Step 3 - Federating GitHub to AWS with OIDC
On the AWS side I configured:
- An IAM OIDC identity provider for
token.actions.githubusercontent.com - An IAM role (for example
github-actions-saiful-cloud-resume) that GitHub Actions can assume - A trust policy scoped to this repository (including GitHub’s immutable subject claim format)
- Permissions limited to syncing the website bucket and creating CloudFront invalidations
When the workflow runs, GitHub presents an OIDC token, AWS issues temporary credentials via AssumeRoleWithWebIdentity, and the job uploads only what it needs—then the credentials expire.
IAM role trust policy for GitHub Actions OIDC.
Step 4 - Deploying to S3 and Invalidating CloudFront
After the build finishes, the workflow publishes the static site:
aws s3 sync dist/ s3://$S3_BUCKET --deletekeeps the bucket aligned with the latest buildaws cloudfront create-invalidation --paths "/*"clears edge cache so visitors see the new HTML and assets
Only the built dist/ output is deployed—not the raw source tree—so visitors always get production assets (hashed CSS/JS, compiled pages).
Step 5 - Testing the Pipeline
To confirm everything worked, I pushed a change to main and opened the repository Actions tab. The Deploy to S3 workflow ran through install, build, OIDC assume-role, sync, and invalidation.
After the job succeeded, refreshing the live site showed the update—no AWS Console upload required.
Successful Deploy to S3 workflow run in GitHub Actions.
Lessons Learned
This stage reinforced that automation reduces risk and improves consistency.
Before CI/CD, every deployment meant manually uploading files in the AWS Console. That works for tiny edits, but it is easy to miss files or ship the wrong tree.
With GitHub Actions and OIDC:
- The GitHub repository remains the source of truth
- Every push to
mainproduces the same build-and-deploy path - AWS access uses short-lived federated credentials instead of standing access keys
- CloudFront invalidation is part of the pipeline, so cache does not hide new releases
I also learned that federation trust policies must match the repository exactly—misconfigured OIDC conditions fail at AssumeRoleWithWebIdentity even when the workflow YAML looks correct.
The Result
The website now has a fully automated deployment workflow.
Whenever I push updates to GitHub:
flowchart TD push[GitHub push] --> gha[GitHub Actions builds dist] gha --> oidc[OIDC assumes IAM role] oidc --> s3[S3 sync] s3 --> cf[CloudFront invalidation] cf --> live[Site updated globally]
I can update the site quickly without manually uploading objects in the AWS console.
What's Next
With the deployment process automated, the next step is focusing on cost visibility and monitoring.
Even though most of the services used in this project fall within the AWS Free Tier, monitoring cloud costs early is an important habit for anyone working in cloud engineering.
In the next post, I will configure AWS Budgets and cost alerts to ensure the infrastructure stays within safe spending limits as the project continues to grow.
Next Up: Cost Optimization - Setting Up My AWS Safety Net







