docs/deployment/ci-cd.md
CI/CD Pipeline
The portfolio uses GitHub Actions for continuous integration and deployment.
Workflows
Deploy Workflow
File: .github/workflows/deploy.yml
Triggers:
- Push to
mainbranch - Manual workflow dispatch
Jobs:
-
Build & Deploy
- Install dependencies
- Build Next.js with OpenNext
- Run E2E tests (fixture mode)
- Build CDK
- Deploy to AWS
- Invalidate CloudFront cache
- Revalidate ISR caches
-
Smoke Tests
- Run real API tests against deployed app
- Run real UI tests against deployed app
Test Workflow
File: .github/workflows/test.yml
Triggers:
- Pull requests to
main
Jobs:
- Build Next.js
- Run Playwright E2E tests
- Upload test report artifacts
Pipeline Stages

Skip Tests Mode
Add [skip] to commit message to skip tests:
git commit -m "Quick fix [skip]"
Or use workflow dispatch with skip_tests: true.
AWS Authentication
Uses OIDC (OpenID Connect) for secure, keyless authentication:
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.CDK_DEPLOY_ROLE_ARN }}
aws-region: us-east-1
Required IAM Permissions
The deploy role needs:
- CloudFormation full access
- Lambda full access
- S3 full access
- DynamoDB full access
- CloudFront full access
- Route53 (if using custom domain)
- ACM (if using custom domain)
- Secrets Manager read
- IAM role management (for Lambda execution roles)
Environment Variables
From Vars (Non-sensitive)
env:
AWS_REGION: ${{ vars.AWS_REGION }}
APP_DOMAIN_NAME: ${{ vars.APP_DOMAIN_NAME }}
PORTFOLIO_GIST_ID: ${{ vars.PORTFOLIO_GIST_ID }}
From Secrets
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
REVALIDATE_SECRET: ${{ secrets.REVALIDATE_SECRET }}
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
Caching
pnpm Cache
- uses: actions/setup-node@v4
with:
cache: 'pnpm'
Playwright Cache
- uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-chromium-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
Artifacts
Test reports uploaded on failure:
- name: Upload test results
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Cache Invalidation
Post-deploy cache invalidation:
- name: Invalidate CloudFront cache
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ steps.get-distribution.outputs.distribution_id }} \
--paths "/blog" "/blog/*" "/projects" "/projects/*" "/api/*"
Revalidation
ISR cache revalidation via API:
- name: Revalidate server caches
run: |
curl -X POST "${TARGET_URL}/api/revalidate" \
-H "x-revalidate-secret: ${REVALIDATE_SECRET}" \
-d '{"paths":["/projects","/blog"],"tags":["github-repos","posts"]}'
Concurrency
Prevents overlapping deployments:
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true
Troubleshooting
Build Failures
- Check OpenNext build output
- Verify all environment variables set
- Check for TypeScript errors
Test Failures
- Review Playwright report artifact
- Check if fixtures are properly loaded
- Verify test environment configuration
Deploy Failures
- Check CloudFormation events in AWS console
- Review CDK diff output
- Verify IAM permissions
Smoke Test Failures
- Confirm deployment completed
- Check CloudFront distribution is enabled
- Verify DNS propagation (custom domain)
