Getting started
CI and pull requests
A CI job runs the same atmos scan . you run on your machine, signs in with a Workspace Token and lets the exit code decide the build. In a pull request, Atmos fails the build only on the findings the change brings in.
Store the Workspace Token#
CI signs in with a Workspace Token instead of atmos login. An owner or admin creates one on the platform's Tokens page, and you store it as a CI secret named ATMOS_TOKEN. The install script and the scan both read it from the environment. See sign-ins and the Workspace Token and protect your credentials.
Both jobs below pin the CLI version with bash -s 0.5.0, so a release reaches CI only when you change that version.
GitHub Actions#
Store the token as an Actions secret named ATMOS_TOKEN, then save this workflow as .github/workflows/atmos.yml. It scans every pull request and every push to main:
name: Atmos on: pull_request: push: branches: [main] jobs: atmos: runs-on: ubuntu-latest env: ATMOS_TOKEN: ${{ secrets.ATMOS_TOKEN }} steps: - uses: actions/checkout@v6 - name: Install atmos run: | curl -fsSL https://atmoslab.dev/install.sh | bash -s 0.5.0 echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Scan run: atmos scan . --fail-on high
The install script puts atmos in ~/.local/bin, and the GITHUB_PATH line puts that folder on the PATH of the steps after it. Replace main with your default branch. The pull request check counts a finding as new when the platform does not have it on your default branch, so keep the scan of every push to the default branch.
GitLab CI#
Add the token under Settings, CI/CD, Variables with Masked visibility. With Protect variable on, only pipelines on protected branches and tags get the token, so merge request pipelines from other branches cannot scan. With it off, every pipeline in the project gets the token, and anyone who can push a branch can run a job that prints it. Masking hides the value in job logs but does not stop that, so turn Protect variable off only in a project where you trust everyone who can push. Then add this job to .gitlab-ci.yml. It runs in merge request pipelines and on your default branch:
atmos: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH image: alpine:3.24 script: - apk add --no-cache bash curl git openssl - curl -fsSL https://atmoslab.dev/install.sh | bash -s 0.5.0 - export PATH="$HOME/.local/bin:$PATH" - atmos scan . --fail-on high
The job needs bash, curl and git, and the install script needs openssl. The apk add line installs them in the Alpine image. In an image that already has them, drop that line.
Fail only on new findings#
A pull request should fail on what it adds, not on findings its target branch already had. In a GitHub Actions pull request and in a GitLab merge request pipeline, Atmos compares with the commit where your branch left the target branch, the baseline commit. Findings the baseline commit already had are left out of the report and never fail the build, and the report ends with how many there were.
| CI | Baseline commit |
|---|---|
| GitHub Actions | Where your branch left GITHUB_BASE_REF, the target branch of the pull request. |
| GitLab CI | Where your branch left CI_MERGE_REQUEST_TARGET_BRANCH_SHA in a merged results pipeline, else CI_MERGE_REQUEST_DIFF_BASE_SHA. |
| Anywhere else | None, so every finding counts. Pass --baseline-commit <REF> to compare with a branch or commit yourself. |
- Atmos scans the baseline commit too, so a scan with a baseline takes about twice as long. The baseline scan reads the
.atmos/rules/,.atmosignoreand.gitignoreof the baseline commit. - CI checkouts are shallow by default. When the checkout does not have the target branch, the scan fetches it from
origintogether with your branch's history, the last 50 commits, then 500, then 5000, until it finds where your branch left the target. In GitLab it fetches the branch named inCI_MERGE_REQUEST_TARGET_BRANCH_NAME. - A branch that left its target more than 5000 commits back needs the full history. With GitHub Actions, set
fetch-depth: 0underwith:onactions/checkout. When the fetch does not find the baseline commit, the scan prints a warning and every finding counts. - A
--baseline-committhe checkout does not have is not fetched. It stops the scan with exit code 2 before anything is sent.
On a push to the default branch there is no target branch, so every finding counts there. JSON and SARIF output still list every finding and mark each one new or unchanged, in baseline_state in JSON and in baselineState in SARIF.
Choose what fails the build#
Without --fail-on, every finding fails the build. The jobs above pass --fail-on high, so critical and high findings fail it, as they fail the pull request check, and findings below high stay in the report. Findings your team decided not to fail on, in .atmos-ignore.yaml or with triage on the platform, never fail the build. See ignoring findings. Every exit code is in the CLI reference.
Pull request checks and comments#
With the GitHub App connected, a scan of a pull request also posts a check and one comment that updates in place on every new scan. See pull request feedback. The check and the exit code agree: both count only new findings, both skip the findings .atmos-ignore.yaml covers and the findings a person marked False positive, Accepted or Ignored, and both fail on critical and high with the jobs above.