DevSecOps · Self-directed · Jenkins shared-pipeline demo
filmapp — a DevSecOps pipeline, not just a React app
A React + TypeScript streaming-app demo whose interesting part is the
Jenkins pipeline around it. Static analysis with SonarQube, dependency
CVE scanning with OWASP Dependency-Check, filesystem + image scanning
with Trivy, and a final rollout to Kubernetes — every gate enforced
before an image reaches the registry.
React
TypeScript
Jenkins
SonarQube
OWASP DC
Trivy
Docker
Kubernetes
DockerHub
01The point of this project
The app itself is a thin React + TypeScript frontend that pulls movie
metadata from the TMDB API. It's a vehicle — the real deliverable is
a Jenkinsfile that demonstrates a full "shift-left" security posture
on every commit.
Most CI pipelines I see stop at "unit tests pass, push image." This
one asks four more questions before it promotes anything:
- Is the code clean? → SonarQube quality gate.
- Are the dependencies safe? → OWASP Dependency-Check.
- Is the filesystem clean of known CVEs? → Trivy FS scan.
- Is the image we just built clean? → Trivy image scan.
02Pipeline stages
01 · Clean workspaceFresh agent workspace — no carry-over from previous runs.
02 · CheckoutPull the repo from main.
03 · SonarQubeFull project scan — bugs, code smells, coverage, duplication.
04 · Quality gateWait for SonarQube's server-side verdict before continuing.
05 · npm installDeterministic install from the lockfile.
06 · OWASP DCDependency CVE scan with an XML report published to Jenkins.
07 · Trivy FS scanFilesystem-level CVE scan of the checked-out code.
08 · Docker build & pushBuild with TMDB API key injected from Jenkins credentials; push to DockerHub.
09 · Trivy image scanRe-scan the just-published image — catches anything the base image brought in.
10 · DeployContainer rollout — locally via docker run for the demo; Kubernetes manifests for the real deploy.
03Secrets stay in Jenkins
The original version of this Jenkinsfile had the TMDB API key baked
straight into docker build --build-arg. After GitHub's
secret scanner (rightly) caught it, the key moved into Jenkins
credentials and is injected as an environment variable only at build
time:
environment {
SCANNER_HOME = tool 'sonar-scanner'
// Injected from Jenkins credentials — never hardcoded.
TMDB_V3_API_KEY = credentials('tmdb-v3-api-key')
}
stage('Docker Build & Push') {
steps {
script {
withDockerRegistry(credentialsId: 'docker', toolName: 'docker') {
sh 'docker build --build-arg TMDB_V3_API_KEY=$TMDB_V3_API_KEY -t filmapp .'
sh 'docker tag filmapp ofdengiz/filmapp:latest'
sh 'docker push ofdengiz/filmapp:latest'
}
}
}
}
04Kubernetes rollout
The same image that passed all four gates deploys into Kubernetes via
a plain Deployment + Service pair. Replicas=2 for demo purposes; the
selector/label pair matches the Deployment and Service so one can't
drift from the other.
apiVersion: apps/v1
kind: Deployment
metadata:
name: filmapp
labels:
app: filmapp
spec:
replicas: 2
selector:
matchLabels:
app: filmapp
template:
metadata:
labels:
app: filmapp
spec:
containers:
- name: filmapp
image: ofdengiz/filmapp:latest
ports:
- containerPort: 80