A production-grade demonstration of automated build → test → containerize → push → deploy.
This repository is not a CRUD app. It is a reference implementation of the
machinery that takes code from a git push to a running production service on
a Linux cloud server — with zero manual steps in between.
The sample application (Spring Boot + React + PostgreSQL) exists only to give the pipeline something real to test, containerize, and deploy. The engineering value lives in the CI/CD pipeline, Docker strategy, reverse proxy, deployment scripts, secrets handling, and reliability mechanisms.
push to main
│
├─▶ backend-test ─────┐ (run in parallel)
├─▶ frontend-build ───┤
│ ▼
│ docker-build (backend + frontend, matrix, layer-cached)
│ ▼
│ image-push (GHCR, immutable sha-<commit> + latest)
│ ▼
│ deploy-production (SSH → Ubuntu VM)
│ ├─ pull new images
│ ├─ recreate containers
│ ├─ health-gate (wait for readiness)
│ └─ auto-rollback on failure
▼
production updated, smoke-tested, traceable
- Full GitHub Actions pipeline — 5 jobs (
backend-test,frontend-build,docker-build,image-push,deploy-production), parallel where possible, Maven + npm + Docker-layer caching, PR-safe (tests on PRs, deploy only onmain). - Multi-stage Dockerfiles — backend builds then runs on a slim JRE as a
non-root user; frontend builds with Vite then serves static assets via nginx.
Both declare container
HEALTHCHECKs. - Health-gated, self-rolling-back deploys — a bad build physically cannot
take production down;
deploy.shreverts to the last good image tag. - Production nginx edge proxy — single exposed port, API/SPA routing, security headers, CSP, per-zone rate limiting, JSON access logs, SSL-ready.
- Real secrets hygiene — every credential is an environment variable fed
from GitHub Secrets; nothing sensitive is committed;
.envis git-ignored. - Observability — Actuator liveness/readiness probes, Micrometer → Prometheus metrics, optional Grafana, structured logs, cron-able smoke test.
- Verified — frontend build + ESLint pass; all shell scripts pass
shellcheck; all YAML validated.
| Layer | Choice |
|---|---|
| Backend | Java 21, Spring Boot 3.3, Spring Security (stateless JWT), JPA, Flyway |
| Frontend | React 18, Vite, React Router |
| Database | PostgreSQL 16 (persistent named volume) |
| Containers | Docker, multi-stage builds, Docker Compose |
| CI/CD | GitHub Actions → GitHub Container Registry |
| Edge | nginx 1.27 reverse proxy (TLS-ready, rate-limited) |
| Host | Ubuntu 22.04/24.04 VM (AWS EC2 compatible) |
| Monitoring | Spring Boot Actuator, Micrometer, Prometheus, Grafana |
See docs/architecture.md for the full diagram and
rationale. In short: the browser hits edge nginx (the only exposed port),
which routes / to the React SPA container and /api/** to the Spring Boot
container, which persists to PostgreSQL on a private bridge network. The
backend and database are never published to the host.
From the project root — one command:
make upThis copies .env.example → docker/.env if needed, then builds and starts
all four services (frontend, backend, PostgreSQL, nginx). First build takes a
few minutes (Maven + npm); the backend healthcheck has a 70 s start window
while Flyway runs the initial migration.
Without make:
copy .env.example .env
docker compose -f docker/docker-compose.yml up --buildThen:
| URL | What |
|---|---|
| http://localhost:8080 | Operations console (sign in admin / AdminPass123) |
| http://localhost:8080/actuator/health | {"status":"UP"} |
| http://localhost:8080/api/public/health | lightweight liveness JSON |
make ps shows health status, make logs tails logs, make down stops it,
make clean also wipes the database volume. Rotate the seeded admin
credential immediately in any real environment.
Full local options (including hot-reload dev mode) are in docs/local-development.md.
One-time VM bootstrap, then every git push origin main deploys
automatically. The complete walkthrough, including AWS EC2 specifics and
the exact GitHub Secrets to set, is in
docs/deployment.md.
# on a fresh Ubuntu VM
curl -fsSL https://raw.githubusercontent.com/<owner>/<repo>/main/scripts/server-bootstrap.sh | sudo bashSSH_HOST · SSH_USER · SSH_PRIVATE_KEY · DOCKER_USERNAME ·
DOCKER_PASSWORD · JWT_SECRET · DATABASE_PASSWORD
No secret is ever hardcoded or committed.
| Job | Trigger | Does |
|---|---|---|
backend-test |
every push & PR | ./mvnw clean verify (JUnit, H2); fails pipeline on any test failure; uploads surefire report |
frontend-build |
every push & PR | npm ci → ESLint (--max-warnings 0) → vite build; uploads dist/ |
docker-build |
after both pass | builds backend + frontend images (matrix, parallel) with GHA layer cache; no push |
image-push |
main only |
pushes immutable sha-<commit> + latest tags to GHCR |
deploy-production |
main only |
SSH to VM → render .env from secrets → registry login → deploy.sh (health-gated, rollback) → smoke test → step summary |
Defined in .github/workflows/ci-cd.yml.
- Backend — stage 1 resolves Maven deps (cached layer) and builds the jar;
stage 2 is a slim
eclipse-temurin:21-jrerunning as an unprivileged user with a tuned JVM (MaxRAMPercentage, G1GC) and a curl-basedHEALTHCHECK. - Frontend — stage 1 runs
vite build; stage 2 serves the static bundle fromnginx:alpinewith immutable asset caching and SPA history fallback. - Compose —
docker/builds locally for development;deployment/pulls versioned registry images for production withrestart: always, per-service healthchecks, resource limits, and a persistentpgdatavolume.
Restart policies, persistent DB volume, container + compose healthchecks, health-gated deploys with automatic rollback, structured backend logs, JSON nginx logs, log rotation, and a cron-friendly smoke test. Metrics via Actuator + Micrometer with an optional Prometheus/Grafana add-on. Details in monitoring/README.md.
.github/workflows/ CI/CD pipeline
backend/ Spring Boot service (Java 21) + multi-stage Dockerfile + tests
frontend/ React + Vite SPA + multi-stage Dockerfile
docker/ local-dev compose (builds images)
deployment/ production compose (pulls registry images)
nginx/ edge reverse proxy: routing, TLS, rate limit, security headers
scripts/ server-bootstrap · vm-setup · deploy · health-check
monitoring/ Prometheus + Grafana add-on
docs/ architecture · deployment · local-development · troubleshooting
- Architecture — diagrams, components, design rationale
- Deployment guide — VM + AWS EC2 + secrets, step by step
- Local development — Docker and hot-reload modes
- Troubleshooting — pipeline & runtime failure modes
- Monitoring — metrics, probes, reliability mechanisms
The production deployment stage requires a configured Linux VM (e.g., AWS EC2) and GitHub repository secrets for SSH access. CI, Docker image builds, and registry publishing are fully functional.
The operations console renders a control-room dashboard (live service health,
user count, process uptime, the last deployment pipeline as a 5-stage strip,
a deployment log, and the authenticated session). Run the quick start above
to view it locally; capture screenshots into docs/screenshots/ and they will
appear here.
- Fixed container healthcheck failures (
localhost→127.0.0.1) - Solved JWT secret initialization failures in Spring Security
- Debugged nginx reverse proxy startup and container dependency ordering
- Implemented health-gated deployments with rollback protection
- Verified container networking across frontend, backend, and PostgreSQL
MIT — see LICENSE.