A practical DevSecOps pipeline example weaves security checks into the stages you already have — commit, build, test, and deploy — so scans run automatically and block risky changes rather than living in a separate audit that happens after release. The goal is not a security stage bolted onto the end; it is a handful of gates placed where they catch problems cheapest, each with a clear pass/fail condition.
Below is a working example you can adapt. It assumes a Git-based workflow with a CI system (GitHub Actions, GitLab CI, Jenkins — the shape is the same) and a containerized deploy target.
The stages at a glance
A sensible pipeline has five checkpoints, ordered from fastest to slowest:
- Pre-commit / commit — secret scanning, linting.
- Build — dependency resolution and software composition analysis (SCA).
- Static test — static application security testing (SAST).
- Artifact — container image scanning, SBOM generation.
- Deploy / post-deploy — dynamic application security testing (DAST) against a running staging instance.
Fast, cheap checks run first so developers get feedback in seconds. Expensive checks like DAST run later, against deployed builds, where they add the most signal.
Stage 1: catch secrets before they land
The cheapest vulnerability to fix is a secret that never gets committed. A pre-commit hook plus a CI job that scans the diff for credentials stops API keys and tokens at the door.
# GitHub Actions: secret scan on every push
scan-secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Scan for secrets
run: gitleaks detect --source . --redact --exit-code 1
--exit-code 1 is the important part: a detected secret fails the job. A warning nobody reads is not a gate.
Stage 2: know your dependencies (SCA)
Most modern code is mostly other people's code, so the dependency graph is usually the largest source of known vulnerabilities. Run software composition analysis right after dependency resolution, on the full transitive tree — not just what your manifest names directly. This is where issues like Log4Shell or a transitive log4j-core pull surface.
sca:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and resolve dependencies
run: mvn -B dependency:resolve
- name: Software composition analysis
run: |
sca-scan --fail-on high --sbom sbom.json
Set a severity threshold you can actually hold — failing on High and Critical is a realistic starting point, with a documented exception process for the false positives every scanner produces. An SCA tool that understands transitive dependencies is what keeps this stage honest.
Stage 3: static analysis (SAST)
SAST reads your source (or bytecode) for injection flaws, unsafe deserialization, hardcoded crypto, and the like. It runs without executing the app, so it fits naturally in the test phase. Tune it hard — an unfiltered SAST tool drowns teams in low-value findings and gets ignored, which is worse than no scan at all.
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Static analysis
run: semgrep ci --config auto --error
The practical move is to fail the build only on a curated ruleset you trust, and report the rest as informational until you have triaged them.
Stage 4: build the artifact, scan the image, emit an SBOM
Once you produce a container image, scan the image itself for OS-package CVEs and generate a software bill of materials. The SBOM is not paperwork — it is the record that lets you answer "are we affected?" the next time a Log4Shell-scale disclosure lands, without re-scanning the world.
image:
runs-on: ubuntu-latest
steps:
- name: Build image
run: docker build -t app:${{ github.sha }} .
- name: Scan image and emit SBOM
run: |
trivy image --severity HIGH,CRITICAL --exit-code 1 app:${{ github.sha }}
syft app:${{ github.sha }} -o cyclonedx-json > sbom.json
Stage 5: dynamic testing against a running build (DAST)
Static tools cannot see runtime behavior — auth flows, misconfigured headers, reflected input. DAST exercises a deployed instance the way an attacker would. Because it needs a running target and takes longer, it belongs after deploy-to-staging, not in the inner loop.
dast:
needs: deploy-staging
runs-on: ubuntu-latest
steps:
- name: Dynamic scan of staging
run: dast-scan --target https://staging.example.com --fail-on high
Point DAST at staging, gate promotion to production on the result, and keep the crawl scoped so the scan finishes inside your deploy window.
Making the gates humane
A pipeline that blocks on every finding gets disabled within a week. Three rules keep it usable:
- Fail on severity, not on count. High and Critical block; Medium and below report.
- Give teams an exception path. A documented, time-boxed waiver beats a scan someone quietly deletes.
- Keep feedback fast. Put the sub-minute checks (secrets, lint) early; push the slow ones (DAST) late.
The point of a DevSecOps pipeline is not maximum scanning. It is putting the right check at the right stage with a clear decision attached, so security travels with every change instead of waiting for a quarterly review. If you are formalizing the practice, the Academy walks through tuning each stage.
FAQ
What is a DevSecOps pipeline?
It is a CI/CD pipeline with automated security checks — secret scanning, SCA, SAST, image scanning, and DAST — embedded in its existing stages, each with a pass/fail gate, so security runs on every change rather than in a separate late-stage review.
Where should SAST and DAST run in the pipeline?
SAST runs in the static test phase because it analyzes source without executing it. DAST runs after deploy-to-staging because it needs a running instance to test. Running both catches different classes of flaws.
How do I stop the pipeline from failing on every finding?
Gate on severity rather than total count — block on High and Critical, report the rest — and provide a documented, time-boxed exception process for false positives. That keeps the gates trusted instead of bypassed.
Do I need every stage from day one?
No. Start with secret scanning and SCA, since they are cheap and high-value, then add SAST, image scanning, and DAST as the team gets comfortable. An incremental rollout sticks better than flipping every gate on at once.