GitLab
Requirements
- Anchore Enterprise is deployed in your environment, with the API accessible from your GitLab CI environment.
- Credentials for your GitLab Container Registry are added to Anchore Enterprise, under the Anchore account that you intend to use with GitLab CI. See Container Registries. For information on what registry/credentials must be added to allow Anchore Enterprise to access your GitLab Container Registry, see https://docs.gitlab.com/ee/user/packages/container_registry/.
1. Configure Variables
Ensure that the following variables are set in your GitLab repository (settings -> CI/CD -> Variables -> Expand -> Add variable) or GitLab Group:
ANCHORECTL_USERNAME
ANCHORECTL_PASSWORD (masked)
ANCHORECTL_URL
Define these once at the group level (Group → Settings → CI/CD → Variables) or instance-wide in the Admin Area. Every underlying project automatically inherits them — meaning new repositories won’t require variable setup, and credential rotation takes a single update. Only set variables at the project level if a specific repository must connect to a different Anchore Enterprise deployment or account.

2. Create Config File
Create a new file in your repository. Name the file .gitlab-ci.yml.

3. Configure Scanning Scope
Anchore Enterprise exposes two scopes for scanning and policy evaluation. Pick the one that matches how your team organizes software.
| Scope | What is scanned | Typical use |
|---|---|---|
| App-Scoped | Every asset attached to an app version — container images, analyzed filesystems, or externally supplied SBOMs | Aggregate and deduplicate vulnerability and policy results across an app version. |
| Image-Scoped | A single container image, identified by digest | Ad-hoc image checks, image-stage CI gates, and any workflow that has not yet adopted the apps, versions, and assets model |
App-Scoped Pipelines
These pipelines manage apps, versions, and assets directly from GitLab CI. The pipeline creates or reuses an app and an app version, attaches the build output to the version as an asset, and then reads vulnerability and policy results back at the version level — aggregated and deduplicated across every asset on that version. See Managing Assets in an App Version to learn more about apps.
anchorectl app commands require Anchore Enterprise and AnchoreCTL v6.0.0 or later. For the full command reference, see Creating and Managing Apps.The examples derive the app name and version from GitLab’s predefined variables (CI_PROJECT_NAME and CI_COMMIT_REF_SLUG) so they run without editing. In practice you will usually want the app version to track a release identifier rather than a branch — for a tag-triggered pipeline, set ANCHORE_APP_VERSION: ${CI_COMMIT_TAG} instead.
Each example is designed to safely run multiple times against the same app version, ensuring repeated CI pipeline runs on new commits won’t break anything:
- The app and version are created only if missing.
anchorectl app getis attempted first, and theaddruns only if the app doesn’t exist. - The asset is replaced, not re-added. An asset name is unique within a version, so adding an asset whose name is already attached fails with
HTTP 409and error codeAS503. Deleting first makes the step repeatable; the|| truecovers the first run, where there is nothing to delete yet.
Replacing results instead of accumulating them ensures accuracy. Because vulnerability lists and policy evaluations aggregate across every asset in an app version, leaving stale builds behind would skew the final gate evaluation.
Each example below is a complete .gitlab-ci.yml. Paste one into the file you created in step 2, then click “Commit changes” to save it.
a) Distributed Analysis
AnchoreCTL pulls the image, generates the SBOM on the build runner, and uploads only the SBOM, which is attached to the app version as a container image asset. Because the runner reads the image, the registry credentials are supplied to anchorectl rather than configured in Anchore Enterprise.
### Anchore App-Scoped Distributed Scan
# you will need three variables defined:
# ANCHORECTL_USERNAME
# ANCHORECTL_PASSWORD
# ANCHORECTL_URL
image: docker:latest
services:
- docker:dind
stages:
- build
- anchore
variables:
### set this to true if you want the app version policy evaluation to determine whether the job succeeds or not
ANCHORECTL_FAIL_BASED_ON_RESULTS: "false"
ANCHORE_IMAGE: ${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG}
### the app, version, and asset name the build output is attached to
ANCHORE_APP: ${CI_PROJECT_NAME}
ANCHORE_APP_VERSION: ${CI_COMMIT_REF_SLUG}
ANCHORE_ASSET: ${CI_PROJECT_NAME}-image
Build:
stage: build
script:
### build and push docker image
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
- docker build -t ${ANCHORE_IMAGE} .
- docker push ${ANCHORE_IMAGE}
Anchore:
stage: anchore
before_script:
### install anchorectl binary
- apk add --no-cache curl
- 'curl "$ANCHORECTL_URL/v2/system/anchorectl?operating_system=linux&architecture=amd64" -H "accept: */*" | tar -zx anchorectl && mv -v anchorectl /usr/bin && chmod +x /usr/bin/anchorectl && /usr/bin/anchorectl version'
- export PATH="${HOME}/.local/bin/:${PATH}"
script:
### provide registry credentials for anchorectl
- export ANCHORECTL_REGISTRY_AUTH_AUTHORITY=$CI_REGISTRY
- export ANCHORECTL_REGISTRY_AUTH_USERNAME="$CI_REGISTRY_USER"
- export ANCHORECTL_REGISTRY_AUTH_PASSWORD="$CI_REGISTRY_PASSWORD"
### create the app if it does not exist yet
- anchorectl app get "${ANCHORE_APP}" -o id > /dev/null 2>&1 || anchorectl app add "${ANCHORE_APP}" --contact-name "Platform Team"
### create the app version if it does not exist yet
- anchorectl app version get "${ANCHORE_APP_VERSION}" --app "${ANCHORE_APP}" -o id > /dev/null 2>&1 || anchorectl app version add "${ANCHORE_APP_VERSION}" --app "${ANCHORE_APP}" --status in_progress
### an asset name is unique within a version, so a rebuild must replace the
### previous asset; a bare re-add returns HTTP 409 (AS503)
- anchorectl app version asset delete "${ANCHORE_ASSET}" --app "${ANCHORE_APP}" --version "${ANCHORE_APP_VERSION}" > /dev/null 2>&1 || true
### scan the image on the runner and attach the resulting SBOM to the app version as an asset
- >
anchorectl app version asset add container-image ${ANCHORE_IMAGE}
--app "${ANCHORE_APP}"
--version "${ANCHORE_APP_VERSION}"
--asset "${ANCHORE_ASSET}"
--dockerfile ./Dockerfile
--annotations "ci=gitlab,project=${CI_PROJECT_PATH},commit=${CI_COMMIT_SHORT_SHA},pipeline=${CI_PIPELINE_ID}"
--wait
### then get the results, aggregated across every asset on the app version:
- anchorectl app version vuln list "${ANCHORE_APP_VERSION}" --app "${ANCHORE_APP}"
### list the specific gates, triggers, and remediation first, so the detail is in the log even if the gate below fails the job
- anchorectl app version policy findings list "${ANCHORE_APP_VERSION}" --app "${ANCHORE_APP}"
- anchorectl app version policy status get "${ANCHORE_APP_VERSION}" --app "${ANCHORE_APP}"
b) Centralized Analysis
Anchore Enterprise pulls and analyzes the image server-side. Swapping container-image for container-image-remote is the only change to the Anchore job; the registry credential exports are dropped, because the deployment — not the runner — reaches the registry. This means the GitLab Container Registry credentials must be configured in Anchore Enterprise, as described in Requirements above. Centralized analysis is also required if you want malware scanning results from Anchore Enterprise’s ClamAV integration.
### Anchore App-Scoped Centralized Scan
# you will need three variables defined:
# ANCHORECTL_USERNAME
# ANCHORECTL_PASSWORD
# ANCHORECTL_URL
image: docker:latest
services:
- docker:dind
stages:
- build
- anchore
variables:
ANCHORECTL_FAIL_BASED_ON_RESULTS: "false"
ANCHORE_IMAGE: ${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG}
ANCHORE_APP: ${CI_PROJECT_NAME}
ANCHORE_APP_VERSION: ${CI_COMMIT_REF_SLUG}
ANCHORE_ASSET: ${CI_PROJECT_NAME}-image
Build:
stage: build
script:
### build and push docker image
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
- docker build -t ${ANCHORE_IMAGE} .
- docker push ${ANCHORE_IMAGE}
Anchore:
stage: anchore
before_script:
### install anchorectl binary
- apk add --no-cache curl
- 'curl "$ANCHORECTL_URL/v2/system/anchorectl?operating_system=linux&architecture=amd64" -H "accept: */*" | tar -zx anchorectl && mv -v anchorectl /usr/bin && chmod +x /usr/bin/anchorectl && /usr/bin/anchorectl version'
- export PATH="${HOME}/.local/bin/:${PATH}"
script:
### note that private registries will require registry credentials to be configured in your Anchore deployment
### create the app if it does not exist yet
- anchorectl app get "${ANCHORE_APP}" -o id > /dev/null 2>&1 || anchorectl app add "${ANCHORE_APP}" --contact-name "Platform Team"
### create the app version if it does not exist yet
- anchorectl app version get "${ANCHORE_APP_VERSION}" --app "${ANCHORE_APP}" -o id > /dev/null 2>&1 || anchorectl app version add "${ANCHORE_APP_VERSION}" --app "${ANCHORE_APP}" --status in_progress
### an asset name is unique within a version, so a rebuild must replace the
### previous asset; a bare re-add returns HTTP 409 (AS503)
- anchorectl app version asset delete "${ANCHORE_ASSET}" --app "${ANCHORE_APP}" --version "${ANCHORE_APP_VERSION}" > /dev/null 2>&1 || true
### have Anchore Enterprise pull and analyze the image, then attach it to the app version as an asset
- >
anchorectl app version asset add container-image-remote ${ANCHORE_IMAGE}
--app "${ANCHORE_APP}"
--version "${ANCHORE_APP_VERSION}"
--asset "${ANCHORE_ASSET}"
--dockerfile ./Dockerfile
--annotations "ci=gitlab,project=${CI_PROJECT_PATH},commit=${CI_COMMIT_SHORT_SHA},pipeline=${CI_PIPELINE_ID}"
--wait
### then get the results, aggregated across every asset on the app version:
- anchorectl app version vuln list "${ANCHORE_APP_VERSION}" --app "${ANCHORE_APP}"
### list the specific gates, triggers, and remediation first, so the detail is in the log even if the gate below fails the job
- anchorectl app version policy findings list "${ANCHORE_APP_VERSION}" --app "${ANCHORE_APP}"
- anchorectl app version policy status get "${ANCHORE_APP_VERSION}" --app "${ANCHORE_APP}"
c) Bring Your Own SBOM (BYOS)
An app version is not limited to container images. If an earlier stage of your pipeline already produces an SBOM — from a vendor, a different SCA tool, AnchoreCTL/Syft, or a non-container build artifact — upload that document directly and attach it to the version as an asset. Any supported SBOM format (CycloneDX, SPDX, or Syft-native JSON) is accepted.
The --type flag declares what the SBOM describes; unlike the container-image add, add sbom cannot infer this and defaults to unknown. See Asset Types for the full enum.
### Anchore App-Scoped SBOM Upload
# you will need three variables defined:
# ANCHORECTL_USERNAME
# ANCHORECTL_PASSWORD
# ANCHORECTL_URL
image: docker:latest
stages:
- anchore
variables:
ANCHORECTL_FAIL_BASED_ON_RESULTS: "false"
ANCHORE_APP: ${CI_PROJECT_NAME}
ANCHORE_APP_VERSION: ${CI_COMMIT_REF_SLUG}
ANCHORE_ASSET: vendor-sbom
### path to an SBOM produced by an earlier stage of your pipeline
ANCHORE_SBOM_FILE: ./sbom.json
Anchore:
stage: anchore
before_script:
### install anchorectl binary
- apk add --no-cache curl
- 'curl "$ANCHORECTL_URL/v2/system/anchorectl?operating_system=linux&architecture=amd64" -H "accept: */*" | tar -zx anchorectl && mv -v anchorectl /usr/bin && chmod +x /usr/bin/anchorectl && /usr/bin/anchorectl version'
- export PATH="${HOME}/.local/bin/:${PATH}"
script:
### create the app if it does not exist yet
- anchorectl app get "${ANCHORE_APP}" -o id > /dev/null 2>&1 || anchorectl app add "${ANCHORE_APP}" --contact-name "Platform Team"
### create the app version if it does not exist yet
- anchorectl app version get "${ANCHORE_APP_VERSION}" --app "${ANCHORE_APP}" -o id > /dev/null 2>&1 || anchorectl app version add "${ANCHORE_APP_VERSION}" --app "${ANCHORE_APP}" --status in_progress
### an asset name is unique within a version, so a re-run must replace the
### previous asset; a bare re-add returns HTTP 409 (AS503)
- anchorectl app version asset delete "${ANCHORE_ASSET}" --app "${ANCHORE_APP}" --version "${ANCHORE_APP_VERSION}" > /dev/null 2>&1 || true
### upload the existing SBOM and attach it to the app version as an asset
- >
anchorectl app version asset add sbom ${ANCHORE_SBOM_FILE}
--app "${ANCHORE_APP}"
--version "${ANCHORE_APP_VERSION}"
--asset "${ANCHORE_ASSET}"
--type container
--annotations "ci=gitlab,project=${CI_PROJECT_PATH},commit=${CI_COMMIT_SHORT_SHA},pipeline=${CI_PIPELINE_ID}"
--wait
### then get the results, aggregated across every asset on the app version:
- anchorectl app version vuln list "${ANCHORE_APP_VERSION}" --app "${ANCHORE_APP}"
- anchorectl app version policy status get "${ANCHORE_APP_VERSION}" --app "${ANCHORE_APP}"
artifacts keyword.Image-Scoped Pipelines
These pipelines add a single container image to the image catalog and read its vulnerability and policy results back inline. No app or app version is involved. For a detailed explanation of the difference between the two analysis modes below, refer to the Images concept page.
a) Distributed Mode
This is the most easily scalable method for scanning images. Distributed scanning uses the anchorectl utility to build the SBOM directly on the build runner and then pushes the SBOM to Anchore Enterprise through the API. To use this scanning method, paste the following workflow script into your new .gitlab-ci.yml file. After building the image from your Dockerfile and scanning it with anchorectl, this workflow will display vulnerabilities and policy results in the build log. After pasting, click “Commit changes” to save the new file.
### Anchore Distributed Scan
# you will need three variables defined:
# ANCHORECTL_USERNAME
# ANCHORECTL_PASSWORD
# ANCHORECTL_URL
image: docker:latest
services:
- docker:dind
stages:
- build
- anchore
variables:
### set this to true if you want the result of the policy check to determine whether the job succeeds or not
ANCHORECTL_FAIL_BASED_ON_RESULTS: "false"
ANCHORE_IMAGE: ${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG}
Build:
stage: build
script:
### build and push docker image
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
- docker build -t ${ANCHORE_IMAGE} .
- docker push ${ANCHORE_IMAGE}
Anchore:
stage: anchore
before_script:
### install anchorectl binary
- apk add --no-cache curl
- 'curl "$ANCHORECTL_URL/v2/system/anchorectl?operating_system=linux&architecture=amd64" -H "accept: */*" | tar -zx anchorectl && mv -v anchorectl /usr/bin && chmod +x /usr/bin/anchorectl && /usr/bin/anchorectl version'
- export PATH="${HOME}/.local/bin/:${PATH}"
script:
### provide registry credentials for anchorectl
- export ANCHORECTL_REGISTRY_AUTH_AUTHORITY=$CI_REGISTRY
- export ANCHORECTL_REGISTRY_AUTH_USERNAME="$CI_REGISTRY_USER"
- export ANCHORECTL_REGISTRY_AUTH_PASSWORD="$CI_REGISTRY_PASSWORD"
### scan image and push to anchore enterprise
- anchorectl image add --no-auto-subscribe --wait --dockerfile ./Dockerfile --from registry ${ANCHORE_IMAGE}
### then get the results:
- anchorectl image vulnerabilities ${ANCHORE_IMAGE}
- anchorectl image check --detail ${ANCHORE_IMAGE}
b) Centralized Mode
This method uses the “analyzer” pods in the Anchore Enterprise deployment to build the SBOM. This can create queuing if there are not enough analyzer processes, and this method will require the operator to provide registry credentials in the Enterprise backend (if the images to be scanned are in private registries). This method may be preferred in cases where the Anchore Enterprise operator does not control the image build process (the analyzers can simply poll registries to look for new image builds as they are pushed), and this method also allows the operator to simply queue up the image for asynchronous scanning later if vulnerability and policy results are not required immediately. If the user wants malware scanning results from Anchore Enterprise’s clamav integration, the Centralized Scanning method is required. To use this scanning method, paste the following workflow script into your new .gitlab-ci.yml file. After building the image from your Dockerfile,, this workflow will tell Anchore Enterprise to scan the image, then it will display the vulnerability and policy results in the build log. After pasting, click “Commit changes” to save the new file.
### Anchore Centralized Scan
# you will need three variables defined:
# ANCHORECTL_USERNAME
# ANCHORECTL_PASSWORD
# ANCHORECTL_URL
image: docker:latest
services:
- docker:dind
stages:
- build
- anchore
variables:
ANCHORECTL_FAIL_BASED_ON_RESULTS: "false"
ANCHORE_IMAGE: ${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG}
Build:
stage: build
script:
### build and push docker image
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
- docker build -t ${ANCHORE_IMAGE} .
- docker push ${ANCHORE_IMAGE}
Anchore:
stage: anchore
before_script:
### install anchorectl binary
- apk add --no-cache curl
- 'curl "$ANCHORECTL_URL/v2/system/anchorectl?operating_system=linux&architecture=amd64" -H "accept: */*" | tar -zx anchorectl && mv -v anchorectl /usr/bin && chmod +x /usr/bin/anchorectl && /usr/bin/anchorectl version'
- export PATH="${HOME}/.local/bin/:${PATH}"
script:
### note that private registries will require registry credentials to be configured in your Anchore deployment
### queue image for scanning
- anchorectl image add --no-auto-subscribe --wait --dockerfile ./Dockerfile ${ANCHORE_IMAGE}
### then get the results:
- anchorectl image vulnerabilities ${ANCHORE_IMAGE}
- anchorectl image check --detail ${ANCHORE_IMAGE}
4. View Pipeline
Gitlab will automatically start a pipeline. Navigate to “Build” -> “Pipelines” and then on your running pipeline.

5. View Output
Once the build is complete, click on the “anchore” stage and view the output of the job. You will see the results of the vulnerability match and policy evaluation in the output.
For an app-scoped pipeline, the same results are also available in the Anchore Enterprise GUI on the app version’s detail page, aggregated across every asset attached to the version. See Creating and Managing App Versions.
6. Gate the Pipeline on Policy
Both scopes expose --fail-based-on-results (-f), which returns a non-zero exit code when the policy evaluation result is fail, failing the GitLab job. Each example above sets the equivalent ANCHORECTL_FAIL_BASED_ON_RESULTS variable to "false"; change it to "true" to break the pipeline on a failing evaluation.
| Scope | Gating command |
|---|---|
| App-scoped | anchorectl app version policy status get <VERSION> --app <APP> --fail-based-on-results |
| Image-scoped | anchorectl image check <IMAGE> --fail-based-on-results --detail |
For more on turning findings into a pass/fail decision, see Gate the Pipeline on Policy.
Last modified September 28, 2026