In my previous blog post I explained how to get started with GitLab’s CI/CD capabilities. I’ve explained a very basic workflow that builds an app, does a basic test, builds a container and deploys the container to Kubernetes. In this blog post we’re going to enhance our flow, and add security capabilities to our flow – because in today’s software landscape, ensuring application security is critical. GitLab comes with different static and dynamic security scanners, which can easily added to your CI/CD flow. Scanners that are provided are Static Application Security Scanning (SAST), Dynamic Application Scanning (DAST), secret detection, dependency scanning, container scanning, IaC scanning and API Security.
Most security scanners shown in this post require GitLab Ultimate. Secret Detection and basic SAST are available in Free/Premium tiers.
Some options to implement Security Scanning
GitLab provides several ways to implement security scanning, depending on your organizational structure and requirements:
- Option 1: Manual Configuration (recommended for learning). Edit your .gitlab-ci.yml directly to include scanners using templates and components. You can do this manually or use the Security Configuration page in the UI.
- Option 2: Scan Execution Policies (Recommended for enterprises). Define security policies centrally and keep scanning configuration out of project .gitlab-ci.yml files. This makes scanning mandatory and invisible to developers. Best for organizations with dedicated security teams, compliance requirements.
- Option 3: Pipeline Execution Policies Use an external .gitlab-ci.yml file stored outside your project, enforced via pipeline execution policy. Best for standardized scanning across many projects
- Option 4: Enable AutoDevOps which includes built-in security scanners automatically.
GitLab also helps you with taking care of compliance – you can make security scans, and their results, part of an approval flow that is part of a merge request. Depending on security scanning results, the merge request can have additional approval steps – or merging is not allowed at all because of specific vulnerabilties
In this post, we’ll use option 1 (manual configuration) to understand how each scanner works.
Templates versus Components
Both Templates and Components allow you to add standard building blocks/configurations to your CI/CD pipeline (as defined in the .gitlab-ci.yml). These configurations are to handle tasks around building applications, runnings tests, security scanning, deployment, etc. Templates already exist for many years, what you’ll see is that GitLab is slowly moving to components – available since the beginning of 2024. Components are in someway comparable with templates, however Components come with some benefits over templates:
- Components are standalone, resuable pieces;
- Components are versioned (so you know what version is working for your workflow, and you can upgrade at your own will);
- Typed inputs with defaults (templates use variables);
- There’s a CI/CD component catalog that you can browse and look for the component you need.
- CI/CD components are maintained by GitLab, the community and 3rd part vendors. You can create your own components if you wish (and this is surprisingly simple!).
Currently you’ll see that a mix of components and templates are being used in a CI/CD flow, this is due to the fact that some of the component “replacements” for templates are currently in experimental or Beta status. Examples:
- Component: `gitlab.com/components/container-scanning/container-scanning@5.1.0`
- Template: `template: Jobs/Dependency-Scanning.v2.gitlab-ci.yml`
Setup a basic pipeline & explore security scanners
Of course we need a basic setup for a .gitlab-ci.yml to get started, this flow can include steps like install, build, test, deploy – just as we introduced in my previous blog post. So let’s say we CI/CD flow that includes the following steps:

There’s a build step that does a clean install of the app, a unit step test, a build docker image step and then at the end a deployment to a Kubernetes staging environment. An optional step to deploy to production is also available, however this one is only triggered when a tag is created. Of course, ‘many ways lead to Rome”, but in this example this is our approach.
So, what are the security options we can add to this pipeline? To repeat what was said earlier, GitLab Ultimate provides scanners for (advanced) Static Application Security Scanning (SAST), Dynamic Application Scanning (DAST), secret detection, dependency scanning, container scanning, IaC scanning and API Security:
-
- The GitLab Advanced SAST scanner (included in GitLab Ultimate) is designed to discover vulnerabilities by performing cross-function and cross-file taint analysis. Note there’s also a basic Semgrep-based SAST scanner available, available in the free and premium version of GitLab (comparison between the two is available here).
- The Secret detection scanner minimizes the risk of exposing secrets (private keys and tokens) that your project uses to authenticate to external sources like databases and storage.
- The Dependency scanner (Gemnasium analyzer – deprecated and Dependency scanning using SBOM – preferred), analyses your application’s dependencies for known vulnerabilities. This is part of a Software Composition Analysis.
- API security testing tests your application’s API for known attacks and vulnerabilities. When we talk about API security, you can think about SAST, Dependency Testing, Container Scanning, API Discovery, API Security Testing analyzer and API fuzzing.
- Web API fuzz testing passes unexpected values to API operation parameters to cause unexpected behaviour and/errors in the backend.
- Container scanning scans your container images for security vulnerabilities and is also part of a Software Composition Analysis. Also read more here about the difference between dependency scanning and container scanning.
- GitLab’s DAST scanner runs automated penetration tests to find vulnerabilities in your web applications and APIs.
- API Discovery analyzes your application and produces an OpenAPI document describing the web API it exposes.
Integrate the scanners into your pipeline
So the next step is to integrate one or more scanners into your pipeline. As stated at the beginning of article, there are different options to include and use these security scanners. In this example we’re going to manually include and configure the scanners. You have the option to specify the stages that run the scanners, and in that way set the order of the CI/CD workflow.
The next diagram depicts the staging order and jobs running in my CI/CD flow (click to enlarge):

