Skip to main content

Overview

Check Image is available as a GitHub Action that validates container images directly in your CI/CD workflows. The action downloads the check-image binary from GitHub Releases and runs natively on the GitHub runner, providing full access to the Docker daemon for local image validation.

Quick Start

name: Validate Image

on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: jarfernandez/[email protected]
        with:
          image: nginx:latest
This runs all 10 checks with default settings.

Action Inputs

All inputs are optional except image.

Required Inputs

InputDescriptionExample
imageContainer image to validatenginx:latest, ghcr.io/org/app:1.0

Configuration Inputs

InputDescriptionDefault
configPath to config file for the all command-
checksComma-separated list of checks to run (mutually exclusive with skip)-
skipComma-separated list of checks to skip (mutually exclusive with checks)-
fail-fastStop on first check failurefalse

Check-Specific Inputs

InputDescriptionDefault
max-ageMaximum image age in days90
max-sizeMaximum image size in MB500
max-layersMaximum number of layers20
allowed-portsComma-separated allowed ports or @file path-
allowed-platformsComma-separated allowed platforms or @file path-
registry-policyPath to registry policy file-
labels-policyPath to labels policy file-
secrets-policyPath to secrets policy file-
skip-env-varsSkip environment variable checks in secrets detectionfalse
skip-filesSkip file system checks in secrets detectionfalse
allow-shell-formAllow shell form for entrypoint or cmdfalse

Other Inputs

InputDescriptionDefault
log-levelLog level (trace, debug, info, warn, error, fatal, panic)info
versioncheck-image version to use0.19.4

Action Outputs

The action provides two outputs for use in subsequent workflow steps:
OutputDescriptionValues
resultValidation resultpassed, failed, error
jsonFull JSON output from check-imageJSON string

Basic Examples

All Checks (Default)

- uses: jarfernandez/[email protected]
  with:
    image: nginx:latest
Runs all 10 checks. Checks requiring configuration (registry, labels, platform) will report an error unless their configuration is provided.

Specific Checks Only

- uses: jarfernandez/[email protected]
  with:
    image: nginx:latest
    checks: age,size,root-user
    max-age: '30'
    max-size: '200'

Skip Specific Checks

- uses: jarfernandez/[email protected]
  with:
    image: nginx:latest
    skip: secrets,healthcheck
    max-age: '30'

Using Configuration Files

With Config File

steps:
  - uses: actions/checkout@v4
  
  - uses: jarfernandez/[email protected]
    with:
      image: myorg/myapp:${{ github.sha }}
      config: .check-image/config.yaml
Config file example (.check-image/config.yaml):
checks:
  age:
    max-age: 90
  size:
    max-size: 500
    max-layers: 20
  ports:
    allowed-ports: "@config/allowed-ports.yaml"
  registry:
    registry-policy: config/registry-policy.yaml
  root-user: {}
  secrets:
    secrets-policy: config/secrets-policy.yaml
  labels:
    labels-policy: config/labels-policy.yaml
  healthcheck: {}
  entrypoint: {}
  platform:
    allowed-platforms: "@config/allowed-platforms.yaml"

With Policy Files

steps:
  - uses: actions/checkout@v4
  
  - uses: jarfernandez/[email protected]
    with:
      image: ghcr.io/myorg/app:latest
      registry-policy: policies/registry-policy.yaml
      labels-policy: policies/labels-policy.json
      skip: healthcheck
Registry policy example (policies/registry-policy.yaml):
trusted-registries:
  - index.docker.io
  - ghcr.io
  - gcr.io
  - quay.io
Labels policy example (policies/labels-policy.json):
{
  "required-labels": [
    {"name": "maintainer"},
    {"name": "org.opencontainers.image.version", "pattern": "^v?\\d+\\.\\d+\\.\\d+$"},
    {"name": "org.opencontainers.image.vendor", "value": "MyCompany"}
  ]
}

