Skip to content

GitHub Actions

Scan every pull request and post a sticky summary comment with the score and findings.

On this page

The Docker Doctor GitHub Action scans the Dockerfiles and Compose files in your repository on every pull request and posts a single summary comment that updates in place on each push — no comment spam.

Quick start

# .github/workflows/docker-doctor.yml
name: Docker Doctor
on:
  pull_request:
permissions:
  contents: read
  pull-requests: write
  issues: write
  statuses: write
jobs:
  docker-doctor:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: PunGrumpy/docker-doctor@v1

That's the whole setup. The action is advisory by default — it reports findings on every pull request but never fails the check, so it won't red-X a teammate's PR on day one.

What the comment shows

Every pull request gets one sticky comment that updates in place on each push:

  • A per-file status table — status dot, issue counts, and when the scan ran — sorted worst-first, with every file linked at the scanned commit. The first 20 files are visible. The rest are in a collapsed list.
  • The health score, linking to a shareable score card.
  • Up to 50 findings grouped by file with a link to the exact line, the message, and the rule id. Findings stay collapsed unless there are errors.

See a live comment on this pull request.

The same summary is mirrored to the workflow's job summary. GitHub limits a comment to 65,536 characters. On a repository with hundreds of Docker files, the comment lists fewer files and refers you to the job summary, which always has the full table. Add a push trigger (push: branches: [main]) alongside pull_request if you also want scans of your default branch visible there. Pushes never fail the check either way.

On a pull request from a fork, GitHub gives the workflow a read-only token. The action then skips the comment and the commit status with a warning. The job summary and the blocking gate still work.

Use pull_request, not pull_request_target. Docker Doctor loads the config file from the scanned directory, and a JavaScript or TypeScript config runs as code, so a pull request's own config runs on the runner. Under pull_request, a pull request from a fork gets a read-only token and no secrets. Under pull_request_target with a checkout of the pull request head, that code would run with the base repository's token and secrets. The action also comments and applies the blocking gate only on pull_request. A branch in the same repository runs with the permissions the workflow grants, so review a changed config file as you would any other code.

Commit status

Every run also publishes a commit status on the scanned commit: Docker Doctor · Score: 82/100 · 1 error · 3 warnings, with a Details link to the run. It appears in the checks line on pull requests and on the commit itself, so a push to your default branch still gets a score even though it gets no PR comment. On pull requests the status mirrors the blocking gate. On pushes findings never turn it red. When a scan has no score because the files it found failed to parse, the status is error on pull requests and pushes alike. Publishing needs statuses: write; without it the action logs a warning and moves on. Set commit-status: false to turn it off.

Inputs

InputDefaultDescription
directory.Directory to scan, relative to the repository root.
versionlatest@docker-doctor/cli version to run — pin it to keep scans reproducible.
blockingnoneWhich severities fail the check on pull requests: none (advisory), warning, or error.
commenttruePost/update the sticky summary comment on pull requests.
commit-statustruePublish a commit status with the score and issue counts, linking to the run. Requires statuses: write.
config—Path to a config file, relative to the repository root, passed to the CLI as --config.

Outputs

Every run exposes step outputs for downstream jobs: score, label, error-count, warning-count, info-count, and total-issues. score and label are empty strings when no file was analyzed, including when no Dockerfile or Compose file was found.

- uses: PunGrumpy/docker-doctor@v1
  id: doctor
- run: echo "Score is ${{ steps.doctor.outputs.score }}"

Graduating the gate

When your team trusts the signal, raise blocking:

- uses: PunGrumpy/docker-doctor@v1
  with:
    blocking: error # fail on error-severity findings ("warning" fails on warnings too)

Three rules always hold:

  • Only pull_request events can fail. Pushes to your default branch are health snapshots — they never go red.
  • On pull requests, a scan that crashes without producing a report fails the check regardless of blocking, so a broken setup can't silently pass. On pushes it stays advisory, like every other result.
  • A scan that could not analyze one of the discovered files (the CLI exits 2) fails the pull request check regardless of blocking. The summary marks those files Unanalyzed, so a broken Dockerfile can't pass as clean. The parser's message and any config warnings are in the workflow log, in the Docker Doctor CLI output group.

Versioning

The action is versioned independently of the CLI. The floating major tag — v1 today — always points at the latest action release and moves only when the action itself changes; breaking changes get a new major. Pin a commit SHA if you need full reproducibility.

The version input controls the CLI independently — by default it runs latest, so the action always scans with the newest rules.