The stages in this CI/CD flow are:
- Build – Do a clean install of the app, this is used for the test stage (unit testing).
- Static security – This runs the Dependency Scan, the Advanced SAST scanner, IAC scanning and does secret detection.
- Test – Runs the unit testing.
- Package – Builds a container and pushes it to the GitLab Registry.
- Container scan – Runs a container scan.
- Staging – Deploys the app to a staging environment.
- DAST – This is running the Dynamic Application Security Testing.
This example demonstrates three security stages: static scanners run during the static-security stage, container scanning executes in the container-scan stage after the image is built, and DAST scanning performs at the end of the CI/CD flow.
So, what’s needed to get this working? Let’s look at some snippets of my .gitlab-ci.yml:
image: node:21-alpine
variables:
FQDN_PRODUCTION: "$KUBE_NAMESPACE_PRODUCTION.$KUBE_INGRESS_BASE_DOMAIN"
FQDN_STAGING: "$KUBE_NAMESPACE_STAGING.$KUBE_INGRESS_BASE_DOMAIN"
IMAGE_TAG: "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA"
DOCKER_TLS_CERTDIR: ''
stages:
- build
- static-security # Fast scans (1-5 min)
- test # Unit test(s)
- package # Docker build
- container-scan # Scan built image
- staging # Deploy to staging
- dast # Dynamic scanning
- production # Deploy to production
include:
- component: gitlab.com/components/container-scanning/container-scanning@5.1.0
inputs:
stage: container-scan
cs_image: $IMAGE_TAG
- component: $CI_SERVER_FQDN/components/sast/sast@3.0.1
inputs:
stage: static-security
run_advanced_sast: true
excluded_paths: "node_modules/**,k8s/**,test/**"
- component: gitlab.com/components/secret-detection/secret-detection@2.1.0
inputs:
stage: static-security
- template: Jobs/Dependency-Scanning.v2.gitlab-ci.yml
- template: Security/SAST-IaC.latest.gitlab-ci.yml
- template: DAST.gitlab-ci.yml
This section of the .gitlab-ci.yml defines the Docker image, pipeline stages, and included components/templates. It also declares variables that set application-specific parameters. Since I’m using custom stages instead of the default test stage, some scanner customization is required. Components are customized during inclusion, while template-based scanners need additional configuration:
dependency-scanning:
stage: static-security
variables:
DS_EXCLUDED_PATHS: "node_modules/**,k8s/**,test/**"
iac-sast:
stage: static-security
dast:
stage: dast
needs:
- deploy-staging
variables:
DAST_WEBSITE: https://$FQDN_STAGING
DAST_FULL_SCAN_ENABLED: "false"
DAST_SPIDER_MINS: "3"
Most settings speak for themselves. The full .gitlab-ci.yml is available to download gitlab-ci.yml (rename the file to yml and remove the txt extension if you want to use it).
Results & Next Steps
Now that we’ve integrated security scanners into our pipeline, you’re probably wondering: Where do these scan results go, and what should I do with them?
After your pipeline runs, GitLab collects all security findings and displays them in several locations:
- Pipeline Security Tab – Overview of vulnerabilities found in this pipeline run
- Merge Request Widget – Shows new vulnerabilities introduced by your changes
- Vulnerability Report – Consolidated view of all findings with severity ratings
- Security Dashboard – Project and group-level security posture overview
By default, security scans run with allow_failure: true, meaning your pipeline continues even when vulnerabilities are detected. The findings are available for review, but won’t block your deployments unless you configure stricter enforcement through security policies.
What’s Coming in a future blog post
In a next blog post, we’ll dive deep into making sense of these security findings:
- Analyzing scan results – Understanding severity levels, filtering, and prioritization
- Managing vulnerabilities – Creating issues, dismissing false positives, tracking remediation
- Security policies – Enforcing mandatory scanning and approval workflows
- Integration strategies – Connecting security findings with your development process
- GitLab Duo integration – Using AI to explain vulnerabilities and suggest fixes
Security scanning is just the first step—effective vulnerability management is what keeps your applications secure!
Stay tuned!