Step Summary

The action automatically generates a GitHub Actions Step Summary visible in the workflow run UI:

Summary Table

Shows pass/fail status for each check:
CheckResultMessage
age✅ PASSImage is less than 90 days old
size✅ PASSImage size and layers within limits
root-user❌ FAILImage runs as root user

Failed Check Details

Expanded details for failed checks:
root-user:
  User: root
  UID: 0

Full JSON Output

Collapsible section with complete JSON output for programmatic processing.

Using Outputs

Accessing Result Output

- uses: jarfernandez/[email protected]
  id: check
  with:
    image: nginx:latest
    checks: age,size

- name: Check result
  run: |
    echo "Validation result: ${{ steps.check.outputs.result }}"
    if [ "${{ steps.check.outputs.result }}" = "failed" ]; then
      echo "Image validation failed"
    fi

Processing JSON Output

- uses: jarfernandez/[email protected]
  id: check
  continue-on-error: true
  with:
    image: nginx:latest
    checks: age,size

- name: Process results
  if: always()
  run: |
    echo '${{ steps.check.outputs.json }}' | jq '.summary'
    echo '${{ steps.check.outputs.json }}' | jq '.checks[] | select(.passed == false)'

Conditional Steps

- uses: jarfernandez/[email protected]
  id: check
  continue-on-error: true
  with:
    image: nginx:latest

- name: Send notification on failure
  if: steps.check.outputs.result == 'failed'
  run: |
    curl -X POST ${{ secrets.WEBHOOK_URL }} \
      -d '{"status": "failed", "details": ${{ steps.check.outputs.json }}}'

Soft Failure Mode

Use continue-on-error to prevent validation failures from stopping the workflow:
- uses: jarfernandez/[email protected]
  id: check
  continue-on-error: true
  with:
    image: nginx:latest
    config: .check-image/config.yaml

- name: Handle results
  if: steps.check.outputs.result == 'failed'
  run: |
    echo "⚠️ Image validation failed but continuing"
    echo '${{ steps.check.outputs.json }}' | jq '.checks[] | select(.passed == false) | .check'

Complete Workflow Examples

Validate Built Image

name: Build and Validate

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build-and-validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .
      
      - uses: jarfernandez/[email protected]
        with:
          image: myapp:${{ github.sha }}
          max-age: '90'
          max-size: '500'
          checks: age,size,root-user,healthcheck,entrypoint

Validate Registry Image with Auth

name: Validate Registry Image

on:
  schedule:
    - cron: '0 0 * * *'  # Daily

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Login to GHCR
        run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
      
      - uses: jarfernandez/[email protected]
        with:
          image: ghcr.io/${{ github.repository }}:latest
          registry-policy: .github/registry-policy.yaml
          labels-policy: .github/labels-policy.yaml
          max-age: '30'

Multi-Image Validation

name: Validate Multiple Images

on: [push]

jobs:
  validate:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        image:
          - nginx:latest
          - alpine:latest
          - ubuntu:latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: jarfernandez/[email protected]
        with:
          image: ${{ matrix.image }}
          checks: age,size,root-user
          max-age: '90'
          max-size: '200'

Fail-Fast Mode

- uses: jarfernandez/[email protected]
  with:
    image: nginx:latest
    fail-fast: true
    checks: age,size,root-user,healthcheck
    max-age: '30'
Stops on the first check that fails, useful for quick feedback.

Platform-Specific Validation

Validate Multi-Arch Image

- uses: jarfernandez/[email protected]
  with:
    image: myapp:latest
    allowed-platforms: linux/amd64,linux/arm64

With Platform Policy File

steps:
  - uses: actions/checkout@v4
  
  - uses: jarfernandez/[email protected]
    with:
      image: myapp:latest
      allowed-platforms: "@.github/allowed-platforms.yaml"
