Approach: Use a Declarative Jenkinsfile that checks out code, runs backend/frontend unit tests in parallel, builds a Docker image with layer caching (pulling a previous image as cache), runs integration tests that launch the built image, and only pushes to a private registry on annotated/tagged commits. Credentials are injected via Jenkins credentials store using withCredentials/docker.withRegistry to avoid exposing secrets. Vulnerability scanning is shown as comments and placed after build (and optionally as part of CI build step).pipeline {
agent any
environment {
IMAGE_NAME = "myorg/myapp"
IMAGE_TAG = "${env.GIT_COMMIT.substring(0,7)}"
REGISTRY = "registry.private.example.com"
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Unit Tests') {
parallel {
stage('Backend Tests') {
steps {
dir('backend') {
sh 'pytest -q' // replace with your test command
}
}
}
stage('Frontend Tests') {
steps {
dir('frontend') {
sh 'npm ci && npm test -- --silent' // replace with your test command
}
}
}
}
}
stage('Build Docker Image') {
steps {
script {
// Attempt to pull previous image to enable layer caching. Ignore failures.
sh "docker pull ${REGISTRY}/${IMAGE_NAME}:latest || true"
// Build using cache-from. Use --cache-from with the pulled image.
sh """
docker build \
--cache-from ${REGISTRY}/${IMAGE_NAME}:latest \
-t ${IMAGE_NAME}:${IMAGE_TAG} .
"""
}
}
}
// Optional vulnerability scan: run scanner (e.g., Trivy) against the built image
stage('Vulnerability Scan') {
steps {
// Inject scanner credentials if needed via withCredentials
// Example using trivy (no creds required for local scan). For authenticated scanners use withCredentials.
sh "trivy image --exit-code 1 ${IMAGE_NAME}:${IMAGE_TAG} || true"
// NOTE: Configure policy: fail the pipeline on high/critical findings if required.
}
}
stage('Integration Tests') {
steps {
script {
// Start services needed for integration tests; test against local image.
sh """
docker run -d --name sut -p 8080:8080 ${IMAGE_NAME}:${IMAGE_TAG}
# wait for readiness (implement healthcheck/wait)
sleep 5
# run integration tests (could be in tests/integration)
./scripts/run-integration-tests.sh http://localhost:8080
docker rm -f sut
"""
}
}
}
stage('Push Image (tags only)') {
when {
buildingTag() // Run this stage only for annotated/tagged commits in multibranch pipeline
}
steps {
script {
// Use credentials stored in Jenkins credentials (id: 'registry-creds')
withCredentials([usernamePassword(credentialsId: 'registry-creds', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS')]) {
sh "docker login ${REGISTRY} -u ${REG_USER} -p ${REG_PASS}"
sh "docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}"
sh "docker push ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}"
// Optionally push latest
sh "docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${REGISTRY}/${IMAGE_NAME}:latest"
sh "docker push ${REGISTRY}/${IMAGE_NAME}:latest"
}
}
}
}
}
post {
always {
sh 'docker system prune -f || true' // cleanup
}
failure {
mail to: 'dev-team@example.com', subject: "Build failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}", body: "See ${env.BUILD_URL}"
}
}
}
Key points:- Credentials: store registry creds in Jenkins (credentialsId 'registry-creds') and inject via withCredentials; use docker.withRegistry if using Docker Pipeline plugin.- Layer caching: pull previous image and pass --cache-from so Docker can reuse layers.- Tag-only push: when { buildingTag() } ensures pushes happen only for tag builds (annotated tags in multibranch pipeline).- Vulnerability scanning: run scanner (Trivy/Anchore/Snyk) after build and before push; fail pipeline on unacceptable severity.- Integration tests: run against the built image locally (docker run) or via test orchestration (docker-compose / Kubernetes). Adjust health checks and timeouts accordingly.Edge cases: ensure docker daemon on agent, handle private base images (provide creds), long-running integration tests timeouts, and flaky network when pulling cache image.