dora.ed
← Projects
04 / AWS Native CI/CD

React App Deployment with AWS CodePipeline

A fully automated CI/CD pipeline using AWS-native tooling — CodePipeline, CodeBuild, and S3 — to build and deploy a React application on every GitHub push. The pipeline lives entirely inside AWS, demonstrating the alternative to GitHub Actions for teams operating within an AWS-centric delivery workflow.

Key Results

Implemented a fully AWS-native CI/CD pipeline — CodePipeline + CodeBuild + S3 — with zero dependency on external CI tooling, demonstrating a clean alternative to GitHub Actions

Automated the full build-and-deploy cycle on every GitHub push, reducing manual release steps to zero

Documented the architectural trade-offs between AWS-native and GitHub Actions pipelines, making the project a reference comparison for two distinct delivery approaches

Pipeline architecture
GitHubpush to mainwebhook triggerAWS CODEPIPELINEStage 1SourceGitHub Apppulls source✓ SucceededStage 2Build — CodeBuildreads buildspec.ymlnpm ci → npm run build✓ SucceededStage 3Deploy — S3extracts build/ artifactstatic website hosting✓ Succeeded
Problem

Manual deployments are slow and error-prone. Organisations using AWS tooling exclusively need a delivery pipeline that lives entirely within AWS — not one that depends on GitHub-hosted runners or external CI services.

Solution

AWS CodePipeline detects every GitHub push via webhook and triggers an automated 3-stage pipeline. CodeBuild installs dependencies and builds the React app. The build output is deployed directly to S3 — no manual steps, no server to manage.

buildspec.yml — phase by phase
install
npm ci --legacy-peer-deps

Clean, reproducible install from package-lock.json. Faster than npm install and guarantees the same versions every run.

build
npm run build

React production build via react-scripts. Outputs optimised static files to the build/ directory.

post_build
echo Build complete.

Confirmation step. Could be extended to send notifications or run post-build validation.

artifacts
base-directory: build

Tells CodePipeline to deploy only the build/ output — not the source code, not node_modules.

CodePipeline vs GitHub Actions
Pipeline location
CPAWS Console (CodePipeline)
GHAGitHub repo (.github/workflows/)
Build environment
CPAWS CodeBuild container
GHAGitHub-hosted runner
AWS authentication
CPCodePipeline IAM service role
GHAIAM OIDC — no stored keys
Build config file
CPbuildspec.yml
GHA*.yml in .github/workflows/
Visibility
CPAWS Console
GHAGitHub Actions tab
Cost
CP~$1/month per pipeline
GHAFree tier (2,000 min/month)
Engineering decisions
Why CodePipeline over GitHub Actions for this project

GitHub Actions keeps the pipeline inside GitHub — the YAML lives in the repo and the runner is GitHub-hosted. CodePipeline keeps everything inside AWS — the pipeline is configured in the AWS console, CodeBuild runs the build in an AWS-managed container, and the deployment is handled by AWS directly. Organisations already deep in the AWS ecosystem often prefer CodePipeline because it integrates natively with CodeBuild, CodeDeploy, and the rest of the AWS developer tooling suite.

Why buildspec.yml must be at the repo root

CodeBuild looks for buildspec.yml at the root of the source directory by default. The file defines all build phases — install, pre_build, build, post_build — and the artifacts section tells CodePipeline exactly which files to pass to the deploy stage.

Why npm ci instead of npm install

npm ci installs exactly what is in package-lock.json without resolving. It is faster than npm install on CI because it skips the resolution step, and it is fully reproducible — the same versions every time regardless of when the build runs.

Why base-directory: build, not dist

Create React App outputs its production build to a build/ directory, not dist/. This is a common source of confusion because many other tools (Vite, Rollup, webpack) default to dist/. Setting the wrong base-directory in buildspec.yml means CodePipeline deploys nothing — or worse, deploys source files instead of the compiled output.

Lessons learned
01

The artifacts base-directory must match the build tool's output exactly. Create React App outputs to build/, not dist/. Getting this wrong means CodePipeline either deploys nothing or deploys the wrong files silently.

02

eslintrc.js config files that reference external packages will fail the CodeBuild if those packages are not installed. The safest approach is to rely on react-scripts' built-in ESLint config and remove any custom eslintrc files.

03

CodeBuild automatically retries failed builds once. This is useful for transient failures but can mask the real error — always read the build logs rather than waiting for the retry to potentially succeed.

04

S3 bucket policy errors with spaces in the ARN (arn:aws:s3:::bucket-name /*) fail silently — the policy saves successfully but GetObject requests return 403. Always validate the ARN has no spaces before the /*.

Tech stack
AWS CodePipelineAWS CodeBuildAmazon S3ReactGitHubbuildspec.ymlIAM service rolesNode.js 18
View repository ↗View live app ↗
Compare with the GitHub Actions approach

Project 03 solves the same delivery problem using GitHub Actions and IAM OIDC instead of CodePipeline.

View Project 03 →