GitLab CI Integration
The pipeline maps cleanly onto GitLab stages: build records provenance, gate decides, deploy ships, verify proves what landed.
CI/CD variables
Set these under Settings → CI/CD → Variables. Mark FIDES_API_TOKEN as masked and protected:
| Variable | Masked | Purpose |
|---|---|---|
FIDES_API_TOKEN |
yes | Writer service-account key |
FIDES_ENCRYPTION_KEY |
yes | Only if encrypting attestation payloads |
ORG_ID |
no | Org (tenant) UUID |
FLOW_ID |
no | Flow UUID for this service |
ENV_ID |
no | Environment UUID to gate and snapshot |
Complete pipeline
stages: [build, gate, deploy, verify]
variables:
FIDES_SERVER_URL: "https://fides.example.com"
TRAIL_ID: "$CI_COMMIT_SHA"
# FIDES_API_TOKEN, ORG_ID, FLOW_ID, ENV_ID set as (masked) CI/CD variables
build:
stage: build
script:
- curl -sSfL $FIDES_SERVER_URL/cli/install.sh | sh
- fides trail start --flow $FLOW_ID --trail $TRAIL_ID
--repository $CI_PROJECT_URL --commit $CI_COMMIT_SHA --branch $CI_COMMIT_REF_NAME
- docker build -t app:$CI_COMMIT_SHA .
- DIGEST=$(docker inspect --format='{{index .Id}}' app:$CI_COMMIT_SHA)
- echo "DIGEST=$DIGEST" > dig.env
- fides artifact report --org $ORG_ID --trail $TRAIL_ID --sha256 $DIGEST --name app --type docker
- fides attest junit --trail $TRAIL_ID --file reports/junit.xml --artifact-sha $DIGEST
- fides attest trivy --trail $TRAIL_ID --file reports/trivy.json --artifact-sha $DIGEST
artifacts: { reports: { dotenv: dig.env } }
gate:
stage: gate
script:
- fides assert --sha256 $DIGEST --policy production-release-rules
- fides policy check --env $ENV_ID --trail $TRAIL_ID
- fides change-gate --trail $TRAIL_ID # exit 2 => job fails => deploy blocked
deploy:
stage: deploy
script: ["./deploy.sh app:$CI_COMMIT_SHA"]
verify:
stage: verify
script:
- fides snapshot k8s --env $ENV_ID --namespace prod
- fides verify-chain --trail $TRAIL_ID
Passing the digest between stages
The dotenv report is the important detail. GitLab jobs run in separate containers, so DIGEST computed in build will not exist in gate unless you export it:
artifacts:
reports:
dotenv: dig.env
Every later job in the pipeline then has $DIGEST in its environment automatically.
Manual approval as a pipeline gate
change-gate holds until a human signs off. Model that as a manual job so the approval is a deliberate, attributable click:
approve:
stage: gate
when: manual
allow_failure: false
script:
- fides approve --trail $TRAIL_ID --role approver --reason "Release board sign-off ($GITLAB_USER_LOGIN)"
Four-eyes requires two distinct humans; the resulting segregation-of-duties attestation is only compliant: true when committer, approver and deployer are pairwise distinct.
Ingesting GitLab’s native SLSA attestations
provenance:
stage: build
script:
- fides attest fetch --trail $TRAIL_ID --artifact-sha $DIGEST --provider gitlab --repo $CI_PROJECT_PATH
Publishing the audit package
audit:
stage: verify
when: always
script:
- fides audit --trail $TRAIL_ID --output trail-audit.zip
artifacts:
paths: [trail-audit.zip]
expire_in: 1 year
Gate exit-code contract
| Gate | Fails the job when |
|---|---|
fides assert --sha256 $DIGEST --policy <name> |
artifact violates policy (exit 1) |
fides policy check --env $ENV --trail $TRAIL |
an applicable environment policy is unsatisfied (exit 2) |
fides allowlist check --env $ENV --sha $DIGEST |
digest not approved for the environment (exit 2) |
fides change-gate --trail $TRAIL |
verdict is HOLD (exit 2) |
fides verify-chain --trail $TRAIL |
attestation chain broken/tampered (exit 2) |
Because every gate fails the job on a non-zero exit, deploy simply never runs — you do not need extra conditional logic.