This is a sample web application showcasing a multi-tier architecture using Node.js, Python (Flask), PostgreSQL, and nginx.
The app is available in two versions:
- Legacy version with traditional upstream container images.
- Chainguard version using minimal, secure-by-default, zero to near-zero CVE container images.
- Docker - Building and Running the Application(s)
- grype - Scanning Container Images for Vulnerabilities
- Cosign - Downloading Attestations and Verifying signatures
Clone this directory and cd into it from your terminal:
git clone https://github.com/bakenfazer/chaiku && cd chaikudocker compose up -d --buildOpen http://localhost:80 in your browser to view the website. You should see the following:
Check that the backend API works by running:
curl http://localhost:5000You should see the following response: Hooray! The API works.
./scanners/grype-scan.shThis will save your results to ./scanners/scan-results/grype-legacy-images.csv.
To clean everything, including volumes:
docker compose down -vdocker compose -f docker-compose-chainguard.yaml up -d --build-
Open http://localhost:80
-
Check the API:
curl http://localhost:5000/./scanners/grype-scan.shThis will save your results to ./scanners/scan-results/grype-chainguard-images.csv.
To clean everything, including volumes:
docker compose down -vAfter scanning both versions, open the CSV files to review the outputs to compare:
- Total CVEs
- Severity levels (Critical, High, etc.)
- Image size and dependency differences
This highlights the value of using Chainguard's minimal, secure-by-default images like those from Chainguard.
Each Chainguard image has published specifications at:
https://images.chainguard.dev/directory/image/<image-name>/specifications
For example, the node Production image runs as a non-root user by default, has no apk package manager, ships without a shell, and uses a minimal set of environment variables:
| Property | Value |
|---|---|
| Has apk? | No |
| Has a shell? | No |
| User | 65532 (non-root) |
| Entrypoint | /usr/bin/node |
| Working Directory | /app |
Raw configuration is also available in JSON format on the specifications page, useful for automation and policy enforcement.
Note: For each image, Chainguard provides a "-dev" variant that includes a shell and a package manager.
Chainguard images are signed with Sigstore and include verifiable attestations. You'll need cosign and jq installed.
cosign verify \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
--certificate-identity=https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main \
public.ecr.aws/chainguard/node | jqEach image ships with three attestation types:
| Attestation | Description |
|---|---|
https://slsa.dev/provenance/v1 |
How and where the image was built (SLSA 1.2) |
https://apko.dev/image-configuration |
Exact build config — dependencies, users, entrypoint |
https://spdx.dev/Document |
Full SBOM — every package inside the image |
cosign download attestation \
--platform=linux/amd64 \
--predicate-type=https://spdx.dev/Document \
public.ecr.aws/chainguard/node | jq -r .payload | base64 -d | jq .predicateSwap --predicate-type to pull a different attestation. Use https://apko.dev/image-configuration for Image Configuration and https://slsa.dev/provenance/v1 for Provenance.
cosign verify-attestation \
--type https://spdx.dev/Document \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
--certificate-identity=https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main \
public.ecr.aws/chainguard/node | jqA successful result confirms:
- Claims were validated
- Existence verified in the transparency log
- Certificate verified against a trusted certificate authority
| Resource | URL |
|---|---|
| Chainguard Image Directory | https://images.chainguard.dev/directory |
| node provenance | https://images.chainguard.dev/directory/image/node/provenance |
| node specifications | https://images.chainguard.dev/directory/image/node/specifications |