Platform policy file (.github/allowed-platforms.yaml):
allowed-platforms:
  - linux/amd64
  - linux/arm64
  - linux/arm/v7

Private Registry Authentication

The action supports private registries through Docker authentication:

Using docker/login-action

steps:
  - name: Login to Docker Hub
    uses: docker/login-action@v3
    with:
      username: ${{ secrets.DOCKER_USERNAME }}
      password: ${{ secrets.DOCKER_PASSWORD }}
  
  - uses: jarfernandez/[email protected]
    with:
      image: myorg/private-app:latest

GitHub Container Registry (GHCR)

steps:
  - name: Login to GHCR
    uses: docker/login-action@v3
    with:
      registry: ghcr.io
      username: ${{ github.actor }}
      password: ${{ secrets.GITHUB_TOKEN }}
  
  - uses: jarfernandez/[email protected]
    with:
      image: ghcr.io/${{ github.repository }}/app:latest

AWS ECR

steps:
  - name: Configure AWS credentials
    uses: aws-actions/configure-aws-credentials@v4
    with:
      aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
      aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      aws-region: us-east-1
  
  - name: Login to ECR
    uses: aws-actions/amazon-ecr-login@v2
  
  - uses: jarfernandez/[email protected]
    with:
      image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:latest

Security Best Practices

Validate Before Push

name: Build, Validate, Push

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .
      
      - name: Validate image
        uses: jarfernandez/[email protected]
        with:
          image: myapp:${{ github.sha }}
          config: .check-image/config.yaml
      
      - name: Push image (only if validation passed)
        run: |
          echo "${{ secrets.REGISTRY_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
          docker tag myapp:${{ github.sha }} ghcr.io/${{ github.repository }}:latest
          docker push ghcr.io/${{ github.repository }}:latest

Block Merges on Validation Failure

Make the validation check required in branch protection rules:
name: PR Validation

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: jarfernandez/[email protected]
        with:
          image: myapp:pr-${{ github.event.pull_request.number }}
          checks: age,size,root-user,secrets,entrypoint
Then enable “Require status checks to pass” in GitHub settings.

Debugging

Enable Debug Logging

- uses: jarfernandez/[email protected]
  with:
    image: nginx:latest
    log-level: debug

Enable GitHub Actions Debug Logging

Set repository secrets:
  • ACTIONS_STEP_DEBUG = true
  • ACTIONS_RUNNER_DEBUG = true

Inspect Action Outputs

- uses: jarfernandez/[email protected]
  id: check
  with:
    image: nginx:latest

- name: Debug outputs
  if: always()
  run: |
    echo "Result: ${{ steps.check.outputs.result }}"
    echo "JSON output:"
    echo '${{ steps.check.outputs.json }}' | jq '.'

Action Version Management

Pinning Versions

# Recommended: Pin to specific version
- uses: jarfernandez/[email protected]

# Pin to major version (auto-updates)
- uses: jarfernandez/check-image@v0

# Pin to commit SHA (most secure)
- uses: jarfernandez/check-image@a1b2c3d

Dependabot Updates

Add to .github/dependabot.yml:
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

Troubleshooting

Action Not Found

Error: Unable to resolve action `jarfernandez/[email protected]`
Solution: Ensure you’re using a valid release tag. Check releases page.

Image Not Found

Error: Image not found: myapp:latest
Solutions:
  • Ensure image is built before validation
  • Check image name and tag spelling
  • Verify registry authentication for private images

Config File Not Found

Error: config file not found: .check-image/config.yaml
Solution: Ensure actions/checkout@v4 runs before the check-image action.

Checks Require Configuration

Error: --registry-policy is required for registry check
Solution: Either provide the required configuration or skip the check:
with:
  image: nginx:latest
  skip: registry,labels,platform

Next Steps

Docker Integration

Use Check Image with Docker CLI

CI/CD Integration

Integrate with GitLab, CircleCI, Jenkins, and more

Build docs developers (and LLMs) love