┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Dev │───▶│ Test │───▶│ Production │
│ (auto) │ │ (auto) │ │ (manual) │
└─────────────┘ └─────────────┘ └─────────────┘
▲
│
┌──────┴──────┐
│ PR Tests │ ◀── Unit tests, lint, SAST
│ (gate) │ run on every PR + push
└─────────────┘
| Environment | Purpose | Deployment | URL (example) |
|---|---|---|---|
| Dev | Integration testing, E2E tests | Automatic after unit tests pass | https://dev.toyswap.example.com |
| Test | QA / UAT validation | Automatic after E2E tests pass in Dev | https://test.toyswap.example.com |
| Production | Live users | Manual approval required | https://toyswap.example.com |
- Backend Tests & Lint — Checkstyle +
mvn verify(JUnit 5, MockMvc, H2) - Frontend Tests & Lint — ESLint + Prettier + TypeScript check + Vitest (with MSW mocks)
- SAST Scan — Semgrep runs OWASP Top 10, Java, TypeScript, and React rulesets
- All three jobs must pass before the PR can be merged
- Steps 1-3 above run first
- Docker Build — Backend JAR + Frontend dist packaged into Docker images
- Deploy to Dev — Images deployed to dev environment
- E2E Tests — Playwright runs against the live dev deployment
- Deploy to Test — Automatic if all E2E tests pass
- Deploy to Production — Waits for manual approval (see below)
- Go to the repository on GitHub
- Click the Actions tab
- Find the latest CI/CD Pipeline workflow run on the
mainbranch - The run will show the
deploy-productionjob as "Waiting" with a yellow clock icon - Click "Review deployments"
- Check the production environment checkbox
- Optionally add a comment (e.g., "Approved after QA sign-off")
- Click "Approve and deploy"
The production deployment will then execute automatically.
You can deploy any commit to production manually:
- Go to Actions → CI/CD Pipeline
- Click "Run workflow" (top right)
- Select branch:
main - Choose deploy environment:
production - Click "Run workflow"
This will run the full pipeline and deploy to production (still requires environment approval).
gh workflow run "CI/CD Pipeline" \
--ref main \
-f deploy_env=productionTo enable the manual approval gate, configure GitHub Environments:
Go to Settings → Environments → New environment
Create three environments: dev, test, production
For the production environment:
- Required reviewers: Add at least one team member who must approve
- Wait timer (optional): Add a delay (e.g., 5 minutes) for cool-down
- Deployment branches: Restrict to
mainonly
Each environment needs these secrets (adjust per environment):
| Secret | Description |
|---|---|
AWS_ROLE_ARN |
IAM role ARN for OIDC authentication |
ECR_REGISTRY |
ECR registry URL (e.g., 123456789.dkr.ecr.us-east-1.amazonaws.com) |
DATABASE_URL |
PostgreSQL connection string |
DATABASE_USERNAME |
DB username |
DATABASE_PASSWORD |
DB password |
docker compose up --buildThis starts:
- Backend on
http://localhost:8080 - Frontend on
http://localhost:80 - PostgreSQL on
localhost:5432
# Terminal 1: Backend
cd backend && ./mvnw spring-boot:run
# Terminal 2: Frontend
cd frontend && npm run dev# Backend unit tests
cd backend && ./mvnw verify
# Frontend unit tests
cd frontend && npm test
# E2E tests (starts both servers automatically)
./start-test-env.sh
# In another terminal:
cd frontend && npx playwright testThe project uses Husky for Git hooks:
| Hook | What it does |
|---|---|
pre-commit |
Runs ESLint + Prettier on staged frontend files (via lint-staged) |
pre-push |
Runs frontend unit tests + backend unit tests |
npm install # installs Husky from root package.jsonTo run locally:
brew install semgrep
semgrep scan --config auto .OWASP ZAP:
# Install
brew install --cask zap
# Baseline scan against local backend
docker run -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py \
-t http://host.docker.internal:8080
# Full scan
docker run -t ghcr.io/zaproxy/zaproxy:stable zap-full-scan.py \
-t http://host.docker.internal:8080NMap:
# Port scan of local services
nmap -sV localhost
# Scan the designated test target
nmap -sV scanme.nmap.orgIf a production deployment needs to be reverted:
- Go to Actions → find the last known good workflow run
- Click "Re-run all jobs" → this redeploys the previous version
- Or use the CLI:
# Find the last good commit git log --oneline -10 # Trigger deployment of that specific commit gh workflow run "CI/CD Pipeline" --ref <good-commit-sha> -f deploy_env=production