GitHub Action running Google Container Structure Tests against a container image
- Docker Hub: devopsinfra/action-container-structure-test:latest
- GitHub Packages: ghcr.io/devops-infra/action-container-structure-test:latest
- Runs GoogleContainerTools/container-structure-test in CI
- Supports all test types: command tests, file existence tests, file content tests, metadata tests, license tests
- Supports
docker,tar, andhostdrivers - Exposes test totals (total / passed / failed) as Action outputs
- Multi-platform image:
linux/amd64andlinux/arm64 - Lightweight Alpine-based Docker image
Check also other actions from DevOps-Infra
This action supports three tag levels for flexible versioning:
vX: latest patch of the major version (e.g.,v1).vX.Y: latest patch of the minor version (e.g.,v1.0).vX.Y.Z: fixed to a specific release (e.g.,v1.0.0).
- name: Run the Action
uses: devops-infra/action-container-structure-test@v1.0.0
with:
image: my-image:latest
config: tests/structure-test.yaml
driver: docker
output: text
debug: false| Input | Required | Default | Description |
|---|---|---|---|
image |
* | Image to test. Required unless image_from_oci_layout is set. Mutually exclusive with it. |
|
config |
Yes | Path(s) to test config file(s). Space or newline-separated for multiple files. | |
driver |
No | docker |
Driver to use when running tests: docker, tar, or host. |
platform |
No | Platform to test, e.g. linux/amd64 or linux/arm64. Defaults to host arch. |
|
pull |
No | false |
Force pull the image before running tests (docker driver only). |
save |
No | false |
Preserve created containers after the test run. |
quiet |
No | false |
Suppress test output. |
no_color |
No | false |
Disable colorized output. |
output |
No | text |
Output format: text, json, or junit. |
test_report |
No | Write test results to this file path, then print it to logs. CST converts text to json automatically. |
|
junit_suite_name |
No | Name for the JUnit test suite (only used when output is junit). |
|
metadata |
No | Path to image metadata file. | |
runtime |
No | Runtime to use with the docker driver (e.g. runsc for gVisor). |
|
force |
No | false |
Force run of host driver without interactive prompt. |
image_from_oci_layout |
No | Path to OCI image layout directory. Mutually exclusive with image. |
|
default_image_tag |
No | Default image tag when OCI layout lacks a ref annotation. Requires image_from_oci_layout. |
|
ignore_ref_annotation |
No | false |
Ignore org.opencontainers.image.ref.name annotation when loading OCI layout. |
debug |
No | false |
Enable verbose debug logging in the action entrypoint. |
| Output | Description |
|---|---|
total |
Total number of tests executed. |
passed |
Number of tests that passed. |
failed |
Number of tests that failed. |
exit_code |
Exit code returned by container-structure-test. |
Run structure tests against a Docker image using a single config file.
name: Run structure tests on each commit
on: [push]
jobs:
container-structure-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Build image
run: docker build -t my-image:latest .
- uses: devops-infra/action-container-structure-test@v1
with:
image: my-image:latest
config: tests/structure-test.yamlRun tests with multiple config files, JSON output, and a saved report.
name: Run structure tests on each commit
on: [push]
jobs:
container-structure-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Build image
run: docker build -t my-image:latest .
- name: Run structure tests
id: cst
uses: devops-infra/action-container-structure-test@v1
with:
image: my-image:latest
config: |
tests/command-tests.yaml
tests/file-tests.yaml
output: json
test_report: /tmp/cst-report.json
pull: 'false'
debug: 'false'
- name: Show test results
run: |
echo "Total: ${{ steps.cst.outputs.total }}"
echo "Passed: ${{ steps.cst.outputs.passed }}"
echo "Failed: ${{ steps.cst.outputs.failed }}"
echo "Exit: ${{ steps.cst.outputs.exit_code }}"Run the action pinned to a specific version tag.
name: Run structure tests on each commit
on: [push]
jobs:
container-structure-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: devops-infra/action-container-structure-test@v1.0.0
id: pin-patch-version
with:
image: my-image:latest
config: tests/structure-test.yaml
- uses: devops-infra/action-container-structure-test@v1.0
id: pin-minor-version
with:
image: my-image:latest
config: tests/structure-test.yaml
- uses: devops-infra/action-container-structure-test@v1
id: pin-major-version
with:
image: my-image:latest
config: tests/structure-test.yamlGenerate a JUnit report for test result publishing.
name: Run structure tests on each commit
on: [push]
jobs:
container-structure-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Build image
run: docker build -t my-image:latest .
- name: Run structure tests
uses: devops-infra/action-container-structure-test@v1
with:
image: my-image:latest
config: tests/structure-test.yaml
output: junit
junit_suite_name: container-structure-tests
test_report: /tmp/cst-results.xmlTest an exported image without a Docker daemon (file/metadata tests only).
name: Run structure tests on each commit
on: [push]
jobs:
container-structure-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Export image as tar
run: docker save my-image:latest -o my-image.tar
- uses: devops-infra/action-container-structure-test@v1
with:
image: my-image.tar
config: tests/file-tests.yaml
driver: tarContainer Structure Test configs are YAML or JSON files.
The current schema version is 2.0.0 and must be set in every config.
schemaVersion: '2.0.0'
commandTests:
- name: "python version"
command: "python3"
args: ["--version"]
expectedOutput: ["Python 3\\..*"]
fileExistenceTests:
- name: "entrypoint exists"
path: "/entrypoint.sh"
shouldExist: true
permissions: "-rwxr-xr-x"
fileContentTests:
- name: "sources list"
path: "/etc/os-release"
expectedContents: [".*alpine.*"]
metadataTest:
workdir: "/app"
envVars:
- key: PATH
value: "/usr/local/bin:.*"
isRegex: trueFull documentation: GoogleContainerTools/container-structure-test
Workflows included:
- (Auto) Pull Request Create (
.github/workflows/auto-pull-request-create.yml)- Trigger: push to any branch except
masteranddependabot/**. - Jobs:
- Lint
- Build and push multi-platform test image, and inspect manifest
- Create pull request
- Trigger: push to any branch except
- (Auto) Create release (
.github/workflows/auto-create-release.yml)- Trigger:
pull_requestclosed andpushtorelease/**(runs only for merged PRs fromrelease/) - Jobs:
- Lint
- Tagging: create
vX.Y.Z; updatevX.YandvX(fails if full tag exists on remote) - Build and push multi-platform image, and inspect manifest
- Publish GitHub Release
- Update Docker hub description
- Trigger:
- (Cron) Weekly dependency build (
.github/workflows/cron-dependency-update.yml)- Trigger: Weekly on Monday at 08:00 UTC
- Jobs:
- Lint
- Build and push multi-platform test image, and inspect manifest
- (Manual) Update version (
.github/workflows/manual-release-create.yml)- Trigger: manual
workflow_dispatchwithtype(patch|minor|major|set) xorversionwhentype=setpushes torelease/**branch and creates a pull request to create a new release - Jobs:
- Update version: bump or set; output
REL_VERSION - Build and push multi-platform image, and inspect manifest
- Create pull request, approve to create a release
- Update version: bump or set; output
- Trigger: manual
Prerequisites:
- Docker with Buildx,
- Task (installed via workflow or from https://taskfile.dev),
- gnu-sed if on macOS (
brew install gnu-sed), - pre-commit (optional).
Common tasks:
# Run all linters
task lint
# Build multi-arch images locally (no push)
task docker:build
# Build a local runnable image for your current architecture
task docker:build:local
# Run container-structure-test action locally (build is required and enforced)
task docker:test:local IMAGE=my-image:latest CONFIG=tests/structure-test.yaml
# Run with multiple config files
task docker:test:local IMAGE=my-image:latest CONFIG="tests/command-tests.yaml tests/file-tests.yaml"
# Run against OCI layout
task docker:test:local IMAGE_FROM_OCI_LAYOUT=./oci-layout CONFIG=tests/structure-test.yaml
# Run built-in smoke test against the locally built action image
task docker:test:smoke
# Push images (requires DOCKER_TOKEN and GITHUB_TOKEN)
DOCKER_TOKEN=... GITHUB_TOKEN=... task docker:pushLocal run notes:
docker:test:localalways builds the action image first viadocker:build:local.docker:test:smokeusestests/docker/local-image.ymlto verify installed binaries, metadata, and cache cleanup on the built image.- For
DRIVER=docker(default), Docker socket access is required. - Optional task variables map to action inputs, for example:
OUTPUT=json,PULL=true,PLATFORM=linux/arm64,DEBUG=true
Pre-commit hooks:
brew install pre-commit
task pre-commit:install
task pre-commitContributions are welcome! See CONTRIBUTING. This project is licensed under the MIT License - see the LICENSE file for details.
This project is licensed under the MIT License - see the LICENSE file for details.
If you have any questions or need help, please:
- π Create an issue
- π Star this repository if you find it useful!