A vulnerability found in CI only matters if you can answer “is it running, and
where?”. TigerGate connects the two ends by capturing a few pieces of
build and deploy metadata from your pipeline, then matching them to cloud
inventory. The result: open a SCA/SAST/secret/IaC finding and see the container
image it shipped in, the cluster/service it deployed to, and the environment.
The two join keys
TigerGate links a pipeline scan to a running asset with two keys it captures at
scan time:
| Key | Where it comes from | What it connects to |
|---|
| Commit SHA | auto-detected from the repo / CI env | the repository + the image built from that commit |
Image digest (sha256:…) | the image your build produced | the running container in cloud inventory (CSPM / container scans) |
The image digest is the strong link — the same immutable digest you build, push,
and run is what cloud inventory records, so the match is exact (not tag- or
name-based).
What the CLI captures
Everything except the build/deploy specifics is automatic (commit, branch, PR,
committers). To complete the code → cloud chain, add the build/deploy flags on
your scan step:
| Flag | Env var | Meaning |
|---|
--image | — | The image reference your build produced. With --type all this also runs the image scan. |
--image-digest | TG_IMAGE_DIGEST / DOCKER_IMAGE_DIGEST | The sha256:… digest — the join key. Auto-resolved from --image when omitted (reads a pinned repo@sha256:… ref, or docker inspect for a locally built image). |
--deploy-target | TG_DEPLOY_TARGET | Where it’s deploying — cluster / service / region (e.g. prod-eks/payments). |
--environment | TG_ENVIRONMENT / ENVIRONMENT | production / staging / dev. |
None of these are required for a scan to succeed — they’re additive. Without
them you still get findings; with them, findings gain the code → cloud link.
Minimal setup
Add the build/deploy flags to the scan step that runs after your image is
built:
docker run --rm \
-e TIGERGATE_API_KEY \
-v "$PWD:/workspace" -w /workspace \
tigergate/tigergate-cli:latest \
scan --type all --upload \
--image registry.example.com/app:1.4 \
--deploy-target prod-eks/payments \
--environment production
The digest is resolved automatically from --image. To pass it explicitly (e.g.
you captured it from docker buildx output), set --image-digest or
TG_IMAGE_DIGEST.
End-to-end pipeline — all CI vendors
The pattern is identical everywhere: build → scan the source + image with the
build/deploy flags → deploy the same digest. Only the env-var names and how you
grab the digest differ. --image-digest auto-resolves from --image (via
docker inspect) when omitted, so most tabs let the CLI resolve it.
GitHub Actions
GitLab CI
Bitbucket Pipelines
Jenkins
CircleCI
Azure Pipelines
name: build-scan-deploy
on:
push: { branches: [main] }
jobs:
ship:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
# 1. Build + push; build-push-action emits the immutable digest.
- id: build
uses: docker/build-push-action@v6
with:
push: true
tags: registry.example.com/app:${{ github.sha }}
# 2. Scan source + image, with build/deploy tracking.
- name: TigerGate scan + track
run: |
docker run --rm \
-e TIGERGATE_API_KEY \
-e GITHUB_SHA -e GITHUB_REF -e GITHUB_REPOSITORY \
-v "$PWD:/workspace" -w /workspace \
tigergate/tigergate-cli:latest \
scan --type all --upload \
--image registry.example.com/app:${{ github.sha }} \
--image-digest "${{ steps.build.outputs.digest }}" \
--deploy-target prod-eks/payments \
--environment production
env:
TIGERGATE_API_KEY: ${{ secrets.TIGERGATE_API_KEY }}
# 3. Deploy the digest you scanned.
- run: kubectl set image deploy/payments app=registry.example.com/app@${{ steps.build.outputs.digest }}
stages: [build, scan, deploy]
build:
stage: build
image: docker:24
services: [docker:24-dind]
script:
- IMG="$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
- docker build -t "$IMG" .
- docker push "$IMG"
# capture the digest the registry returned
- DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMG" | cut -d@ -f2)
- echo "IMAGE=$IMG" >> build.env
- echo "IMAGE_DIGEST=$DIGEST" >> build.env
artifacts: { reports: { dotenv: build.env } }
scan:
stage: scan
image: tigergate/tigergate-cli:latest
needs: [build]
variables:
TG_DEPLOY_TARGET: prod-gke/checkout
TG_ENVIRONMENT: production
script:
- tigergate scan --type all --upload --image "$IMAGE" --image-digest "$IMAGE_DIGEST"
deploy:
stage: deploy
needs: [build]
script:
- kubectl set image deploy/checkout app="${IMAGE%:*}@$IMAGE_DIGEST"
TG_DEPLOY_TARGET / TG_ENVIRONMENT / TG_IMAGE_DIGEST are read from the
environment, so you can set them once as CI variables.pipelines:
branches:
main:
- step:
name: Build + scan + track
image: tigergate/tigergate-cli:latest
services: [docker]
script:
- export IMG="$DOCKER_REGISTRY/app:$BITBUCKET_COMMIT"
- docker build -t "$IMG" . && docker push "$IMG"
- export DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMG" | cut -d@ -f2)
- tigergate scan --type all --upload
--image "$IMG" --image-digest "$DIGEST"
--deploy-target prod-eks/api --environment production
pipeline {
agent any
environment { TIGERGATE_API_KEY = credentials('tigergate-api-key') }
stages {
stage('Build') {
steps {
script {
env.IMG = "registry.example.com/app:${env.GIT_COMMIT}"
sh "docker build -t ${env.IMG} . && docker push ${env.IMG}"
env.IMAGE_DIGEST = sh(returnStdout: true, script:
"docker inspect --format='{{index .RepoDigests 0}}' ${env.IMG} | cut -d@ -f2").trim()
}
}
}
stage('Scan + track') {
steps {
sh """
docker run --rm -e TIGERGATE_API_KEY \
-v \$WORKSPACE:/workspace -w /workspace \
tigergate/tigergate-cli:latest \
scan --type all --upload \
--image ${env.IMG} --image-digest ${env.IMAGE_DIGEST} \
--deploy-target prod-eks/api --environment production
"""
}
}
}
}
version: 2.1
jobs:
build-scan:
docker: [{ image: tigergate/tigergate-cli:latest }]
steps:
- checkout
- setup_remote_docker
- run: |
IMG="registry.example.com/app:$CIRCLE_SHA1"
docker build -t "$IMG" . && docker push "$IMG"
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMG" | cut -d@ -f2)
tigergate scan --type all --upload \
--image "$IMG" --image-digest "$DIGEST" \
--deploy-target prod-eks/api --environment production
workflows: { ci: { jobs: [build-scan] } }
trigger: [main]
pool: { vmImage: ubuntu-latest }
steps:
- script: |
IMG="$(containerRegistry)/app:$(Build.SourceVersion)"
docker build -t "$IMG" . && docker push "$IMG"
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMG" | cut -d@ -f2)
docker run --rm -e TIGERGATE_API_KEY \
-e BUILD_SOURCEVERSION -e BUILD_SOURCEBRANCH \
-v "$(System.DefaultWorkingDirectory)":/workspace -w /workspace \
tigergate/tigergate-cli:latest \
scan --type all --upload \
--image "$IMG" --image-digest "$DIGEST" \
--deploy-target prod-aks/api --environment production
env:
TIGERGATE_API_KEY: $(TIGERGATE_API_KEY)
The key discipline across all of them: scan the digest you deploy — pass the
same sha256 to the scan and to the deploy step (@sha256:…, not a mutable tag),
so the finding and the running pod share one digest.
No build step in a given pipeline? Drop --image* and just pass
--deploy-target / --environment. Findings still attribute to the
environment; only the image-digest link is unavailable. Other runners (Drone,
AWS CodePipeline, GCP Cloud Build) follow the same three steps — build, scan
with the flags, deploy the digest.
Scanning a container image
Image scanning is part of the same CLI — no separate tool or image subcommand.
It runs whenever you pass --image:
# Scan ONLY the image (OS + library CVEs and secrets in the image layers)
tigergate scan --type image --image registry.example.com/app:1.4 --upload
# Scan the source AND the image in one pass (image runs because --image is set)
tigergate scan --type all --image registry.example.com/app:1.4 --upload
--image accepts any pullable reference — name:tag or name@sha256:…. The
CLI pulls it if it isn’t already local.
- A pinned
…@sha256:… reference (or --image-digest) is what links the image
findings to the running cloud asset — see the per-vendor pipelines above.
- In CI, the image-scan step is the same
docker run … tigergate/tigergate-cli
invocation shown in each tab; just set --type image if you only want the
image (e.g. a separate registry-gate job after docker push).
Container-image inventory and registry/admission gating are a dedicated product —
see Container Security.
Where it shows up
Once the metadata is flowing:
- Findings carry their image digest, deploy target, and environment, so you can filter “show me criticals running in production”.
- IaC findings map to the cloud resource they declare (a misconfigured bucket/role/SG in the repo → the deployed asset).
- The security graph (Cloud Security → Map) is where the two ends meet — a code finding can be traced toward the internet-exposed cloud asset it reaches.
Under the hood the platform stores these keys on the scan run and links them
through a correlation table keyed by image_digest + git_commit_sha; cloud
inventory rows that carry the same digest (or git labels) join automatically.
Checklist
Build produces a digest
Use a builder that emits the image digest (docker/build-push-action outputs digest; docker buildx build --metadata-file; or docker inspect after a local build).
Scan after build, before deploy
Run scan --type all --upload --image <ref> --image-digest <sha256> so the artifact’s vulns are captured against the digest.
Pass deploy context
Add --deploy-target and --environment (or the TG_* env vars).
Deploy the same digest
Reference the image by @sha256:…, not by mutable tag, so the running asset matches what you scanned.