Skip to content
CloudOps
Newsletter
All guides
AI for GitLab CI/CD By James Joyner IV · · 9 min read

GitLab Container Scanning, SAST and DAST: Shift Security Left Without Slowing the Pipeline

How to wire container scanning, SAST and DAST into GitLab CI so vulnerabilities surface in the merge request instead of in production — without tanking pipeline speed.

  • #gitlab
  • #cicd
  • #security
  • #sast
  • #dast
  • #container-scanning
  • #devsecops

The first time a security scanner blocked one of my merge requests, I was annoyed. The second time it caught a log4j-shaped problem before it shipped, I stopped being annoyed. GitLab ships a whole family of scanners — SAST, DAST, container scanning, and more — and the difference between a team that uses them and a team that ignores them usually comes down to one thing: whether the findings show up in the merge request, where someone is already paying attention, or in a report nobody opens.

This is how to wire the three scanners that matter most into your pipeline so they’re useful instead of noise.

The three scanners and what each one actually finds

It’s worth being precise, because the names blur together:

  • SAST (Static Application Security Testing) reads your source code without running it. It catches things like SQL injection patterns, hardcoded secrets, and unsafe deserialization. Fast, runs on every commit.
  • Container Scanning inspects the OS packages and libraries inside your built image against vulnerability databases. This is where you catch the vulnerable openssl or glibc you inherited from a base image you didn’t pick.
  • DAST (Dynamic Application Security Testing) runs against a deployed, running app and probes it like an attacker would. Slower, needs an environment, but catches runtime issues SAST can’t see.

The trap teams fall into is treating these as one undifferentiated “security stage.” They have different costs and different feedback loops. SAST belongs on every push; DAST belongs on a review-app or a nightly run.

SAST: the cheapest win

GitLab’s SAST is template-driven. You include the template and it auto-detects your languages:

include:
  - template: Jobs/SAST.gitlab-ci.yml

stages:
  - test

sast:
  stage: test
  variables:
    SAST_EXCLUDED_PATHS: "spec, test, tests, tmp"

The analyzers run as separate jobs per language and write findings to a gl-sast-report.json artifact that GitLab parses into the Security tab of the merge request. Reviewers see new vulnerabilities introduced by this MR — not the entire historical backlog — which is the single most important property for adoption. People will fix the thing they broke; they will not fix a 400-item report.

Container scanning: scan what you actually ship

Container scanning needs your image built first. The template expects an image reference, so the pattern is build-then-scan:

include:
  - template: Jobs/Container-Scanning.gitlab-ci.yml

build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

container_scanning:
  stage: test
  variables:
    CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

A practical tip from running this in anger: most container findings come from your base image, not your code. Switching from a full debian base to a -slim or distroless variant routinely drops your CVE count by an order of magnitude, because you’re shipping fewer packages to be vulnerable. Scanning is what makes that trade-off visible.

DAST: the one that needs an environment

DAST is the slow, expensive sibling. Don’t run it on every push — run it against a deployed environment, ideally a review app. The minimal shape:

include:
  - template: DAST.gitlab-ci.yml

dast:
  stage: dast
  variables:
    DAST_WEBSITE: https://$CI_ENVIRONMENT_SLUG.review.example.com
    DAST_FULL_SCAN_ENABLED: "false"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

The DAST_FULL_SCAN_ENABLED: "false" keeps it to a passive baseline scan during MRs — a full active scan can take an hour and hammer your environment. Save the full scan for a scheduled nightly pipeline.

Stop the pipeline from getting slow

The number-one reason teams rip security scanners back out is pipeline slowdown. Three habits keep it fast:

Run scanners in parallel, not in series. SAST, dependency scanning, and unit tests have no dependency on each other. Put them in the same stage so GitLab runs them concurrently across your runners.

Gate destructive scans behind rules. DAST and full container scans belong on merge_request_event or schedule, never on every feature-branch push.

Use allow_failure deliberately. Early on, set allow_failure: true so findings are visible without blocking. Once your backlog is clean, flip it to blocking. Going straight to blocking on a legacy codebase guarantees the team disables it by Friday.

Turning findings into fixes

A scanner that finds 50 issues and explains none of them creates a new bottleneck: triage. This is where I lean on AI to do the first pass. Paste the gl-sast-report.json finding — the rule ID, the file, the code snippet — and ask for an explanation of the actual exploit path and a suggested patch. The model is genuinely good at “is this a real SQL injection or a false positive because the input is already parameterized?” — the question that eats the most reviewer time.

The same instinct powers our AI Code Review assistant: surface the risky change, explain why it’s risky in plain language, and propose the fix, so a human spends their attention on the judgment call instead of decoding scanner output. For the broader pipeline-security picture, the rest of our GitLab CI/CD guides cover OIDC secrets and dependency scanning that pair naturally with these scanners.

A sane default config

Put it together and a reasonable starting point looks like this: SAST and container scanning on every MR with allow_failure: true until the backlog is clean, dependency scanning alongside them, and DAST gated to review apps and nightly schedules. Findings land in the MR Security tab. Reviewers see only what the change introduced.

That’s the whole game. Security tools don’t make you secure — they make insecurity visible at the moment you can cheaply fix it. Get the findings into the merge request, keep the fast scanners on every push and the slow ones on a schedule, and let a human (with an AI first-pass) decide what’s real.

Scanner findings and AI triage suggestions are assistive, not authoritative. Always confirm a vulnerability and its fix against your own threat model before shipping.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.