pipeline { agent any environment { REGISTRY = '192.168.1.11:5000' IMAGE_NAME = 'cammonitor' // Tag with the short git commit SHA; also push 'latest' on the main branch. IMAGE_TAG = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim() // TRIVY_SEVERITY controls which finding levels fail the build. // Set to 'CRITICAL' to only fail on critical CVEs, or remove the // --exit-code flag entirely to make the scan advisory-only. TRIVY_SEVERITY = 'HIGH,CRITICAL' } options { // Keep only the last 10 builds to save disk space on the Atom server. buildDiscarder(logRotator(numToKeepStr: '10')) timestamps() } stages { stage('Checkout') { steps { checkout scm } } stage('Build') { steps { // Builds the image defined in Dockerfile using the same flags // that docker compose would use, but targets the registry tag directly. sh """ docker build \ --tag ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} \ --tag ${REGISTRY}/${IMAGE_NAME}:latest \ . """ } } stage('Test') { // Spin up a throw-away container and hit /health to confirm the binary // starts correctly before we push anything. steps { sh """ docker run --rm -d \ --name cammonitor-ci-${BUILD_NUMBER} \ -p 19080:8080 \ -e FOOTAGE_ROOT=/tmp \ -e DB_PATH=/tmp/cammonitor-ci.db \ ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} # Give the process a moment to bind the port, then check health. sleep 3 curl --fail --silent --show-error http://127.0.0.1:19080/health """ } post { always { sh 'docker stop cammonitor-ci-${BUILD_NUMBER} || true' } } } stage('Scan') { // Trivy scans the locally built image for OS and library CVEs before // anything is pushed to the registry. // // Trivy must be installed on the Jenkins agent: // https://aquasecurity.github.io/trivy/latest/getting-started/installation/ // e.g. apt install trivy or brew install trivy // // The vulnerability DB is cached at ~/.cache/trivy between builds. // On air-gapped networks set TRIVY_NO_PROGRESS=true and pre-download // the DB: https://aquasecurity.github.io/trivy/latest/docs/advanced/air-gap/ steps { // --exit-code 1 makes the stage (and build) fail when findings at // or above TRIVY_SEVERITY are detected. Remove it to make the scan // informational only. sh """ trivy image \ --severity ${TRIVY_SEVERITY} \ --exit-code 1 \ --no-progress \ --ignore-unfixed \ --format table \ ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} """ // --ignore-unfixed suppresses findings where Debian maintainers have // marked the CVE as will_not_fix or fix_deferred with no fixed version // available in the repo (libexpat1, ncurses, perl-base, zlib1g). // These cannot be upgraded away — blocking on them produces no action. } post { always { // Also emit a machine-readable SARIF report so results can be // ingested by Jenkins Warnings NG or uploaded to GitHub Advanced // Security if you ever connect this repo there. sh """ trivy image \ --severity ${TRIVY_SEVERITY} \ --exit-code 0 \ --no-progress \ --ignore-unfixed \ --format sarif \ --output trivy-report.sarif \ ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} """ archiveArtifacts artifacts: 'trivy-report.sarif', allowEmptyArchive: true } } } stage('Push') { // NOTE: 192.168.1.11:5000 is an HTTP registry (no TLS). // The Docker daemon on the Jenkins agent must have it listed under // "insecure-registries" in /etc/docker/daemon.json: // // { "insecure-registries": ["192.168.1.11:5000"] } // // If the registry requires credentials, replace the plain push below // with the block commented out underneath it, and add a 'registry-creds' // username/password credential in Jenkins > Manage Credentials. steps { sh "docker push ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" // Only move the 'latest' pointer when building the main branch. script { if (env.BRANCH_NAME == 'main') { sh "docker push ${REGISTRY}/${IMAGE_NAME}:latest" } } /* --- With registry credentials (uncomment if needed) --- withCredentials([usernamePassword( credentialsId: 'registry-creds', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS' )]) { sh """ docker login ${REGISTRY} -u \$REG_USER -p \$REG_PASS docker push ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} docker push ${REGISTRY}/${IMAGE_NAME}:latest docker logout ${REGISTRY} """ } -------------------------------------------------------- */ } } } post { always { // Remove local copies so the build agent does not fill up. sh """ docker rmi ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} || true docker rmi ${REGISTRY}/${IMAGE_NAME}:latest || true """ } success { echo "Image pushed: ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" } failure { echo "Build failed — image was NOT pushed to the registry." } } }