Skip to content

Adding the runner to your CI pipeline

Analysis runs in the CI pipeline where your code already lives - your source code never leaves your infrastructure. In the examples below, the runner is invoked with the project-specific token.

All examples pull the image from the AppArch Hub; see that page for authentication, language matching and common errors.

.github/workflows/apparch.yml
name: AppArch
on: [pull_request]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # the target branch is then in the clone: the change's own files and its merge base
- run: |
echo "$APPARCH_INSTALLATION_KEY" | docker login hub.apparch.io -u "$APPARCH_INSTALLATION_ID" --password-stdin
docker run --rm -v "$PWD:/workspace:ro" \
-e APPARCH_Collector__Token="$APPARCH_TOKEN" \
-e APPARCH_Collector__ApiUrl="https://api.yourdomain.com" \
-e GITHUB_EVENT_NAME -e GITHUB_REF -e GITHUB_REPOSITORY \
-e GITHUB_SERVER_URL -e GITHUB_BASE_REF -e GITHUB_SHA \
hub.apparch.io/collectors/dotnet:latest /workspace
env:
APPARCH_TOKEN: ${{ secrets.APPARCH_TOKEN }}
APPARCH_INSTALLATION_ID: ${{ secrets.APPARCH_INSTALLATION_ID }}
APPARCH_INSTALLATION_KEY: ${{ secrets.APPARCH_INSTALLATION_KEY }}
# Image pulls are authorized with your INSTALLATION credential, not the project token
# (Settings → CI/CD → Variables):
# DOCKER_AUTH_CONFIG = {"auths":{"hub.apparch.io":{"auth":"base64(<installation-id>:<installation-key>)"}}}
archpilot:
image: hub.apparch.io/collectors/dotnet:latest
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
variables:
APPARCH_Collector__Token: "$APPARCH_TOKEN"
APPARCH_Collector__ApiUrl: "https://api.yourdomain.com"
script:
# Optional flags: --modules archpilot,archscan (pin what runs).
# --modules archpilot alone reads ONLY the merge request's changed files - the fast option
# for MR pipelines; archscan / archsecurity are repository reports and read everything.
# --fail-on critical (fail this step when findings at/above the level exist)
- apparch "$CI_PROJECT_DIR"
pipelines:
pull-requests:
'**':
- step:
image:
name: hub.apparch.io/collectors/dotnet:latest
username: $APPARCH_INSTALLATION_ID
password: $APPARCH_INSTALLATION_KEY
run-as-user: 0 # the clone belongs to root; a PR run fetches the target branch into it
script:
- export APPARCH_Collector__Token="$APPARCH_TOKEN"
- export APPARCH_Collector__ApiUrl="https://api.yourdomain.com"
- apparch "$BITBUCKET_CLONE_DIR"
trigger: none
pr: ['*']
pool: { vmImage: ubuntu-latest }
steps:
- checkout: self
fetchDepth: 0 # the target branch is then in the clone: the change's own files and its merge base
- script: |
echo "$(APPARCH_INSTALLATION_KEY)" | docker login hub.apparch.io -u "$(APPARCH_INSTALLATION_ID)" --password-stdin
docker run --rm -v "$(Build.SourcesDirectory):/workspace:ro" \
-e APPARCH_Collector__Token="$(APPARCH_TOKEN)" \
-e APPARCH_Collector__ApiUrl="https://api.yourdomain.com" \
-e SYSTEM_PULLREQUEST_PULLREQUESTID="$(System.PullRequest.PullRequestId)" \
-e SYSTEM_PULLREQUEST_TARGETBRANCH="$(System.PullRequest.TargetBranch)" \
-e SYSTEM_TEAMPROJECT="$(System.TeamProject)" \
-e BUILD_REPOSITORY_NAME="$(Build.Repository.Name)" \
-e SYSTEM_COLLECTIONURI="$(System.CollectionUri)" \
-e BUILD_SOURCEVERSION="$(Build.SourceVersion)" \
hub.apparch.io/collectors/dotnet:latest /workspace
pipeline {
agent { docker { image 'hub.apparch.io/collectors/dotnet:latest' registryUrl 'https://hub.apparch.io' registryCredentialsId 'apparch-hub' } }
environment {
APPARCH_Collector__Token = credentials('APPARCH_TOKEN')
APPARCH_Collector__ApiUrl = 'https://api.yourdomain.com'
// PR review (ArchPilot) needs the MR context set explicitly; without these the run is a full scan (ArchScan).
APPARCH_Collector__Provider__Type = 'gitlab' // gitlab | github | bitbucket | azuredevops
APPARCH_Collector__Provider__ProjectId = 'group/project'
APPARCH_Collector__Provider__PullRequestId = "${env.CHANGE_ID}"
}
stages {
stage('AppArch') {
steps {
sh 'apparch "$WORKSPACE"'
}
}
}
}

The next time a PR/MR is opened, ArchPilot leaves line-by-line comments. The runner picks the mode automatically: a PR/MR pipeline runs the fast review (ArchPilot), a push pipeline runs the full scan (ArchScan).

The collector images run as a non-root user (uid 10001) and only read the checkout, which may belong to another user. On a pull request the collector compares the change with its target branch (the change’s own files, its merge base), so that branch must be in the clone:

  • GitHub Actions and Azure DevOps: the examples check out the whole history (fetch-depth: 0, fetchDepth: 0); the target branch is then already there and nothing is written.
  • GitLab CI and Jenkins: work as written (GitLab’s clone is writable for the job, Jenkins runs the container as its own user).
  • Bitbucket Pipelines: the clone always belongs to root and the collector fetches the target branch into it, so the example runs the step as root (run-as-user: 0).

Without this the run still passes, but it reviews every finding instead of the change’s and skips the checks that compare with the target branch (a removed authorization, a new dependency cycle). If your runner insists on root elsewhere, say so explicitly: docker run --user 0 ..., a Kubernetes securityContext.runAsUser: 0.

If your AppArch installation is served with a certificate the CI runner does not trust - an internal CA, or a self-signed cert - the run fails at the rule fetch with a TLS error. Two ways out, in order of preference:

  1. Trust the CA in the runner image. The certificate is then verified like any other and nothing about the collector changes.
  2. Turn verification off with SslVerify:
Terminal window
SslVerify=false # or SSL_VERIFY, or APPARCH_Collector__SslVerify

false, 0, no and off disable it; anything else - empty included - leaves it on. The .NET collector also accepts the --no-ssl-verify flag. Every run prints ssl verify on or ssl verify OFF - server certificate not checked in its boot block, so a pipeline that weakens it says so in its own log.