Showing preview only (445K chars total). Download the full file or copy to clipboard to get everything.
Repository: GoogleContainerTools/distroless
Branch: main
Commit: bab6ba66e024
Files: 204
Total size: 402.4 KB
Directory structure:
gitextract_vcjsnv1p/
├── .bazelignore
├── .bazelrc
├── .bazelversion
├── .cloudbuild/
│ ├── cloudbuild.yaml
│ ├── lifecycle_tag.sh
│ ├── lifecycle_tag.yaml
│ └── release.sh
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── actionable-debian-cve.md
│ │ ├── actionable-non-debian-cve.md
│ │ └── bug_report.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── buildifier.yaml
│ ├── check-ldconfig.yaml
│ ├── ci.bazelrc
│ ├── ci.yaml
│ ├── config-diff.yaml
│ ├── examples.yaml
│ ├── image-check.yaml
│ ├── scorecards-analysis.yml
│ ├── update-deb-package-non-snapshots.yml
│ ├── update-deb-package-snapshots.yml
│ └── update-node-archives.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .prettierignore
├── BUILD
├── CONTRIBUTING.md
├── LICENSE
├── MODULE.bazel
├── PACKAGE_METADATA.md
├── README.md
├── RELEASES.md
├── SECURITY.md
├── SUPPORT_POLICY.md
├── WORKSPACE
├── base/
│ ├── BUILD
│ ├── README.md
│ ├── base.bzl
│ ├── config.bzl
│ ├── test.sh
│ └── testdata/
│ ├── base.yaml
│ └── debug.yaml
├── cc/
│ ├── BUILD
│ ├── README.md
│ ├── cc.bzl
│ └── config.bzl
├── common/
│ ├── BUILD.bazel
│ └── variables.bzl
├── cosign.pub
├── distro.bzl
├── downloader.cfg
├── examples/
│ ├── BUILD
│ ├── cc/
│ │ ├── BUILD
│ │ ├── Dockerfile
│ │ ├── hello.c
│ │ ├── hello_cc.cc
│ │ └── testdata/
│ │ ├── hello_cc_debian12.yaml
│ │ ├── hello_cc_debian13.yaml
│ │ ├── hello_debian12.yaml
│ │ └── hello_debian13.yaml
│ ├── go/
│ │ ├── BUILD
│ │ ├── Dockerfile
│ │ ├── go.mod
│ │ ├── main.go
│ │ └── main_test.go
│ ├── java/
│ │ ├── BUILD
│ │ ├── Dockerfile
│ │ ├── HelloJava.java
│ │ └── testdata/
│ │ ├── hello_nonroot_debian13.yaml
│ │ └── hello_root_debian13.yaml
│ ├── nodejs/
│ │ ├── BUILD
│ │ ├── Dockerfile
│ │ ├── hello.js
│ │ ├── hello_http.js
│ │ ├── node-express/
│ │ │ ├── Dockerfile
│ │ │ ├── hello_express.js
│ │ │ └── package.json
│ │ ├── package.json
│ │ └── testdata/
│ │ └── hello.yaml
│ ├── nonroot/
│ │ ├── BUILD
│ │ └── testdata/
│ │ ├── user.go
│ │ └── user.yaml
│ ├── python3/
│ │ ├── BUILD
│ │ ├── Dockerfile
│ │ └── hello.py
│ ├── python3-requirements/
│ │ ├── Dockerfile
│ │ ├── README.md
│ │ ├── psutil_example.py
│ │ └── requirements.txt
│ ├── rust/
│ │ ├── .dockerignore
│ │ ├── BUILD
│ │ ├── Cargo.toml
│ │ ├── Dockerfile
│ │ └── src/
│ │ └── main.rs
│ └── test.sh
├── experimental/
│ ├── BUILD
│ └── busybox/
│ ├── BUILD
│ └── commands.bzl
├── java/
│ ├── BUILD
│ ├── README.md
│ ├── config.bzl
│ ├── control
│ ├── java.bzl
│ ├── jre_ver.bzl
│ └── testdata/
│ ├── CheckCerts.java
│ ├── CheckEncoding.java
│ ├── CheckLibharfbuzz.java
│ ├── java17_debian13.yaml
│ ├── java17_debug_debian13.yaml
│ ├── java21_debian13.yaml
│ ├── java21_debug_debian13.yaml
│ ├── java25_debian13.yaml
│ ├── java25_debug_debian13.yaml
│ ├── java_base.yaml
│ ├── java_base_debug.yaml
│ ├── java_certs.yaml
│ ├── java_encoding.yaml
│ └── java_libharfbuzz.yaml
├── knife
├── knife.d/
│ ├── update_java_versions.sh
│ └── update_node_archives.js
├── nodejs/
│ ├── BUILD
│ ├── README.md
│ ├── config.bzl
│ ├── control
│ ├── nodejs.bzl
│ └── testdata/
│ ├── check_certificate.js
│ ├── check_certificate.yaml
│ ├── check_headers.yaml
│ ├── check_npm.yaml
│ ├── nodejs20.yaml
│ ├── nodejs22.yaml
│ └── nodejs24.yaml
├── private/
│ ├── extensions/
│ │ ├── BUILD.bazel
│ │ ├── busybox.bzl
│ │ ├── node.bzl
│ │ └── version.bzl
│ ├── oci/
│ │ ├── BUILD.bazel
│ │ ├── cc_image.bzl
│ │ ├── defs.bzl
│ │ ├── digest.bzl
│ │ ├── go_image.bzl
│ │ ├── java_image.bzl
│ │ ├── rust_image.bzl
│ │ ├── sign_and_push.bzl
│ │ └── sign_and_push.sh.tpl
│ ├── pkg/
│ │ ├── BUILD.bazel
│ │ ├── debian_spdx.bzl
│ │ ├── debian_spdx.go
│ │ ├── oci_image_spdx.bzl
│ │ ├── oci_image_spdx.go
│ │ └── test/
│ │ └── oci_image/
│ │ ├── BUILD.bazel
│ │ ├── fat_image_sbom.spdx.json
│ │ ├── image_amd64.spdx.json
│ │ └── image_arm64.spdx.json
│ ├── repos/
│ │ └── deb/
│ │ ├── BUILD.bazel
│ │ ├── bookworm.lock.json
│ │ ├── bookworm.yaml
│ │ ├── bookworm_python.lock.json
│ │ ├── bookworm_python.yaml
│ │ ├── deb.MODULE.bazel
│ │ ├── package.BUILD.tmpl
│ │ ├── trixie.lock.json
│ │ ├── trixie.yaml
│ │ ├── trixie_adoptium.lock.json
│ │ ├── trixie_adoptium.yaml
│ │ ├── trixie_java.lock.json
│ │ ├── trixie_java.yaml
│ │ ├── trixie_python.lock.json
│ │ └── trixie_python.yaml
│ ├── stamp.bash
│ ├── tools/
│ │ ├── diff/
│ │ │ ├── BUILD.bazel
│ │ │ └── diff.bash
│ │ └── lifecycle/
│ │ ├── BUILD.bazel
│ │ ├── defs.bzl
│ │ ├── tag.bzl
│ │ ├── tag.sh
│ │ └── tag.sh.README.md
│ └── util/
│ ├── BUILD
│ ├── deb.bzl
│ ├── extract.bzl
│ ├── java_cacerts.bzl
│ ├── merge_providers.bzl
│ └── tar.bzl
├── python3/
│ ├── BUILD
│ ├── README.md
│ ├── config.bzl
│ ├── ldconfig/
│ │ ├── ld.so.cache.amd64
│ │ ├── ld.so.cache.arm64
│ │ └── ldconfig.sh
│ ├── ldconfig.bzl
│ ├── python.bzl
│ └── testdata/
│ ├── debian12.yaml
│ ├── debian13.yaml
│ └── python3.yaml
└── static/
├── BUILD
├── config.bzl
├── nsswitch.conf
├── static.bzl
└── testdata/
├── check_certs.go
├── debian12.yaml
├── debian13.yaml
├── debug.yaml
└── static.yaml
================================================
FILE CONTENTS
================================================
================================================
FILE: .bazelignore
================================================
================================================
FILE: .bazelrc
================================================
# Bazel settings that apply to this repository.
# Take care to document any settings that you expect users to apply.
# Settings that apply only to CI are in .github/workflows/ci.bazelrc
# Allow DOCKER_HOST env to leak into test actions.
test --test_env=DOCKER_HOST
# Stamp
build:release --workspace_status_command=./private/stamp.bash --stamp
run:release --workspace_status_command=./private/stamp.bash --stamp
test:release --workspace_status_command=./private/stamp.bash --stamp
# Allow external dependencies to be retried. debian snapshot is unreliable and needs retries.
common --experimental_repository_downloader_retries=20
common --experimental_downloader_config=downloader.cfg
common --http_timeout_scaling=2.0
# Enable platform specific options
build --enable_platform_specific_config
# Use a hermetic Java version
build --java_runtime_version=remotejdk_11
# Newer versions jdk creates collisions on /tmp
# See: https://github.com/bazelbuild/bazel/issues/3236
# https://github.com/GoogleContainerTools/rules_distroless/actions/runs/7118944984/job/19382981899?pr=9#step:8:51
common:linux --sandbox_tmpfs_path=/tmp
# Load any settings specific to the current user.
# .bazelrc.user should appear in .gitignore so that settings are not shared with team members
# This needs to be last statement in this
# config, as the user configuration should be able to overwrite flags from this file.
# See https://docs.bazel.build/versions/master/best-practices.html#bazelrc
# (Note that we use .bazelrc.user so the file appears next to .bazelrc in directory listing,
# rather than user.bazelrc as suggested in the Bazel docs)
try-import %workspace%/.bazelrc.user
================================================
FILE: .bazelversion
================================================
7.4.0
================================================
FILE: .cloudbuild/cloudbuild.yaml
================================================
timeout: 10800s # 3 hours
options:
machineType: E2_HIGHCPU_32
logging: CLOUD_LOGGING_ONLY
artifacts:
objects:
location: 'gs://${_ARTIFACTS_GCS_}/logs'
paths: ['.logs/*.log']
steps:
- name: gcr.io/cloud-builders/bazel@sha256:70e96d9faec4bab40a9d8d55d6b86ce2657927d8bca6bdc2dcb21a82b66dbdf7 # 5.4.0
env:
- PROJECT_ID=${PROJECT_ID}
- COMMIT_SHA=${COMMIT_SHA}
- REGISTRY=gcr.io
- REMOTE_CACHE_GCS=${_REMOTE_CACHE_GCS_}
- KEYLESS=keyless@${PROJECT_ID}.iam.gserviceaccount.com
entrypoint: bash
args: [".cloudbuild/release.sh"]
================================================
FILE: .cloudbuild/lifecycle_tag.sh
================================================
#!/usr/bin/env bash
set -o errexit -o nounset -o xtrace -o pipefail
BAZELISK_VERSION="1.27.0"
BAZELISK_SHA256="e1508323f347ad1465a887bc5d2bfb91cffc232d11e8e997b623227c6b32fb76"
GGCR_VERSION="0.20.7"
GGCR_SHA256="8ef3564d264e6b5ca93f7b7f5652704c4dd29d33935aff6947dd5adefd05953e"
# install gcrane
curl -fsSL "https://github.com/google/go-containerregistry/releases/download/v${GGCR_VERSION}/go-containerregistry_Linux_x86_64.tar.gz" -o ggcr.tar.gz
echo "${GGCR_SHA256} ggcr.tar.gz" | sha256sum --check
tar -xzf ggcr.tar.gz gcrane
chmod a+x gcrane
mv gcrane /usr/bin/gcrane # needs to be on path
# we need jq too
apt-get update
apt-get -y install jq
# install bazelisk (TODO: there's probably a better way to do this)
curl -fsSL "https://github.com/bazelbuild/bazelisk/releases/download/v${BAZELISK_VERSION}/bazelisk-linux-amd64" -o bazelisk
echo "${BAZELISK_SHA256} bazelisk" | sha256sum --check
chmod a+x bazelisk
echo "common --google_default_credentials" >> ~/.bazelrc
echo "common --announce_rc" >> ~/.bazelrc
./bazelisk run :attach_lifecycle_tags --config=release
================================================
FILE: .cloudbuild/lifecycle_tag.yaml
================================================
timeout: 1800s # 30 minutes
options:
machineType: E2_MEDIUM
logging: CLOUD_LOGGING_ONLY
steps:
- name: gcr.io/cloud-builders/bazel@sha256:70e96d9faec4bab40a9d8d55d6b86ce2657927d8bca6bdc2dcb21a82b66dbdf7 # 5.4.0
env:
- PROJECT_ID=${PROJECT_ID}
- REGISTRY=gcr.io
entrypoint: bash
args: [".cloudbuild/lifecycle_tag.sh"]
================================================
FILE: .cloudbuild/release.sh
================================================
#!/usr/bin/env bash
set -o errexit -o xtrace -o pipefail
BAZEL_REMOTE_VERSION="2.6.1"
BAZEL_REMOTE_SHA256="025d53aeb03a7fdd4a0e76262a5ae9eeee9f64d53ca510deff1c84cf3f276784"
BAZELISK_VERSION="1.27.0"
BAZELISK_SHA256="e1508323f347ad1465a887bc5d2bfb91cffc232d11e8e997b623227c6b32fb76"
# setup remote cache
curl -fsSL "https://github.com/buchgr/bazel-remote/releases/download/v${BAZEL_REMOTE_VERSION}/bazel-remote-${BAZEL_REMOTE_VERSION}-linux-amd64" -o bazel-remote
echo "${BAZEL_REMOTE_SHA256} bazel-remote" | sha256sum --check
chmod +x bazel-remote
mkdir .logs
./bazel-remote --max_size 8 --dir ~/.cache/bazel-remote --experimental_remote_asset_api --grpc_address 0.0.0.0:4700 --gcs_proxy.bucket $REMOTE_CACHE_GCS --gcs_proxy.use_default_credentials > .logs/bazel-remote.log 2>&1 &
# install bazelisk (TODO: there's probably a better way to do this)
curl -fsSL "https://github.com/bazelbuild/bazelisk/releases/download/v${BAZELISK_VERSION}/bazelisk-linux-amd64" -o bazelisk
echo "${BAZELISK_SHA256} bazelisk" | sha256sum --check
chmod a+x bazelisk
# setup remote caching and remote asset API.
echo "common --remote_cache=grpc://0.0.0.0:4700" >> ~/.bazelrc
echo "common --experimental_remote_downloader=grpc://0.0.0.0:4700" >> ~/.bazelrc
echo "common --google_default_credentials" >> ~/.bazelrc
echo "common --announce_rc" >> ~/.bazelrc
for i in $(seq 5); do
./bazelisk cquery 'kind(merge_providers, deps(kind(oci_image, ...)))' --output=label --config=release && break || sleep 20;
done
./bazelisk run :sign_and_push --config=release -- --keyless $KEYLESS
================================================
FILE: .github/ISSUE_TEMPLATE/actionable-debian-cve.md
================================================
---
name: Actionable debian CVE
about: A CVE from a debian package where a fix is available
title: ''
labels: ''
assignees: ''
---
- [ ] I have read the [SECURITY.md](https://github.com/GoogleContainerTools/distroless/blob/main/SECURITY.md)
- [ ] I understand that this repo tracks debian package releases and cannot fix debian CVEs on its own
- [ ] this CVE shows a fix is available in the appropriate debian version (bookworm) and channel (main, security) *and* it has been more than 48 hours.
Please describe the image you encountered this with and a link to the debian security tracker
https://security-tracker.debian.org/tracker/CVE-XXXX-YYYYY
================================================
FILE: .github/ISSUE_TEMPLATE/actionable-non-debian-cve.md
================================================
---
name: Actionable non debian CVE
about: A CVE is an package imported into distroless that is not from debian
title: ''
labels: ''
assignees: ''
---
**CVE disclosure**
A link to a public CVE disclosure
**Name of image**
The distroless image you are using
**Link to updated package**
A link to an available update for this package
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: There is an issue with how distroless is built
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
**Expected behavior**
A clear and concise description of what you expected to happen.
**Console Output**
If applicable, add information from your container run
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "gomod"
directory: "debian-package-manager/"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
================================================
FILE: .github/workflows/buildifier.yaml
================================================
name: Buildifier
on:
pull_request:
branches: ["main"]
permissions:
contents: read
jobs:
autoformat:
name: Auto-format and Check
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.25"
- name: Check out code
uses: actions/checkout@v6
- name: Install Dependencies
run: |
go install github.com/bazelbuild/buildtools/buildifier@3.2.0
- name: Run buildifier
run: |
./knife lint --check
================================================
FILE: .github/workflows/check-ldconfig.yaml
================================================
name: Check ldconfig cache
on:
pull_request:
branches: ["main"]
push:
branches: ["main"]
permissions:
contents: read
jobs:
check-ldconfig:
name: Check ldconfig cache up to date
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Check ldconfig caches
run: bazel test //python3:check_ldconfig_tests
================================================
FILE: .github/workflows/ci.bazelrc
================================================
build --announce_rc
build --repository_cache=~/.cache/bazel-repo
# intentionally disable build cache, it gets too big
# build --disk_cache=~/.cache/bazel
common --curses=no
test --test_output=errors
================================================
FILE: .github/workflows/ci.yaml
================================================
name: CI
on:
workflow_dispatch: # Allow manual runs.
pull_request:
branches: ["main"]
push:
branches: ["main"]
permissions:
contents: read
jobs:
lockfile:
name: Check lockfile up to date
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Check lockfile
run: bazel mod deps --lockfile_mode=error
ci-build-test:
name: CI build and unit test
runs-on: distroless-ci-large-ubuntu-22.04
steps:
- uses: actions/checkout@v6
- name: Mount bazel caches
uses: actions/cache@v4
with:
path: |
~/.cache/bazel-repo
key: bazel-cache-deps-ci1-${{ github.sha }}
restore-keys: |
bazel-cache-deps-ci1-${{ github.sha }}
bazel-cache-deps-ci1-
- name: Fetch # this can take a long time if there are a lot of errors
run: |
for i in $(seq 10); do
bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc fetch //... && break || sleep 180;
done
- name: Build All Images
run: |
set -e
targets=$(bazel query 'kind(oci_image, deps(:sign_and_push))')
bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc build $targets
- name: Unit Tests
run: bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc test //... --build_tests_only
- name: Build Examples
run: bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc build //examples/...
ci-images:
name: CI image tests
runs-on: distroless-ci-large-ubuntu-22.04
steps:
- uses: actions/checkout@v6
- name: Mount bazel caches
uses: actions/cache@v4
with:
path: |
~/.cache/bazel-repo
key: bazel-cache-deps-ci2-${{ github.sha }}
restore-keys: |
bazel-cache-deps-ci2-${{ github.sha }}
bazel-cache-deps-ci2-
- name: Fetch
run: |
for i in $(seq 20); do
bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc cquery 'attr(tags, "amd64", ...)' && break
sleep 10;
done
- name: Image Tests
run: bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc test $(bazel query 'attr(tags, "amd64", ...)')
================================================
FILE: .github/workflows/config-diff.yaml
================================================
name: Config Check
on:
workflow_dispatch:
pull_request:
branches: ["main"]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
pull-requests: write
jobs:
diff:
runs-on: ubuntu-latest
steps:
- name: Checkout PR Branch
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
path: pr_branch
- name: Build :sign_and_push.query for PR
run: |
cd pr_branch
bazel build :sign_and_push.query
cp bazel-bin/sign_and_push_query ../pr_query_output.txt
cd ..
- name: Checkout main Branch
uses: actions/checkout@v6
with:
ref: main
path: main_branch
- name: Build :sign_and_push.query for main
run: |
cd main_branch
bazel build :sign_and_push.query
cp bazel-bin/sign_and_push_query ../main_query_output.txt
cd ..
- name: Diff the query outputs
id: diff
run: |
# diff may exit with non-zero
DIFF_OUTPUT=$(diff -u <(sort main_query_output.txt) <(sort pr_query_output.txt)) || true
if [ "$DIFF_OUTPUT" ]; then
echo "$DIFF_OUTPUT"
echo "changed_build<<EOF" >> $GITHUB_OUTPUT
echo "$DIFF_OUTPUT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
fi
- uses: peter-evans/find-comment@v4
id: fc
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: "github-actions[bot]"
body-includes: 🌳 🔧 Config Check
- name: Report diff
if: ${{ !github.event.pull_request.head.repo.fork && steps.diff.outputs.changed_build }}
uses: peter-evans/create-or-update-comment@v5
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
🌳 🔧 Config Check
This pull request has modified the root BUILD
```diff
${{steps.diff.outputs.changed_build}}
```
edit-mode: replace
- name: Report no diff
if: ${{ !github.event.pull_request.head.repo.fork && !steps.diff.outputs.changed_build }}
uses: peter-evans/create-or-update-comment@v5
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
🌳 🔧 Config Check
This pull request has not modified the root BUILD
edit-mode: replace
================================================
FILE: .github/workflows/examples.yaml
================================================
name: Examples
on:
pull_request:
branches: [ 'main' ]
permissions:
contents: read
jobs:
examples:
name: Build and run examples
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: ./examples/test.sh
================================================
FILE: .github/workflows/image-check.yaml
================================================
name: Image Check
on:
workflow_dispatch:
pull_request:
branches: ["main"]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
pull-requests: write
jobs:
diff:
runs-on: distroless-ci-large-ubuntu-22.04
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.25"
- uses: actions/cache@v4
with:
path: |
~/.cache/bazel-repo
key: bazel-cache-deps-ci2-${{ github.sha }}
restore-keys: |
bazel-cache-deps-ci2-${{ github.sha }}
bazel-cache-deps-ci2-
- name: Fetch
run: |
for i in $(seq 10); do
bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc cquery 'attr(tags, "amd64", ...)' && break
sleep 10;
done
- name: Build all images
run: bazel build //:sign_and_push
- name: Install Deps
run: |
go install github.com/google/go-containerregistry/cmd/crane@main
go install github.com/reproducible-containers/diffoci/cmd/diffoci@master
go install filippo.io/mkcert@master
sudo curl -fsSL "https://github.com/project-zot/zot/releases/download/v2.0.2-rc2/zot-linux-amd64-minimal" > /usr/local/bin/zot
sudo chmod +x /usr/local/bin/zot
- name: Diff All Images
id: diff
env:
HEAD_REF: ${{ github.head_ref }}
BASE_REF: ${{ github.event.pull_request.base.ref }}
run: |
./private/tools/diff/diff.bash \
--query-bazel --registry-spawn-https \
--head-ref "$HEAD_REF" \
--base-ref "$BASE_REF" \
--set-github-output-on-diff \
--skip-image-index \
--jobs $(($(nproc --all) * 2)) \
--logs ./verbose.log \
--report ./report.log
- uses: actions/upload-artifact@v7.0.0
id: report
with:
name: "Report"
path: |
./verbose.log
./report.log
- name: Write diff to job output for forks
if: ${{ github.event.pull_request.head.repo.fork && steps.diff.outputs.changed_targets }}
env:
CHANGED: ${{ steps.diff.outputs.changed_targets }}
run: |
echo "This pull request has modified the following images:"
echo "$CHANGED"
- uses: peter-evans/find-comment@v4
id: fc
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: "github-actions[bot]"
body-includes: 🌳 🔄 Image Check
- name: Report diff
if: ${{ !github.event.pull_request.head.repo.fork && steps.diff.outputs.changed_targets }}
uses: peter-evans/create-or-update-comment@v5
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
🌳 🔄 Image Check
This pull request has modified the following images:
```starlark
${{steps.diff.outputs.changed_targets}}
```
You can check the details in the report [here](${{steps.report.outputs.artifact-url}})
edit-mode: replace
- name: Report no diff
if: ${{ !github.event.pull_request.head.repo.fork && !steps.diff.outputs.changed_targets }}
uses: peter-evans/create-or-update-comment@v5
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
🌳 🔄 Image Check
This pull request doesn't make any changes to the images. 👍
You can check the details in the report [here](${{steps.report.outputs.artifact-url}})
edit-mode: replace
================================================
FILE: .github/workflows/scorecards-analysis.yml
================================================
name: Scorecards supply-chain security
on:
# Only the default branch is supported.
branch_protection_rule:
schedule:
- cron: '38 10 * * 1'
push:
branches: [ main ]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecards analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
id-token: write
steps:
- name: "Checkout code"
uses: actions/checkout@v6
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
# Read-only PAT token.
repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Publish the results to enable scorecard badges. For more details, see
# https://github.com/ossf/scorecard-action#publishing-results.
publish_results: true
# Upload the results as artifacts (optional).
- name: "Upload artifact"
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with:
sarif_file: results.sarif
================================================
FILE: .github/workflows/update-deb-package-non-snapshots.yml
================================================
name: update-non-snapshots
on:
# will send emails to last editor of this cron syntax (distroless-bot)
schedule:
- cron: "35 20 * * *"
# allow this workflow to be manually run
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v6
- name: Update non-snapshots
run: ./knife update-non-snapshots
- name: Check for changes
run: |
git status
if [[ $(git status --porcelain) ]]; then
echo "DISTROLESS_DIFF=true" >> "$GITHUB_ENV"
else
echo "No changes detected"
exit 0
fi
- name: Run update lockfile
if: env.DISTROLESS_DIFF
run: bazel mod deps --lockfile_mode=update
- name: Create commits
if: env.DISTROLESS_DIFF
id: create-commits
run: |
git checkout -b update-non-snapshots
# Set identity.
git config --global user.email "distroless-bot@google.com"
git config --global user.name "Distroless Bot"
# Commit changes
git add .
git commit -s -m "Bumping non-snapshot packages to latest stable versions"
git push --force origin HEAD
- name: Create Pull Request
if: env.DISTROLESS_DIFF
env:
GH_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
run: |
BODY_FILE=$(mktemp)
printf "Bumping non-snapshot packages to latest stable version\n" >> $BODY_FILE
if ! OUTPUT=$(gh pr create -B main -H update-non-snapshots -t "Bumping non-snapshot packages to latest stable versions" --body-file "$BODY_FILE" 2>&1) ; then
echo $OUTPUT
if [[ "${OUTPUT}" =~ "already exists" ]]; then
echo "PR already exists and it was updated. Ending successfully";
exit 0;
else
exit 1;
fi
fi
================================================
FILE: .github/workflows/update-deb-package-snapshots.yml
================================================
name: update-snapshots
on:
# will send emails to last editor of this cron syntax (distroless-bot)
schedule:
- cron: "35 20 * * *"
# allow this workflow to be manually run
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: "1.25"
- name: Update snapshots
run: ./knife github-update-snapshots
- name: Set up QEMU
if: env.DISTROLESS_DIFF
uses: docker/setup-qemu-action@v4
- name: Update ldconfig
if: env.DISTROLESS_DIFF
run: bazel run //python3:update_ldconfig
- name: Run update sboms script
if: env.DISTROLESS_DIFF
run: |
for i in $(seq 5); do
bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc fetch //private/pkg/test/oci_image:test_sboms && break || sleep 20;
done
bazel run @//private/pkg/test/oci_image:test_sboms
- name: Run update lockfile
if: env.DISTROLESS_DIFF
run: bazel mod deps --lockfile_mode=update
- name: Create commits
if: env.DISTROLESS_DIFF
id: create-commits
run: |
git checkout -b update-snapshots
# Set identity.
git config --global user.email "distroless-bot@google.com"
git config --global user.name "Distroless Bot"
# Commit changes
git add .
git commit -s -m "Bumping packages to latest stable versions"
git push --force origin HEAD
- name: Create Pull Request
if: env.DISTROLESS_DIFF
env:
GH_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
run: |
BODY_FILE=$(mktemp)
printf "Bumping packages to latest stable version\n\`\`\`diff\n$DISTROLESS_DIFF\n\`\`\`\n" >> $BODY_FILE
if ! OUTPUT=$(gh pr create -B main -H update-snapshots -t "Bumping packages to latest stable versions" --body-file "$BODY_FILE" 2>&1) ; then
echo $OUTPUT
if [[ "${OUTPUT}" =~ "already exists" ]]; then
echo "PR already exists and it was updated. Ending successfully";
exit 0;
else
exit 1;
fi
fi
================================================
FILE: .github/workflows/update-node-archives.yml
================================================
name: update-node-packages
on:
# will send emails to last editor of this cron syntax (distroless-bot)
schedule:
- cron: "30 20 * * *"
# allow this workflow to be manually run
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- name: Update node archives
run: ./knife update-node-archives
- name: Check for changes
run: |
git status
if [[ $(git status --porcelain) ]]; then
echo "DISTROLESS_DIFF=true" >> "$GITHUB_ENV"
else
echo "No changes detected"
exit 0
fi
- name: Run update lockfile
if: env.DISTROLESS_DIFF
run: bazel mod deps --lockfile_mode=update
- name: Create commits
if: env.DISTROLESS_DIFF
run: |
git checkout -b update-node-archives
# Set identity.
git config --global user.email "distroless-bot@google.com"
git config --global user.name "Distroless Bot"
# Commit changes
git add .
git commit -s -m "Bumping node archives to latest stable versions"
git push --force origin HEAD
- name: Create Pull Request
if: env.DISTROLESS_DIFF
env:
GH_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
run: |
BODY_FILE=$(mktemp)
if ! OUTPUT=$(gh pr create -B main -H update-node-archives --fill 2>&1) ; then
echo $OUTPUT
if [[ "${OUTPUT}" =~ "already exists" ]]; then
echo "PR already exists and it was updated. Ending successfully";
exit 0;
else
exit 1;
fi
fi
================================================
FILE: .gitignore
================================================
# Ignore backup files.
*~
# Ignore Vim swap files.
.*.swp
# Ignore files generated by IDEs.
/.classpath
/.factorypath
.idea/
*.iml
/.project
/.settings
/bazel.iml
# Ignore all bazel-* symlinks. There is no full list since this can change
# based on the name of the directory bazel is cloned into.
/bazel-*
# Ignore outputs generated during Bazel bootstrapping.
/output/
# ignore user bazelrc
.bazelrc.user
*.log
================================================
FILE: .pre-commit-config.yaml
================================================
# See CONTRIBUTING.md for instructions.
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
# Commitizen runs in commit-msg stage
# but we don't want to run the other hooks on commit messages
default_stages: [commit]
# Use a slightly older version of node by default
# as the default uses a very new version of GLIBC
default_language_version:
node: 16.18.0
repos:
# Check formatting and lint for starlark code
- repo: https://github.com/keith/pre-commit-buildifier
rev: 6.1.0.1
hooks:
- id: buildifier
- id: buildifier-lint
# Enforce that commit messages allow for later changelog generation
- repo: https://github.com/commitizen-tools/commitizen
rev: v2.18.0
hooks:
# Requires that commitizen is already installed
- id: commitizen
stages: [commit-msg]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: "v2.4.0"
hooks:
- id: prettier
================================================
FILE: .prettierignore
================================================
private/pkg/test/oci_image/*
================================================
FILE: BUILD
================================================
load("//base:config.bzl", "BASE_ARCHITECTURES", "BASE_DISTROS")
load("//cc:config.bzl", "CC_ARCHITECTURES", "CC_DISTROS")
load("//java:config.bzl", "JAVA_ARCHITECTURES", "JAVA_DISTROS", "JAVA_MAJOR_VERSIONS")
load("//nodejs:config.bzl", "NODEJS_ARCHITECTURES", "NODEJS_DISTROS", "NODEJS_MAJOR_VERSIONS")
load("//private/oci:defs.bzl", "sign_and_push_all")
load("//private/tools/lifecycle:defs.bzl", "attach_lifecycle_tags")
load("//python3:config.bzl", "PYTHON_ARCHITECTURES", "PYTHON_DISTROS")
load("//static:config.bzl", "STATIC_ARCHITECTURES", "STATIC_DISTROS")
package(default_visibility = ["//visibility:public"])
DEFAULT_DISTRO = "debian13"
VARIANTS = [
("latest", "", "root"),
("nonroot", "", "nonroot"),
("debug", "_debug", "root"),
("debug-nonroot", "_debug", "nonroot"),
]
###############
# STATIC #
###############
STATIC = {
"{REGISTRY}/{PROJECT_ID}/static:" + tag_base + "-" + arch: "//static:static" + debug_mode + "_" + user + "_" + arch + "_" + DEFAULT_DISTRO
for arch in STATIC_ARCHITECTURES[DEFAULT_DISTRO]
for (tag_base, debug_mode, user) in VARIANTS
}
# oci_image_index
STATIC |= {
"{REGISTRY}/{PROJECT_ID}/static:" + tag_base: "//static:static" + debug_mode + "_" + user + "_" + DEFAULT_DISTRO
for (tag_base, debug_mode, user) in VARIANTS
}
STATIC |= {
"{REGISTRY}/{PROJECT_ID}/static-" + distro + ":" + tag_base + "-" + arch: "//static:static" + debug_mode + "_" + user + "_" + arch + "_" + distro
for (tag_base, debug_mode, user) in VARIANTS
for distro in STATIC_DISTROS
for arch in STATIC_ARCHITECTURES[distro]
}
# oci_image_index
STATIC |= {
"{REGISTRY}/{PROJECT_ID}/static-" + distro + ":" + tag_base: "//static:static" + debug_mode + "_" + user + "_" + distro
for (tag_base, debug_mode, user) in VARIANTS
for distro in STATIC_DISTROS
}
###############
# BASE #
###############
BASE = {
"{REGISTRY}/{PROJECT_ID}/base:" + tag_base + "-" + arch: "//base:base" + debug_mode + "_" + user + "_" + arch + "_" + DEFAULT_DISTRO
for arch in BASE_ARCHITECTURES[DEFAULT_DISTRO]
for (tag_base, debug_mode, user) in VARIANTS
}
# oci_image_index
BASE |= {
"{REGISTRY}/{PROJECT_ID}/base:" + tag_base: "//base:base" + debug_mode + "_" + user + "_" + DEFAULT_DISTRO
for (tag_base, debug_mode, user) in VARIANTS
}
BASE |= {
"{REGISTRY}/{PROJECT_ID}/base-" + distro + ":" + tag_base + "-" + arch: "//base:base" + debug_mode + "_" + user + "_" + arch + "_" + distro
for distro in BASE_DISTROS
for arch in BASE_ARCHITECTURES[distro]
for (tag_base, debug_mode, user) in VARIANTS
}
# oci_image_index
BASE |= {
"{REGISTRY}/{PROJECT_ID}/base-" + distro + ":" + tag_base: "//base:base" + debug_mode + "_" + user + "_" + distro
for (tag_base, debug_mode, user) in VARIANTS
for distro in BASE_DISTROS
}
###############
# BASE_NOSSL #
###############
BASE_NOSSL = {
"{REGISTRY}/{PROJECT_ID}/base-nossl:" + tag_base + "-" + arch: "//base:base_nossl" + debug_mode + "_" + user + "_" + arch + "_" + DEFAULT_DISTRO
for arch in BASE_ARCHITECTURES[DEFAULT_DISTRO]
for (tag_base, debug_mode, user) in VARIANTS
}
# oci_image_index
BASE_NOSSL |= {
"{REGISTRY}/{PROJECT_ID}/base-nossl:" + tag_base: "//base:base_nossl" + debug_mode + "_" + user + "_" + DEFAULT_DISTRO
for (tag_base, debug_mode, user) in VARIANTS
}
BASE_NOSSL |= {
"{REGISTRY}/{PROJECT_ID}/base-nossl-" + distro + ":" + tag_base + "-" + arch: "//base:base_nossl" + debug_mode + "_" + user + "_" + arch + "_" + distro
for (tag_base, debug_mode, user) in VARIANTS
for distro in BASE_DISTROS
for arch in BASE_ARCHITECTURES[distro]
}
# oci_image_index
BASE_NOSSL |= {
"{REGISTRY}/{PROJECT_ID}/base-nossl-" + distro + ":" + tag_base: "//base:base_nossl" + debug_mode + "_" + user + "_" + distro
for (tag_base, debug_mode, user) in VARIANTS
for distro in BASE_DISTROS
}
###############
# CC #
###############
CC = {
"{REGISTRY}/{PROJECT_ID}/cc:" + tag_base + "-" + arch: "//cc:cc" + debug_mode + "_" + user + "_" + arch + "_" + DEFAULT_DISTRO
for arch in CC_ARCHITECTURES[DEFAULT_DISTRO]
for (tag_base, debug_mode, user) in VARIANTS
}
# oci_image_index
CC |= {
"{REGISTRY}/{PROJECT_ID}/cc:" + tag_base: "//cc:cc" + debug_mode + "_" + user + "_" + DEFAULT_DISTRO
for (tag_base, debug_mode, user) in VARIANTS
}
CC |= {
"{REGISTRY}/{PROJECT_ID}/cc-" + distro + ":" + tag_base + "-" + arch: "//cc:cc" + debug_mode + "_" + user + "_" + arch + "_" + distro
for (tag_base, debug_mode, user) in VARIANTS
for distro in CC_DISTROS
for arch in CC_ARCHITECTURES[distro]
}
# oci_image_index
CC |= {
"{REGISTRY}/{PROJECT_ID}/cc-" + distro + ":" + tag_base: "//cc:cc" + debug_mode + "_" + user + "_" + distro
for (tag_base, debug_mode, user) in VARIANTS
for distro in CC_DISTROS
}
###############
# PYTHON 3 #
###############
PYTHON3 = {
"{REGISTRY}/{PROJECT_ID}/python3:" + tag_base + "-" + arch: "//python3:python3" + debug_mode + "_" + user + "_" + arch + "_" + DEFAULT_DISTRO
for arch in PYTHON_ARCHITECTURES[DEFAULT_DISTRO]
for (tag_base, debug_mode, user) in VARIANTS
}
# oci_image_index
PYTHON3 |= {
"{REGISTRY}/{PROJECT_ID}/python3:" + tag_base: "//python3:python3" + debug_mode + "_" + user + "_" + DEFAULT_DISTRO
for (tag_base, debug_mode, user) in VARIANTS
}
PYTHON3 |= {
"{REGISTRY}/{PROJECT_ID}/python3-" + distro + ":" + tag_base + "-" + arch: "//python3:python3" + debug_mode + "_" + user + "_" + arch + "_" + distro
for distro in PYTHON_DISTROS
for arch in PYTHON_ARCHITECTURES[distro]
for (tag_base, debug_mode, user) in VARIANTS
}
# oci_image_index
PYTHON3 |= {
"{REGISTRY}/{PROJECT_ID}/python3-" + distro + ":" + tag_base: "//python3:python3" + debug_mode + "_" + user + "_" + distro
for distro in PYTHON_DISTROS
for (tag_base, debug_mode, user) in VARIANTS
}
###############
# NODEJS #
###############
NODEJS = {
"{REGISTRY}/{PROJECT_ID}/nodejs" + version + "-" + distro + ":" + tag_base + "-" + arch: "//nodejs:nodejs" + version + debug_mode + "_" + user + "_" + arch + "_" + distro
for version in NODEJS_MAJOR_VERSIONS
for distro in NODEJS_DISTROS
for arch in NODEJS_ARCHITECTURES[distro][version]
for (tag_base, debug_mode, user) in VARIANTS
}
# oci_image_index
NODEJS |= {
"{REGISTRY}/{PROJECT_ID}/nodejs" + version + "-" + distro + ":" + tag_base: "//nodejs:nodejs" + version + debug_mode + "_" + user + "_" + distro
for version in NODEJS_MAJOR_VERSIONS
for distro in NODEJS_DISTROS
for (tag_base, debug_mode, user) in VARIANTS
}
NODEJS |= {
"{REGISTRY}/{PROJECT_ID}/nodejs" + version + ":" + tag_base + "-" + arch: "//nodejs:nodejs" + version + debug_mode + "_" + user + "_" + arch + "_" + DEFAULT_DISTRO
for version in NODEJS_MAJOR_VERSIONS
for arch in NODEJS_ARCHITECTURES[DEFAULT_DISTRO][version]
for (tag_base, debug_mode, user) in VARIANTS
}
# oci_image_index
NODEJS |= {
"{REGISTRY}/{PROJECT_ID}/nodejs" + version + ":" + tag_base: "//nodejs:nodejs" + version + debug_mode + "_" + user + "_" + DEFAULT_DISTRO
for version in NODEJS_MAJOR_VERSIONS
for (tag_base, debug_mode, user) in VARIANTS
}
###############
# JAVA_BASE #
###############
JAVA_BASE = {
"{REGISTRY}/{PROJECT_ID}/java-base:" + tag_base + "-" + arch: "//java:java_base" + debug_mode + "_" + user + "_" + arch + "_" + DEFAULT_DISTRO
for arch in JAVA_ARCHITECTURES[DEFAULT_DISTRO]
for (tag_base, debug_mode, user) in VARIANTS
}
JAVA_BASE |= {
"{REGISTRY}/{PROJECT_ID}/java-base:" + tag_base: "//java:java_base" + debug_mode + "_" + user + "_" + DEFAULT_DISTRO
for (tag_base, debug_mode, user) in VARIANTS
}
JAVA_BASE |= {
"{REGISTRY}/{PROJECT_ID}/java-base-" + distro + ":" + tag_base + "-" + arch: "//java:java_base" + debug_mode + "_" + user + "_" + arch + "_" + distro
for distro in JAVA_DISTROS
for arch in JAVA_ARCHITECTURES[distro]
for (tag_base, debug_mode, user) in VARIANTS
}
JAVA_BASE |= {
"{REGISTRY}/{PROJECT_ID}/java-base-" + distro + ":" + tag_base: "//java:java_base" + debug_mode + "_" + user + "_" + distro
for (tag_base, debug_mode, user) in VARIANTS
for distro in JAVA_DISTROS
}
###############
# JAVA #
###############
JAVA = {
"{REGISTRY}/{PROJECT_ID}/java" + version + ":" + tag_base + "-" + arch: "//java:java" + version + debug_mode + "_" + user + "_" + arch + "_" + DEFAULT_DISTRO
for version in JAVA_MAJOR_VERSIONS[DEFAULT_DISTRO]
for arch in JAVA_ARCHITECTURES[DEFAULT_DISTRO]
for (tag_base, debug_mode, user) in VARIANTS
}
# oci_image_index
JAVA |= {
"{REGISTRY}/{PROJECT_ID}/java" + version + ":" + tag_base: "//java:java" + version + debug_mode + "_" + user + "_" + DEFAULT_DISTRO
for version in JAVA_MAJOR_VERSIONS[DEFAULT_DISTRO]
for (tag_base, debug_mode, user) in VARIANTS
}
JAVA |= {
"{REGISTRY}/{PROJECT_ID}/java" + version + "-" + distro + ":" + tag_base + "-" + arch: "//java:java" + version + debug_mode + "_" + user + "_" + arch + "_" + distro
for distro in JAVA_DISTROS
for version in JAVA_MAJOR_VERSIONS[distro]
for arch in JAVA_ARCHITECTURES[distro]
for (tag_base, debug_mode, user) in VARIANTS
}
# oci_image_index
JAVA |= {
"{REGISTRY}/{PROJECT_ID}/java" + version + "-" + distro + ":" + tag_base: "//java:java" + version + debug_mode + "_" + user + "_" + distro
for (tag_base, debug_mode, user) in VARIANTS
for distro in JAVA_DISTROS
for version in JAVA_MAJOR_VERSIONS[distro]
}
ALL = {}
ALL |= STATIC
ALL |= BASE
ALL |= BASE_NOSSL
ALL |= CC
ALL |= PYTHON3
ALL |= NODEJS
ALL |= JAVA_BASE
ALL |= JAVA
# create additional tags by appending COMMIT_SHA to all tags
# remove "latest" if they contain it (this is brittle if we make funky changes):
# - image:latest -> image:{COMMIT_SHA}
# - image:latest-xyz -> image:xyz-{COMMIT_SHA}
COMMIT_SUFFIXED_TAGS = {
(image_ref.replace("latest", "") + "-{COMMIT_SHA}").replace(":-", ":"): build_target
for (image_ref, build_target) in ALL.items()
}
ALL |= COMMIT_SUFFIXED_TAGS
sign_and_push_all(
name = "sign_and_push",
images = ALL,
)
attach_lifecycle_tags(
name = "attach_lifecycle_tags",
images = ALL,
)
================================================
FILE: CONTRIBUTING.md
================================================
# How to become a contributor and submit your own code
## Contributor License Agreements
We'd love to accept your patches! Before we can take them, we have to jump a couple of legal hurdles.
Please fill out either the individual or corporate [Contributor License Agreement (CLA)](https://cla.developers.google.com/about).
- If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA](https://cla.developers.google.com/about/google-individual).
- If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA](https://cla.developers.google.com/about/google-corporate).
Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests.
## How to Build and Test
1. `bazel build //...` to build the whole project or ex:`bazel build //base:static_root_amd64_debian17` for a single image
2. For running tests, check `./knife test`. (`bazel test //...` will NOT run all tests, as many tests are marked "manual".)
3. For building and loading images to your local Docker engine, you need to add a new rule for that image to the BUILD:
```starlark
load("@rules_oci//oci:defs.bzl", "oci_load")
oci_load(
name = "local_build",
image = "//base:static_root_amd64_debian17",
repo_tags = [],
)
```
then run the following command to load into the daemon
```shell
bazel run //:local_build
```
## Adding or removing Debian packages
Whenever a change made to `common/*.yaml` manifests, the locking step has to be performed to regenerate lock files.
This can be done by running; `./knife lock`
## Code style
For styling Bazel files, install and run `buildifier` with:
```shell
# Install buildifier version 3.2.0
go install github.com/bazelbuild/buildtools/buildifier@latest
# This will automatically fix files.
buildifier -mode=fix $(find . -name 'BUILD*' -o -name 'WORKSPACE*' -o -name '*.bzl' -type f)
```
For styling Python files, [install](https://www.pylint.org/#install) and run `pylint` with:
```shell
# Install pylint
sudo pip install pylint
# Or
sudo apt-get install pylint
# Or on macos
brew install pylint
# Identify python style issues.
find . -name "*.py" | xargs pylint --disable=R,C
```
## Contributing a Patch
1. Submit an issue describing your proposed change to the repo in question.
1. The repo owner will respond to your issue promptly.
1. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above).
1. Fork the desired repo, develop and test your code changes.
1. Submit a pull request.
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: MODULE.bazel
================================================
"distroless"
module(name = "distroless")
bazel_dep(name = "bazel_skylib", version = "1.8.2")
bazel_dep(name = "aspect_bazel_lib", version = "2.21.1")
bazel_dep(name = "rules_pkg", version = "1.1.0")
bazel_dep(name = "platforms", version = "1.0.0")
bazel_dep(name = "rules_go", version = "0.57.0")
bazel_dep(name = "gazelle", version = "0.38.0")
bazel_dep(name = "rules_rust", version = "0.63.0")
bazel_dep(name = "container_structure_test", version = "1.19.1")
bazel_dep(name = "rules_oci", version = "2.2.7")
bazel_dep(name = "rules_distroless", version = "0.6.2")
bazel_dep(name = "rules_python", version = "1.5.3")
bazel_dep(name = "rules_cc", version = "0.2.4")
### OCI ###
# Note: rules_oci registers toolchains in its MODULE.bazel
# We import the repos to use crane in sign_and_push rule
oci = use_extension("@rules_oci//oci:extensions.bzl", "oci")
use_repo(oci, "oci_crane_toolchains")
### PYTHON ###
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(
ignore_root_user_error = True,
python_version = "3.11",
)
### GO ####
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.module(
path = "github.com/spdx/tools-golang",
sum = "h1:9B623Cfs+mclYK6dsae7gLSwuIBHvlgmEup87qpqsAQ=",
version = "v0.3.1-0.20230104082527-d6f58551be3f",
)
use_repo(go_deps, "com_github_spdx_tools_golang")
### BUSYBOX ###
busybox = use_extension("//private/extensions:busybox.bzl", "busybox")
busybox.archive()
use_repo(busybox, "busybox_amd64", "busybox_arm", "busybox_arm64", "busybox_ppc64le", "busybox_riscv64", "busybox_s390x")
### JAVA ###
### NODE ###
node = use_extension("//private/extensions:node.bzl", "node")
node.archive()
use_repo(node, "nodejs20_amd64", "nodejs20_arm", "nodejs20_arm64", "nodejs20_ppc64le", "nodejs20_s390x", "nodejs22_amd64", "nodejs22_arm", "nodejs22_arm64", "nodejs22_ppc64le", "nodejs22_s390x", "nodejs24_amd64", "nodejs24_arm64", "nodejs24_ppc64le", "nodejs24_s390x")
### DEBIAN ###
include("//private/repos/deb:deb.MODULE.bazel")
================================================
FILE: PACKAGE_METADATA.md
================================================
# Package Metadata
## `dpkg` Metadata Structure Standard
### Overview
This document defines the standard structure for `dpkg` metadata within Google Distroless Debian-based images. The goal is to provide a clear specification for tool authors and users, enabling consistent and accurate results for vulnerability (CVE) scanning and package analysis.
### Directory Structure
Distroless Debian images differ from traditional Debian images in their handling of `dpkg` metadata. The relevant structure is as follows:
```
/var/lib/dpkg/
└── status.d/
├── <package>
└── <package>.md5sums
```
### Details
- **`/var/lib/dpkg/status.d/`**
- Contains one file per installed package.
- Each file is named after the package (e.g., `libc6`, `libssl1.1`).
- **`/var/lib/dpkg/status.d/<package>`**
- Contains package metadata, equivalent to the output of:
```
dpkg-deb --field <package>.deb > /var/lib/dpkg/status.d/<package>
```
- **`/var/lib/dpkg/status.d/<package>.md5sums`**
- Contains file checksums, equivalent to the output of:
```
dpkg-deb --control <package>.deb CONTROL
cp CONTROL/md5sums /var/lib/dpkg/status.d/<package>.md5sums
rm -rf CONTROL
```
### Omitted Files
Distroless images intentionally omit several files found in standard Debian images:
- `/var/lib/dpkg/status`
- **Not present.** Replaced by the `status.d` directory.
- `/var/lib/dpkg/info/<package>.list`
- **Not present.** File lists can be inferred from the `.md5sums` files.
- `/var/lib/dpkg/info/<package>.md5sums`
- **Not present.** Checksums are stored in `status.d/<package>.md5sums`.
- Other `/var/lib/dpkg/info/<package>.*` files
- **Not present.** Not required for CVE scanning.
### Rationale
- **CVE Scanning Compatibility:**
The provided metadata is sufficient for most vulnerability scanners to identify installed packages and their versions.
- **Avoiding Conflicts:**
Using the `status.d` directory exclusively prevents confusion or potential issues that could arise if files conflicted with default `dpkg` folders. This approach ensures compatibility, especially if `dpkg` is used directly within the image.
### Guidance for Tool Authors
- Scan `/var/lib/dpkg/status.d/` for installed package metadata.
- Use `<package>` files for package details.
- Use `<package>.md5sums` for file checksums and file lists.
- Do not expect `/var/lib/dpkg/status` or `/var/lib/dpkg/info/` files.
## Non-debian packages
TODO
================================================
FILE: README.md
================================================
# "Distroless" Container Images.
[](https://github.com/GoogleContainerTools/distroless/actions/workflows/ci.yaml)
"Distroless" images contain only your application and its runtime dependencies.
They do not contain package managers, shells or any other programs you would expect to find in a standard Linux distribution.
For more information, see this [talk](https://swampup2017.sched.com/event/A6CW/distroless-docker-containerizing-apps-not-vms?iframe=no&w=100%&sidebar=yes&bg=no) ([video](https://www.youtube.com/watch?v=lviLZFciDv4)).
## Why should I use distroless images?
Restricting what's in your runtime container to precisely what's necessary for your app is a best practice employed by Google
and other tech giants that have used containers in production for many years.
It improves the signal to noise of scanners (e.g. CVE) and reduces the burden of establishing provenance to just what you need.
Distroless images are _very small_.
The smallest distroless image, `gcr.io/distroless/static-debian12`, is around 2 MiB.
That's about 50% of the size of `alpine` (~5 MiB), and less than 2% of the size of `debian` (124 MiB).
## How do I use distroless images?
These images are built using [bazel](https://bazel.build), but they can also be used through other Docker image build tooling.
### What images are available?
The following images are currently published and updated by the distroless project (see [SUPPORT_POLICY.md](SUPPORT_POLICY.md) for support timelines)
These images refer to image indexes with references to all supported architectures. Architecture specific images can be directly referenced using an additional architecture suffix on the tag, like `gcr.io/distroless/static-debian12:latest-amd64`
Any other tags are considered deprecated and are no longer updated
#### Debian 12
| Image | Tags | Architecture Suffixes |
| ------------------------------------- | ------------------------------------- | --------------------------------- |
| gcr.io/distroless/static-debian12 | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le |
| gcr.io/distroless/base-debian12 | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le |
| gcr.io/distroless/base-nossl-debian12 | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le |
| gcr.io/distroless/cc-debian12 | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le |
| gcr.io/distroless/python3-debian12 | latest, nonroot, debug, debug-nonroot | amd64, arm64 |
#### Debian 13
Debian 13 distroless images use the debian [UsrMerge](https://wiki.debian.org/UsrMerge) scheme. If you use `rules_distroless` to add packages to an image, set `mergedusr = True` in [`apt.install`](https://registry.bazel.build/docs/rules_distroless#apt_install).
| Image | Tags | Architecture Suffixes |
| ------------------------------------- | ------------------------------------- | ------------------------------------------ |
| gcr.io/distroless/static-debian13 | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le, riscv64 |
| gcr.io/distroless/base-debian13 | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le, riscv64 |
| gcr.io/distroless/base-nossl-debian13 | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le, riscv64 |
| gcr.io/distroless/cc-debian13 | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le, riscv64 |
| gcr.io/distroless/java-base-debian13 | latest, nonroot, debug, debug-nonroot | amd64, arm64, s390x, ppc64le, riscv64 |
| gcr.io/distroless/java17-debian13 | latest, nonroot, debug, debug-nonroot | amd64, arm64, s390x, ppc64le, riscv64 |
| gcr.io/distroless/java21-debian13 | latest, nonroot, debug, debug-nonroot | amd64, arm64, s390x, ppc64le, riscv64 |
| gcr.io/distroless/java25-debian13 | latest, nonroot, debug, debug-nonroot | amd64, arm64, s390x, ppc64le, riscv64 |
| gcr.io/distroless/nodejs20-debian13 | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le |
| gcr.io/distroless/nodejs22-debian13 | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le |
| gcr.io/distroless/nodejs24-debian13 | latest, nonroot, debug, debug-nonroot | amd64, arm64, s390x, ppc64le |
| gcr.io/distroless/python3-debian13 | latest, nonroot, debug, debug-nonroot | amd64, arm64 |
## Why is distroless still using `gcr.io` instead of `pkg.dev`?
Distroless's serving infrastructure has moved to artifact registry but we still use the `gcr.io` domain. Users will get the benefits of the newer infrastructure without changing their builds.
## How do I verify distroless images?
All distroless images are signed by [cosign](https://github.com/sigstore/cosign) with ephemeral keys (keyless). We recommend verifying any distroless image you use before building your image. You can verify the keyless signature of any distroless image with:
```sh
cosign verify $IMAGE_NAME --certificate-oidc-issuer https://accounts.google.com --certificate-identity keyless@distroless.iam.gserviceaccount.com
```
### Entrypoints
Note that distroless images by default do not contain a shell.
That means the Dockerfile `ENTRYPOINT` command, when defined, must be specified in `vector` form, to avoid the container runtime prefixing with a shell.
This works:
```dockerfile
ENTRYPOINT ["myapp"]
```
But this does not work:
```dockerfile
ENTRYPOINT "myapp"
```
For the same reasons, if the entrypoint is set to the empty vector, the CMD command should be specified in `vector` form (see examples below).
Note that by default static, base and cc images have the empty vector entrypoint. Images with an included language runtime have a language specific default (see: [java](java/README.md#usage), [nodejs](nodejs/README.md#usage), [python3](python3/README.md#usage)).
### Docker
Docker multi-stage builds make using distroless images easy.
Follow these steps to get started:
- Pick the right base image for your application stack.
- Write a multi-stage docker file.
Note: This requires Docker 17.05 or higher.
The basic idea is that you'll have one stage to build your application artifacts, and insert them into your runtime distroless image.
If you'd like to learn more, please see the documentation on [multi-stage builds](https://docs.docker.com/engine/userguide/eng-image/multistage-build/).
#### Examples with Docker
Here's a quick example for go:
```dockerfile
# Start by building the application.
FROM golang:1.18 as build
WORKDIR /go/src/app
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 go build -o /go/bin/app
# Now copy it into our base image.
FROM gcr.io/distroless/static-debian12
COPY --from=build /go/bin/app /
CMD ["/app"]
```
You can find other examples here:
- [Java](examples/java/Dockerfile)
- [Python 3](examples/python3/Dockerfile)
- [Go](examples/go/Dockerfile)
- [Node.js](examples/nodejs/Dockerfile)
- [Rust](examples/rust/Dockerfile)
To run any example, go to the directory for the language and run:
```sh
docker build -t myapp .
docker run -t myapp
```
To run the [Node.js Express example app](examples/nodejs/node-express) and expose the container's ports:
```sh
npm install # Install express and its transitive dependencies
docker build -t myexpressapp . # Normal build command
docker run -p 3000:3000 -t myexpressapp
```
This should expose the Express application to your `localhost:3000`
### Bazel
For full documentation on how to use bazel to generate Container images, see the [bazel-contrib/rules_oci](https://github.com/bazel-contrib/rules_oci) repository.
For documentation and example on how to create custom container images, see the [GoogleContainerTools/rules_distroless](https://github.com/GoogleContainerTools/rules_distroless) repository.
Examples can be found in the [GoogleContainerTools/rules_distroless](https://github.com/GoogleContainerTools/rules_distroless/tree/main/examples) repository.
#### Examples with Bazel
We have some examples on how to run some common application stacks in the /examples directory.
See here for:
- [Java](examples/java/BUILD)
- [Python 3](examples/python3/BUILD)
- [Go](examples/go/BUILD)
- [Node.js](examples/nodejs/BUILD)
See here for examples on how to complete some common tasks in your image:
- [Adding and running as a non-root user](examples/nonroot)
- [Including Debian Packages](https://registry.bazel.build/docs/rules_distroless#module_extension-apt)
- [Including CA certificates](https://registry.bazel.build/docs/rules_distroless#rule-cacerts)
See here for more information on how these images are [built and released](RELEASES.md).
### Base Operating System
Distroless images are based on Debian 12 (bookworm). Images are explicitly tagged with Debian version suffixes (e.g. `-debian12`). Specifying an image without the distribution will currently select `-debian12` images, but that will change in the future to a newer version of Debian. It can be useful to reference the distribution explicitly, to prevent breaking builds when the next Debian version is released.
### Operating System Updates for Security Fixes and CVEs
Distroless tracks the upstream Debian releases, using [Github actions to automatically generate a pull request when there are updates](https://github.com/GoogleContainerTools/distroless/blob/main/.github/workflows/update-deb-package-snapshots.yml).
### Debug Images
Distroless images are minimal and lack shell access. The `:debug` image set for each language provides a busybox shell to enter.
For example:
```sh
cd examples/python3/
```
edit the `Dockerfile` to change the final image to `:debug`:
```dockerfile
FROM gcr.io/distroless/python3-debian12:debug
COPY . /app
WORKDIR /app
CMD ["hello.py", "/etc"]
```
then build and launch with a shell entrypoint:
```sh
docker build -t my_debug_image .
```
```sh
$ docker run --entrypoint=sh -ti my_debug_image
/app # ls
BUILD Dockerfile hello.py
```
> Note: If the image you are using already has a tag, for example `gcr.io/distroless/java17-debian13:nonroot`, use the tag `debug-<existing tag>` instead, for example `gcr.io/distroless/java17-debian13:debug-nonroot`.
> Note: [ldd](http://man7.org/linux/man-pages/man1/ldd.1.html) is not installed in the base image as it's a shell script, you can copy it in or download it.
### Who uses Distroless?
- [Kubernetes](https://github.com/kubernetes/enhancements/blob/master/keps/sig-release/1729-rebase-images-to-distroless/README.md), since v1.15
- [Knative](https://knative.dev)
- [Tekton](https://tekton.dev)
- [Teleport](https://goteleport.com)
- [BloodHound](https://github.com/SpecterOps/BloodHound) by [SpecterOps](https://specterops.io/)
If your project uses Distroless, send a PR to add your project here!
## Community Discussion
- [distroless-users Google Group](https://groups.google.com/forum/#!forum/distroless-users)
- [Kubernetes slack #distroless channel](https://slack.k8s.io/)
================================================
FILE: RELEASES.md
================================================
# Release Process
Images in this repository are built and released in the `gcr.io/distroless` repository on the [Google Container Registry](https://cloud.google.com/container-registry/).
Images are automatically built and pushed every commit, according to the policy defined in [cloudbuild.yaml](.cloudbuild/cloudbuild.yaml).
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Supported Versions
Distroless currently tracks debian 12 ([bookworm](https://packages.debian.org/bookworm/allpackages)) and debian 13 ([trixie](https://packages.debian.org/trixie/allpackages)) packages.
Debian package versions used for the current build are found in https://github.com/GoogleContainerTools/distroless/blob/main/private/repos/deb. It can be parsed and printed into simple json data by invoking `./knife deb-versions` at the root of this project.
## Reporting a Vulnerability
If a distroless image you are using contains a CVE or other vulnerability:
1. ensure you are using a [currently supported image](https://github.com/GoogleContainerTools/distroless#what-images-are-available)
1. find the appropriate debian security-tracker notice: `https://security-tracker.debian.org/tracker/CVE-XXXX-YYYYY`, for [example](https://security-tracker.debian.org/tracker/CVE-2022-21476).
1. check if a fix is available for the appropriate debian version in the main/security channels (ex `trixie`, `trixie (security)`).
1. if a fix is not yet available, do not file a bug, track it in your internal tracker until one becomes available.
1. if a fix is available *and* it has been more than 48 hours, please let the team know by creating an issue and pointing to the CVE or vulnerability disclosure.
================================================
FILE: SUPPORT_POLICY.md
================================================
Distroless currently tracks Debian on their [standard support timeline](https://wiki.debian.org/DebianReleases#Production_Releases).
The current estimation of end of life for images with the pattern:
`gcr.io/distroless/<image>-debian<version>:(latest|nonroot|debug|debug-nonroot)`
| Image | Debian 12 EOL | Debian 13 EOL |
| ----------- | -------------- | --------------------------- |
| static | Sept 2026 | debian 14 release day + 1yr |
| base | Sept 2026 | debian 14 release day + 1yr |
| base-nossl | Sept 2026 | debian 14 release day + 1yr |
| cc | Sept 2026 | debian 14 release day + 1yr |
| java* | Jan 2026 | debian 14 release day + 3mo |
| node* | Jan 2026 | debian 14 release day + 3mo |
| python* | April 2026 | debian 14 release day + 3mo |
\* see below for language specific runtime notes
### Java
Java will only support current LTS version distributed by debian [see here](https://wiki.debian.org/Java).
### Node
Node version support is for even numbered releases (20, 22, 24, etc) that are current, active or in LTS maintenance. For more information, [see here](https://nodejs.org/en/about/previous-releases#release-schedule).
### Python (TBD)
### Images no longer supported
A list of supported image tags is available here: https://github.com/GoogleContainerTools/distroless#what-images-are-available
================================================
FILE: WORKSPACE
================================================
load("@rules_oci//cosign:repositories.bzl", "cosign_register_toolchains")
cosign_register_toolchains(name = "oci_cosign")
================================================
FILE: base/BUILD
================================================
load(":base.bzl", "base_image", "base_image_index", "base_nossl_image", "base_nossl_image_index")
load(":config.bzl", "BASE_ARCHITECTURES", "BASE_DISTROS", "BASE_NOSSL_PACKAGES", "BASE_PACKAGES")
package(default_visibility = ["//visibility:public"])
# base nossl
[
base_nossl_image(
arch = arch,
distro = distro,
packages = BASE_NOSSL_PACKAGES[distro],
)
for distro in BASE_DISTROS
for arch in BASE_ARCHITECTURES[distro]
]
[
base_nossl_image_index(
architectures = BASE_ARCHITECTURES[distro],
distro = distro,
)
for distro in BASE_DISTROS
]
# base (with libssl)
[
base_image(
arch = arch,
distro = distro,
packages = BASE_PACKAGES[distro],
)
for distro in BASE_DISTROS
for arch in BASE_ARCHITECTURES[distro]
]
[
base_image_index(
architectures = BASE_ARCHITECTURES[distro],
distro = distro,
)
for distro in BASE_DISTROS
]
================================================
FILE: base/README.md
================================================
# Documentation for `gcr.io/distroless/base`, `gcr.io/distroless/base-nossl` and `gcr.io/distroless/static`
## Image Contents
This image contains a minimal Linux, glibc-based system. It is intended for use directly by "mostly-statically compiled" languages like Go, Rust or D.
Statically compiled applications (Go) that do not require libc can use the `gcr.io/distroless/static` image, which contains:
* ca-certificates
* A /etc/passwd entry for a root user
* A /tmp directory
* tzdata
Applications that require libc but do not need libssl can use the `gcr.io/distroless/base-nossl`, which contains all
of the packages in `gcr.io/distroless/static`, and
* glibc
Most other applications (and Go apps that require libc/cgo) should start with `gcr.io/distroless/base`, which contains all
of the packages in `gcr.io/distroless/static`, and
* glibc
* libssl
* for Debian 13 or later, libssl dependencies (zlib, libzstd) are also included.
Note: Debian 13 base image does not include [OpenSSL legacy algorithms](https://docs.openssl.org/3.5/man7/OSSL_PROVIDER-legacy/#operations-and-algorithms).
If you want to use them, you will need to add `openssl-legacy-provider` yourself.
## Usage
Users are expected to include their compiled application and set the correct cmd in their image.
================================================
FILE: base/base.bzl
================================================
"defines a function to replicate the container images for different distributions"
load("@container_structure_test//:defs.bzl", "container_structure_test")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_image_index")
load("//common:variables.bzl", "DEBUG_MODE", "USERS")
load("//private/util:deb.bzl", "deb")
def base_nossl_image_index(distro, architectures):
"""base nossl image index for a distro
Args:
distro: name of distribution
architectures: all architectures included in index
"""
[
oci_image_index(
name = "base_nossl" + mode + "_" + user + "_" + distro,
images = [
"base_nossl" + mode + "_" + user + "_" + arch + "_" + distro
for arch in architectures
],
)
for user in USERS
for mode in DEBUG_MODE
]
def base_image_index(distro, architectures):
"""base image index for a distro
Args:
distro: name of distribution
architectures: all architectures included in index
"""
[
oci_image_index(
name = "base" + mode + "_" + user + "_" + distro,
images = [
"base" + mode + "_" + user + "_" + arch + "_" + distro
for arch in architectures
],
)
for user in USERS
for mode in DEBUG_MODE
]
def base_nossl_image(distro, arch, packages):
"""base nossl and debug images and tests for a distro/arch
Args:
distro: name of the distribution
arch: the target architecture
"""
[
oci_image(
name = "base_nossl" + mode + "_" + user + "_" + arch + "_" + distro,
base = "//static:static" + mode + "_" + user + "_" + arch + "_" + distro,
tars = [
deb.package(arch, distro, pkg)
for pkg in packages
],
)
for user in USERS
for mode in DEBUG_MODE
]
# Check for common base files.
container_structure_test(
name = "base_nossl_" + arch + "_" + distro + "_test",
configs = ["testdata/base.yaml"],
image = ":base_nossl_root_" + arch + "_" + distro,
tags = ["manual", arch],
)
# Check for busybox
container_structure_test(
name = "base_nossl_debug_" + arch + "_" + distro + "_test",
configs = ["testdata/debug.yaml"],
image = ":base_nossl_debug_root_" + arch + "_" + distro,
tags = ["manual", arch],
)
def base_image(distro, arch, packages):
"""base and debug images and tests for a distro/arch
Args:
distro: name of the distribution
arch: the target architecture
"""
LIBSSL = {
"debian12": "libssl3",
"debian13": "libssl3t64",
}
[
oci_image(
name = "base" + mode + "_" + user + "_" + arch + "_" + distro,
base = "//static:static" + mode + "_" + user + "_" + arch + "_" + distro,
tars = [
deb.package(arch, distro, pkg)
for pkg in packages
],
)
for user in USERS
for mode in DEBUG_MODE
]
# Check for common base files.
container_structure_test(
name = "base_" + arch + "_" + distro + "_test",
configs = ["testdata/base.yaml"],
image = ":base_root_" + arch + "_" + distro,
tags = ["manual", arch],
)
# Check for busybox
container_structure_test(
name = "base_debug_" + arch + "_" + distro + "_test",
configs = ["testdata/debug.yaml"],
image = ":base_debug_root_" + arch + "_" + distro,
tags = ["manual", arch],
)
================================================
FILE: base/config.bzl
================================================
BASE_DISTROS = ["debian12", "debian13"]
BASE_ARCHITECTURES = {
"debian12": ["amd64", "arm64", "arm", "s390x", "ppc64le"],
"debian13": ["amd64", "arm64", "arm", "s390x", "ppc64le", "riscv64"],
}
BASE_PACKAGES = {
"debian12": [
"libc6",
"libssl3",
],
"debian13": [
"libc6",
"libssl3t64",
"libzstd1",
"zlib1g",
],
}
BASE_NOSSL_PACKAGES = {
"debian12": [
"libc6",
],
"debian13": [
"libc6",
],
}
================================================
FILE: base/test.sh
================================================
$RUNFILES_DIR/runtimes_common/structure_tests/ext_run.sh \
-i bazel/base:cc \
-t $RUNFILES_DIR/distroless/base/base.tar \
-c $RUNFILES_DIR/distroless/base/testdata/base.yaml
================================================
FILE: base/testdata/base.yaml
================================================
schemaVersion: "1.0.0"
fileExistenceTests:
# Basic FS sanity checks.
- name: root
path: '/'
shouldExist: true
- name: tmp
path: '/tmp'
shouldExist: true
- name: passwd
path: '/etc/passwd'
shouldExist: true
- name: group
path: '/etc/group'
shouldExist: true
- name: etc-os-release
path: '/etc/os-release'
shouldExist: true
- name: certs
path: '/etc/ssl/certs/ca-certificates.crt'
shouldExist: true
- name: certs_copyright
path: '/usr/share/doc/ca-certificates/copyright'
shouldExist: true
- name: services
path: '/etc/services'
shouldExist: true
- name: tzdata_copyright
path: '/usr/share/doc/tzdata/copyright'
shouldExist: true
- name: tzdata_zoneinfo
path: '/usr/share/zoneinfo'
shouldExist: true
- name: homedir
path: '/root'
shouldExist: true
- name: nonroot-homedir
path: '/home/nonroot'
shouldExist: true
- name: dpkg-status.d
path: '/var/lib/dpkg/status.d/libc6'
shouldExist: true
fileContentTests:
- name: 'known users'
path: '/etc/passwd'
expectedContents: ['^root:x:0:0:root:/root:/sbin/nologin\nnobody:x:65534:65534:nobody:/nonexistent:/sbin/nologin\nnonroot:x:65532:65532:nonroot:/home/nonroot:/sbin/nologin\n$']
- name: 'known groups'
path: '/etc/group'
expectedContents: ['^root:x:0:\nnobody:x:65534:\ntty:x:5:\nstaff:x:50:\nnonroot:x:65532:\n$']
- name: '/usr/lib/os-release pretty name'
path: '/usr/lib/os-release'
expectedContents: ['PRETTY_NAME="Distroless"']
# /etc/os-release is a symlink to /usr/lib/os-release, make sure they match.
- name: '/etc/os-release pretty name'
path: '/etc/os-release'
expectedContents: ['PRETTY_NAME="Distroless"']
================================================
FILE: base/testdata/debug.yaml
================================================
schemaVersion: "1.0.0"
fileExistenceTests:
# Basic FS sanity checks.
- name: busybox
path: '/busybox'
shouldExist: true
commandTests:
- name: busybox
command: ["/busybox/busybox"]
expectedOutput: ['BusyBox v1\.37\.0']
================================================
FILE: cc/BUILD
================================================
load(":cc.bzl", "cc_image", "cc_image_index")
load(":config.bzl", "CC_ARCHITECTURES", "CC_DISTROS", "CC_PACKAGES")
package(default_visibility = ["//visibility:public"])
[
cc_image(
arch = arch,
distro = distro,
packages = CC_PACKAGES[distro],
)
for distro in CC_DISTROS
for arch in CC_ARCHITECTURES[distro]
]
[
cc_image_index(
architectures = CC_ARCHITECTURES[distro],
distro = distro,
)
for distro in CC_DISTROS
]
================================================
FILE: cc/README.md
================================================
# Documentation for `gcr.io/distroless/cc`
## Image Contents
This image contains a minimal Linux, glibc runtime for "mostly-statically compiled" languages like Rust and D.
Specifically, the image contains everything in the [base image](../base/README.md), plus:
* libgcc1 and its dependencies.
## Usage
Users are expected to include their compiled application and set the correct CMD in their image.
================================================
FILE: cc/cc.bzl
================================================
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_image_index")
load("//common:variables.bzl", "DEBUG_MODE", "USERS")
load("//private/util:deb.bzl", "deb")
def cc_image_index(distro, architectures):
"""cc image index for a distro
Args:
distro: name of distribution
architectures: all architectures included in index
"""
[
oci_image_index(
name = "cc" + mode + "_" + user + "_" + distro,
images = [
"cc" + mode + "_" + user + "_" + arch + "_" + distro
for arch in architectures
],
)
for mode in DEBUG_MODE
for user in USERS
]
def cc_image(distro, arch, packages):
"""base nossl and debug images and tests for a distro/arch
Args:
distro: name of the distribution
arch: the target architecture
"""
[
oci_image(
name = "cc" + mode + "_" + user + "_" + arch + "_" + distro,
base = "//base:base" + mode + "_" + user + "_" + arch + "_" + distro,
tars = [
deb.package(arch, distro, pkg)
for pkg in packages
],
)
for mode in DEBUG_MODE
for user in USERS
]
================================================
FILE: cc/config.bzl
================================================
CC_DISTROS = ["debian12", "debian13"]
CC_ARCHITECTURES = {
"debian12": ["amd64", "arm64", "arm", "s390x", "ppc64le"],
"debian13": ["amd64", "arm64", "arm", "s390x", "ppc64le", "riscv64"],
}
CC_PACKAGES = {
"debian12": [
"libgomp1",
"libstdc++6",
"libgcc-s1",
"gcc-12-base",
],
"debian13": [
"libgomp1",
"libstdc++6",
"libgcc-s1",
"gcc-14-base",
],
}
================================================
FILE: common/BUILD.bazel
================================================
load("@aspect_bazel_lib//lib:tar.bzl", "tar")
load("@rules_distroless//distroless:defs.bzl", "cacerts", "group", "home", "locale", "os_release", "passwd")
load("//:distro.bzl", "ALL_DISTROS", "VERSIONS")
load("//private/util:deb.bzl", "deb")
load(":variables.bzl", "NOBODY", "NONROOT", "OS_RELEASE", "ROOT", "quote")
package(default_visibility = ["//visibility:public"])
COMMON_DISTROS = ALL_DISTROS
COMMON_ARCHITECTURES = {
"debian12": [
"amd64",
"arm64",
"arm",
"s390x",
"ppc64le",
],
"debian13": [
"amd64",
"arm64",
"arm",
"s390x",
"ppc64le",
"riscv64",
],
}
tar(
name = "rootfs",
srcs = [],
args = [
"--format",
"gnutar",
],
compress = "gzip",
mtree = ["./ type=dir uid=0 gid=0 mode=0755 time=0.0"],
)
tar(
name = "tmp",
srcs = [],
# original tmp.tar was created on a gnutar, mimic that.
args = [
"--format",
"gnutar",
],
compress = "gzip",
mtree = ["./tmp gname=root uname=root time=0.0 mode=1777 gid=0 uid=0 type=dir"],
)
[
os_release(
name = "os_release_%s" % dist,
content = {
key: quote(value.format(
CODENAME = codename,
VERSION = version,
))
for (key, value) in OS_RELEASE.items()
},
)
for (dist, codename, version) in VERSIONS
]
[
locale(
name = "locale_%s_%s" % (distro, arch),
charset = "C.utf8",
package = deb.data(arch, distro, "libc-bin"),
)
for distro in COMMON_DISTROS
for arch in COMMON_ARCHITECTURES[distro]
]
[
cacerts(
name = "cacerts_%s_%s" % (distro, arch),
package = deb.data(arch, distro, "ca-certificates"),
)
for distro in COMMON_DISTROS
for arch in COMMON_ARCHITECTURES[distro]
]
# create /etc/group with the root, tty, and staff groups
group(
name = "group",
entries = [
{
"name": "root", # root_group
"gid": ROOT,
"password": "x",
},
{
"name": "nobody", # nobody_group
"gid": NOBODY,
"password": "x",
},
{
"name": "tty", # tty_group
"gid": 5,
"password": "x",
},
{
"name": "staff", # staff_group
"gid": 50,
"password": "x",
},
{
"name": "nonroot", # nonroot_group
"gid": NONROOT,
"password": "x",
},
],
)
passwd(
name = "passwd",
entries = [
{
"gecos": ["root"],
"gid": ROOT,
"shell": "/sbin/nologin",
"home": "/root",
"uid": ROOT,
"password": "x",
"username": "root",
},
{
"gecos": ["nobody"],
"gid": NOBODY,
"home": "/nonexistent",
"shell": "/sbin/nologin",
"uid": NOBODY,
"password": "x",
"username": "nobody",
},
{
"gecos": ["nonroot"],
"gid": NONROOT,
"home": "/home/nonroot",
"shell": "/sbin/nologin",
"uid": NONROOT,
"password": "x",
"username": "nonroot",
},
],
)
home(
name = "home",
dirs = [
{
"home": "/root",
"uid": ROOT,
"gid": ROOT,
"mode": 700,
},
{
"home": "/home",
"uid": ROOT,
"gid": ROOT,
"mode": 755,
},
{
"home": "/home/nonroot",
"uid": NONROOT,
"gid": NONROOT,
"mode": 700,
},
],
)
================================================
FILE: common/variables.bzl
================================================
"common variables"
def quote(str):
return '''"{}"'''.format(str)
OS_RELEASE = dict(
PRETTY_NAME = "Distroless",
NAME = "Debian GNU/Linux",
ID = "debian",
VERSION_ID = "{VERSION}",
VERSION = "Debian GNU/Linux {VERSION} ({CODENAME})",
HOME_URL = "https://github.com/GoogleContainerTools/distroless",
SUPPORT_URL = "https://github.com/GoogleContainerTools/distroless/blob/master/README.md",
BUG_REPORT_URL = "https://github.com/GoogleContainerTools/distroless/issues/new",
)
NOBODY = 65534
NONROOT = 65532
ROOT = 0
DEBUG_MODE = ["", "_debug"]
USERS = ["root", "nonroot"]
================================================
FILE: cosign.pub
================================================
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWZzVzkb8A+DbgDpaJId/bOmV8n7Q
OqxYbK0Iro6GzSmOzxkn+N2AKawLyXi84WSwJQBK//psATakCgAQKkNTAA==
-----END PUBLIC KEY-----
================================================
FILE: distro.bzl
================================================
VERSIONS = [
("debian12", "bookworm", "12"),
("debian13", "trixie", "13"),
]
VARIANTS = {
"arm": "v7",
"arm64": "v8",
}
ALL_ARCHITECTURES = ["amd64", "arm64", "arm", "s390x", "ppc64le", "riscv64"]
ALL_DISTROS = ["debian12", "debian13"]
================================================
FILE: downloader.cfg
================================================
rewrite (mirrors\.kernel\.org)/gnu/(.*) https://ftp.gnu.org/gnu/$2
rewrite (mirrors\.kernel\.org)/gnu/(.*) https://ftpmirror.gnu.org/gnu/$2
================================================
FILE: examples/BUILD
================================================
package(default_visibility = ["//visibility:public"])
================================================
FILE: examples/cc/BUILD
================================================
# Public notice: this file is for internal documentation, testing, and
# reference only. Note that repo maintainers can freely change any part of the
# repository code at any time.
load("@container_structure_test//:defs.bzl", "container_structure_test")
load("//cc:config.bzl", DISTROS = "CC_DISTROS")
load("//private/oci:defs.bzl", "cc_image")
package(default_visibility = ["//visibility:public"])
[cc_image(
name = "hello_" + distro,
srcs = ["hello.c"],
base = "//cc:cc_root_amd64_" + distro,
) for distro in DISTROS]
[cc_image(
name = "hello_cc_" + distro,
srcs = ["hello_cc.cc"],
base = "//cc:cc_root_amd64_" + distro,
) for distro in DISTROS]
[container_structure_test(
name = "hello_" + distro + "_test",
size = "small",
configs = ["testdata/hello_" + distro + ".yaml"],
image = ":hello_" + distro,
tags = [
"amd64",
"manual",
],
) for distro in DISTROS]
[container_structure_test(
name = "hello_cc_" + distro + "_test",
size = "small",
configs = ["testdata/hello_cc_" + distro + ".yaml"],
image = ":hello_cc_" + distro,
tags = [
"amd64",
"manual",
],
) for distro in DISTROS]
================================================
FILE: examples/cc/Dockerfile
================================================
FROM gcc:6 AS build-env
COPY . /app
WORKDIR /app
RUN cc hello.c -o hello
FROM gcr.io/distroless/cc
COPY --from=build-env /app /app
WORKDIR /app
CMD ["./hello"]
================================================
FILE: examples/cc/hello.c
================================================
#include <stdio.h>
int main() {
printf("Hello from distroless C!\n");
return 0;
}
================================================
FILE: examples/cc/hello_cc.cc
================================================
#include <iostream>
int main() {
std::cout << "Hello from distroless C++!" << std::endl;
return 0;
}
================================================
FILE: examples/cc/testdata/hello_cc_debian12.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
- name: hello_cc
command: ['/hello_cc_debian12_binary']
expectedOutput: ['Hello from distroless C\+\+!']
================================================
FILE: examples/cc/testdata/hello_cc_debian13.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
- name: hello_cc
command: ['/hello_cc_debian13_binary']
expectedOutput: ['Hello from distroless C\+\+!']
================================================
FILE: examples/cc/testdata/hello_debian12.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
- name: hello
command: ['/hello_debian12_binary']
expectedOutput: ['Hello from distroless C!']
================================================
FILE: examples/cc/testdata/hello_debian13.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
- name: hello
command: ['/hello_debian13_binary']
expectedOutput: ['Hello from distroless C!']
================================================
FILE: examples/go/BUILD
================================================
# Public notice: this file is for internal documentation, testing, and
# reference only. Note that repo maintainers can freely change any part of the
# repository code at any time.
load("@rules_oci//oci:defs.bzl", "oci_load")
load("//private/oci:defs.bzl", "go_image")
package(default_visibility = ["//visibility:public"])
go_image(
name = "go_example",
srcs = ["main.go"],
base = "//base:base_root_amd64_debian12",
)
# Run
# bazel run //examples/go:tarball
# podman run localhost/distroless/examples/go:latest
oci_load(
name = "tarball",
image = ":go_example",
repo_tags = ["distroless/examples/go:latest"],
)
================================================
FILE: examples/go/Dockerfile
================================================
FROM golang:1.22 as build
WORKDIR /go/src/app
COPY . .
RUN go mod download
RUN go vet -v
RUN go test -v
RUN CGO_ENABLED=0 go build -o /go/bin/app
FROM gcr.io/distroless/static-debian12
COPY --from=build /go/bin/app /
CMD ["/app"]
================================================
FILE: examples/go/go.mod
================================================
module github.com/GoogleContainerTools/distroless/examples/go
go 1.18
================================================
FILE: examples/go/main.go
================================================
// Copyright 2017 Google Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import "fmt"
func main() {
fmt.Println("Hello, world!")
}
================================================
FILE: examples/go/main_test.go
================================================
package main
import (
"testing"
)
func Test1(t *testing.T) {
}
================================================
FILE: examples/java/BUILD
================================================
# Public notice: this file is for internal documentation, testing, and
# reference only. Note that repo maintainers can freely change any part of the
# repository code at any time.
load("@container_structure_test//:defs.bzl", "container_structure_test")
load("//private/oci:defs.bzl", "java_image")
package(default_visibility = ["//visibility:public"])
JAVA_VERSIONS_PER_DISTRO = [
("17", "debian13"),
]
[
java_image(
name = "hello_java" + java_version + "_" + user + "_" + distro,
srcs = ["HelloJava.java"],
base = "//java:java" + java_version + "_" + user + "_amd64" + "_" + distro,
main_class = "examples.HelloJava",
)
for user in [
"root",
"nonroot",
]
for java_version, distro in JAVA_VERSIONS_PER_DISTRO
]
[
container_structure_test(
name = "hello_java" + java_version + "_" + user + "_" + distro + "_test",
size = "small",
configs = ["testdata/hello_" + user + "_" + distro + ".yaml"],
image = ":hello_java" + java_version + "_" + user + "_" + distro,
tags = [
"amd64",
"manual",
],
)
for user in [
"root",
"nonroot",
]
for java_version, distro in JAVA_VERSIONS_PER_DISTRO
]
================================================
FILE: examples/java/Dockerfile
================================================
FROM eclipse-temurin:17-jdk AS build-env
COPY . /app/examples
WORKDIR /app
RUN javac examples/*.java
RUN jar cfe main.jar examples.HelloJava examples/*.class
FROM gcr.io/distroless/java17-debian13
COPY --from=build-env /app /app
WORKDIR /app
CMD ["main.jar"]
================================================
FILE: examples/java/HelloJava.java
================================================
package examples;
public class HelloJava {
public static void main(String[] args) {
System.out.println("Hello world");
}
}
================================================
FILE: examples/java/testdata/hello_nonroot_debian13.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
- name: hello
command: ['/usr/bin/java', '-cp', '/*', 'examples.HelloJava']
expectedOutput: ['Hello world']
================================================
FILE: examples/java/testdata/hello_root_debian13.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
- name: hello
command: ['/usr/bin/java', '-cp', '/*', 'examples.HelloJava']
expectedOutput: ['Hello world']
================================================
FILE: examples/nodejs/BUILD
================================================
# Public notice: this file is for internal documentation, testing, and
# reference only. Note that repo maintainers can freely change any part of the
# repository code at any time.
load("@container_structure_test//:defs.bzl", "container_structure_test")
load("@rules_oci//oci:defs.bzl", "oci_image")
load("//private/util:tar.bzl", "tar")
package(default_visibility = ["//visibility:public"])
ARCHITECTURES = [
"amd64",
"arm64",
]
DISTROS = [
"debian13",
]
# These examples are adapted from:
# https://howtonode.org/hello-node
tar(
name = "hello_tar",
srcs = [
"hello.js",
],
)
tar(
name = "hello_http_tar",
srcs = [
"hello_http.js",
],
)
[
oci_image(
name = "hello_" + user + "_" + arch + "_" + distro,
base = "//nodejs:nodejs22_" + user + "_" + arch + "_" + distro,
cmd = ["hello.js"],
tars = [":hello_tar"],
)
for user in [
"root",
"nonroot",
]
for arch in ARCHITECTURES
for distro in DISTROS
]
[
oci_image(
name = "hello_http_" + user + "_" + arch + "_" + distro,
base = "//nodejs:nodejs22_" + user + "_" + arch + "_" + distro,
cmd = ["hello_http.js"],
tars = [":hello_http_tar"],
)
for user in [
"root",
"nonroot",
]
for arch in ARCHITECTURES
for distro in DISTROS
]
[
container_structure_test(
name = "hello_" + user + "_" + arch + "_" + distro + "_test",
configs = ["testdata/hello.yaml"],
image = ":hello_" + user + "_" + arch + "_" + distro,
tags = [
arch,
"manual",
],
)
for user in [
"root",
"nonroot",
]
for arch in ARCHITECTURES
for distro in DISTROS
]
================================================
FILE: examples/nodejs/Dockerfile
================================================
FROM node:22 AS build-env
COPY . /app
WORKDIR /app
RUN npm ci --omit=dev
FROM gcr.io/distroless/nodejs22-debian13
COPY --from=build-env /app /app
WORKDIR /app
CMD ["hello.js"]
================================================
FILE: examples/nodejs/hello.js
================================================
console.log("Hello World");
================================================
FILE: examples/nodejs/hello_http.js
================================================
// Load the http module to create an http server.
var http = require('http');
// Configure our HTTP server to respond with Hello World to all requests.
var server = http.createServer(function (request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.end("Hello World\n");
});
// Listen on port 8000, IP defaults to 127.0.0.1
server.listen(8000);
// Put a friendly message on the terminal
console.log("Server running at http://127.0.0.1:8000/");
================================================
FILE: examples/nodejs/node-express/Dockerfile
================================================
FROM node:22 AS build-env
ADD . /app
WORKDIR /app
RUN npm install --omit=dev
FROM gcr.io/distroless/nodejs22-debian13
COPY --from=build-env /app /app
WORKDIR /app
EXPOSE 3000
CMD ["hello_express.js"]
================================================
FILE: examples/nodejs/node-express/hello_express.js
================================================
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => res.send('Hello World!'))
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
================================================
FILE: examples/nodejs/node-express/package.json
================================================
{
"name": "distroless-express",
"version": "1.0.0",
"description": "Distroless express node.js",
"repository": {
"type": "git",
"url": "https://github.com/GoogleContainerTools/distroless.git"
},
"dependencies": {
"express": "4.20.0"
},
"author": "Bryant Hagadorn",
"license": "ISC"
}
================================================
FILE: examples/nodejs/package.json
================================================
{
"name": "nodejs",
"version": "1.0.0",
"description": "Example",
"main": "hello.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
================================================
FILE: examples/nodejs/testdata/hello.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
- name: hello
command: ["/nodejs/bin/node", "/hello.js"]
expectedOutput: ['Hello World']
================================================
FILE: examples/nonroot/BUILD
================================================
# Public notice: this file is for internal documentation, testing, and
# reference only. Note that repo maintainers can freely change any part of the
# repository code at any time.
load("@container_structure_test//:defs.bzl", "container_structure_test")
load("@rules_distroless//distroless:defs.bzl", "home", "passwd")
load("@rules_go//go:def.bzl", "go_binary")
load("@rules_oci//oci:defs.bzl", "oci_image")
load("//:distro.bzl", DISTROS = "ALL_DISTROS")
load("//private/util:tar.bzl", "tar")
# Create a passwd file and home directory with a nonroot user and uid.
passwd(
name = "passwd",
entries = [
{
"gecos": ["nonroot"],
"gid": 1000,
"home": "/home",
"shell": "/bin/bash",
"uid": 1002,
"username": "nonroot",
},
],
)
home(
name = "home",
dirs = [
{
"home": "/home",
"uid": 1002,
"gid": 1000,
},
],
)
# Include it in our image as a tar.
oci_image(
name = "passwd_image",
base = "//base:base_root_amd64_debian12",
tars = [
":passwd",
":home",
],
user = "nonroot",
visibility = ["//visibility:private"],
)
# Simple go program to print out the username and uid.
go_binary(
name = "user",
srcs = ["testdata/user.go"],
goarch = "amd64",
# Test image is linux based
goos = "linux",
pure = "on",
)
tar(
name = "user_tar",
srcs = [":user"],
)
[oci_image(
name = "check_user_image_" + distro,
base = ":passwd_image",
tars = [":user_tar"],
visibility = ["//visibility:private"],
) for distro in DISTROS]
# Test to verify this works :)
[container_structure_test(
name = "check_user_" + distro + "_test",
configs = ["testdata/user.yaml"],
image = ":check_user_image_" + distro,
tags = [
"amd64",
"manual",
],
visibility = ["//visibility:private"],
) for distro in DISTROS]
================================================
FILE: examples/nonroot/testdata/user.go
================================================
// Copyright 2017 Google Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
)
func main() {
// We can't use user.Current here because it requires cgo.
uid := strconv.Itoa(os.Getuid())
passwd, err := ioutil.ReadFile("/etc/passwd")
if err != nil {
fmt.Printf("Error reading /etc/passwd: %v", err)
return
}
for _, line := range strings.Split(string(passwd), "\n") {
if len(line) == 0 {
continue
}
entries := strings.Split(line, ":")
name, id := entries[0], entries[2]
if id == uid {
fmt.Println("User:", name)
fmt.Println("Uid:", id)
return
}
}
}
================================================
FILE: examples/nonroot/testdata/user.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
- name: user
command: ['/user']
expectedOutput: ['User: nonroot', 'Uid: 1002']
================================================
FILE: examples/python3/BUILD
================================================
# Public notice: this file is for internal documentation, testing, and
# reference only. Note that repo maintainers can freely change any part of the
# repository code at any time.
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_load")
load("//private/util:tar.bzl", "tar")
load("//python3:config.bzl", DISTROS = "PYTHON_DISTROS")
tar(
name = "hello_py",
srcs = ["hello.py"],
)
# This example runs a python program that walks the filesystem under "/etc" and prints every filename.
[
oci_image(
name = "hello_" + distro,
base = "//python3:python3_root_amd64_" + distro,
cmd = [
"hello.py",
"/etc",
],
tars = [
":hello_py",
],
)
for distro in DISTROS
]
# Run
# bazel run //examples/python3:tarball_debian12
# podman run localhost/distroless/examples/py:latest
[
oci_load(
name = "tarball_" + distro,
image = ":hello_" + distro,
repo_tags = ["distroless/examples/py:latest"],
)
for distro in DISTROS
]
================================================
FILE: examples/python3/Dockerfile
================================================
FROM python:3-slim AS build-env
COPY . /app
WORKDIR /app
FROM gcr.io/distroless/python3
COPY --from=build-env /app /app
WORKDIR /app
CMD ["hello.py", "/etc"]
================================================
FILE: examples/python3/hello.py
================================================
#!/bin/python
# Copyright 2017 Google Inc. All rights reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import os
parser = argparse.ArgumentParser()
parser.add_argument('root', type=str,
help='The root directory to walk.')
def main(args):
"""Prints the files that are inside the container, rooted at the first argument."""
for dirpath, _, files in os.walk(args.root):
for f in files:
print(os.path.join(dirpath, f))
if __name__ == "__main__":
main(parser.parse_args())
================================================
FILE: examples/python3-requirements/Dockerfile
================================================
# Build a virtualenv using the appropriate Debian release
# * Install python3-venv for the built-in Python3 venv module (not installed by default)
# * Install gcc libpython3-dev to compile C Python modules
# * In the virtualenv: Update pip setuputils and wheel to support building new packages
FROM debian:12-slim AS build
RUN apt-get update && \
apt-get install --no-install-suggests --no-install-recommends --yes python3-venv gcc libpython3-dev && \
python3 -m venv /venv && \
/venv/bin/pip install --upgrade pip setuptools wheel
# Build the virtualenv as a separate step: Only re-execute this step when requirements.txt changes
FROM build AS build-venv
COPY requirements.txt /requirements.txt
RUN /venv/bin/pip install --disable-pip-version-check -r /requirements.txt
# Copy the virtualenv into a distroless image
FROM gcr.io/distroless/python3-debian12
COPY --from=build-venv /venv /venv
COPY . /app
WORKDIR /app
ENTRYPOINT ["/venv/bin/python3", "psutil_example.py"]
================================================
FILE: examples/python3-requirements/README.md
================================================
# Python 3 with requirements.txt
This is a Python 3 application that specifies third-party dependencies using requirements.txt. The
psutil module it uses is a C module that must be compiled. This is the most annoying kind of
dependency, since you need to build it in an environment where the C library and Python version
match where it will run.
It builds the final container in three stages:
1. `build`: Set up a Debian build environment that can compile Python C modules.
2. `build-venv`: Create a virtualenv using `requirements.txt`.
3. Output: Copy the venv and the code and build the final image.
The first step is only re-executed if you edit `Dockerfile`. The second step is only re-executed
if you change requirements.txt. The final step is very fast and will change on every code edit.
## Build and run
1. Build the image: `docker build . --tag=psutil-example`
2. Run it! `docker run --rm psutil-example`
## Example output
```
RSS: 13.0 MiB; SHARED: 5.7 MiB; VIRTUAL: 19.9 MiB
```
## TODO: Bazel
It would be nice if we could show how to build this container with Bazel, but I don't actually know
how to do that.
================================================
FILE: examples/python3-requirements/psutil_example.py
================================================
#!/usr/bin/env python3
# Distroless's test.sh runs pylint on all python files, but this module will not exist
# pylint: disable=import-error
import psutil
def mib(total_bytes):
'''Converts bytes to MiB (float).'''
return total_bytes / 1024 / 1024
def main():
current_process = psutil.Process()
memory = current_process.memory_info()
print(f'RSS: {mib(memory.rss):.1f} MiB; SHARED: {mib(memory.shared):.1f} MiB; VIRTUAL: {mib(memory.vms):.1f} MiB')
if __name__ == '__main__':
main()
================================================
FILE: examples/python3-requirements/requirements.txt
================================================
# this version of psutil does not ship binary wheels: must be compiled
psutil==5.6.6
================================================
FILE: examples/rust/.dockerignore
================================================
.git/
target/
================================================
FILE: examples/rust/BUILD
================================================
# Public notice: this file is for internal documentation, testing, and
# reference only. Note that repo maintainers can freely change any part of the
# repository code at any time.
# Rust
load("//private/oci:defs.bzl", "rust_image")
package(default_visibility = ["//visibility:public"])
# NOTE: Bazel Rust rules don't support cross-compilation yet,
# so at this time it's required that you build on the same platform as the image
rust_image(
name = "rust_example",
srcs = ["src/main.rs"],
base = "//cc:cc_root_amd64_debian12",
tags = [
"amd64",
"manual",
],
)
================================================
FILE: examples/rust/Cargo.toml
================================================
[package]
name = "hello-world-distroless"
version = "0.1.0"
authors = ["Bryant Hagadorn <blhagadorn@gmail.com>"]
[dependencies]
================================================
FILE: examples/rust/Dockerfile
================================================
FROM rust:1 as build-env
WORKDIR /app
COPY . /app
RUN cargo build --release
FROM gcr.io/distroless/cc-debian12
COPY --from=build-env /app/target/release/hello-world-distroless /
CMD ["./hello-world-distroless"]
================================================
FILE: examples/rust/src/main.rs
================================================
fn main() {
println!("Hello World!");
}
================================================
FILE: examples/test.sh
================================================
#!/usr/bin/env bash
set -euo pipefail
for d in examples/*; do
# Skip non-directories.
if [[ ! -d "$d" ]]; then continue; fi
# Skip if the directory doesn't have a Dockerfile.
if [[ ! -f $d/Dockerfile ]]; then continue; fi
# Skip these non-working examples.
if [[ $d == "examples/cc" ]]; then continue; fi
# Build the Dockerfile and run the image, or fail.
echo ====================================
echo = building $d
echo ====================================
docker build -t $d ./$d/
docker run $d
done
================================================
FILE: experimental/BUILD
================================================
================================================
FILE: experimental/busybox/BUILD
================================================
load("//:distro.bzl", BUSYBOX_ARCHITECTURES = "ALL_ARCHITECTURES")
load(":commands.bzl", "busybox_layer")
package(default_visibility = ["//static:__subpackages__"])
[
busybox_layer(
name = "busybox_" + arch,
busybox = "@busybox_" + arch + "//:file",
compress = "gzip",
)
for arch in BUSYBOX_ARCHITECTURES
]
================================================
FILE: experimental/busybox/commands.bzl
================================================
load("@aspect_bazel_lib//lib:tar.bzl", "tar")
BUSYBOX_COMMANDS = [
"[",
"[[",
"acpid",
"add-shell",
"addgroup",
"adduser",
"adjtimex",
"ar",
"arp",
"arping",
"ash",
"awk",
"base64",
"basename",
"blkdiscard",
"blkid",
"blockdev",
"bootchartd",
"brctl",
"bunzip2",
"bzcat",
"bzip2",
"cal",
"cat",
"chat",
"chattr",
"chgrp",
"chmod",
"chown",
"chpasswd",
"chpst",
"chroot",
"chrt",
"chvt",
"cksum",
"clear",
"cmp",
"comm",
"conspy",
"cp",
"cpio",
"crond",
"crontab",
"cryptpw",
"cttyhack",
"cut",
"date",
"dc",
"dd",
"deallocvt",
"delgroup",
"deluser",
"depmod",
"devmem",
"df",
"dhcprelay",
"diff",
"dirname",
"dmesg",
"dnsd",
"dnsdomainname",
"dos2unix",
"dpkg",
"dpkg-deb",
"du",
"dumpkmap",
"dumpleases",
"echo",
"ed",
"egrep",
"eject",
"env",
"envdir",
"envuidgid",
"expand",
"expr",
"factor",
"fakeidentd",
"false",
"fatattr",
"fbset",
"fbsplash",
"fdflush",
"fdformat",
"fdisk",
"fgconsole",
"fgrep",
"find",
"findfs",
"flash_eraseall",
"flash_lock",
"flash_unlock",
"flashcp",
"flock",
"fold",
"free",
"freeramdisk",
"fsck",
"fsck.minix",
"fsfreeze",
"fstrim",
"fsync",
"ftpd",
"ftpget",
"ftpput",
"fuser",
"getopt",
"getty",
"grep",
"groups",
"gunzip",
"gzip",
"halt",
"hd",
"hdparm",
"head",
"hexdump",
"hostid",
"hostname",
"httpd",
"hush",
"hwclock",
"i2cdetect",
"i2cdump",
"i2cget",
"i2cset",
"id",
"ifconfig",
"ifenslave",
"ifplugd",
"inetd",
"init",
"inotifyd",
"insmod",
"install",
"ionice",
"iostat",
"ip",
"ipaddr",
"ipcalc",
"ipcrm",
"ipcs",
"iplink",
"ipneigh",
"iproute",
"iprule",
"iptunnel",
"kbd_mode",
"kill",
"killall",
"killall5",
"klogd",
"last",
"less",
"link",
"linux32",
"linux64",
"linuxrc",
"ln",
"loadfont",
"loadkmap",
"logger",
"login",
"logname",
"losetup",
"lpd",
"lpq",
"lpr",
"ls",
"lsattr",
"lsmod",
"lsof",
"lspci",
"lsscsi",
"lsusb",
"lzcat",
"lzma",
"lzop",
"lzopcat",
"makedevs",
"makemime",
"man",
"md5sum",
"mdev",
"mesg",
"microcom",
"mkdir",
"mkdosfs",
"mke2fs",
"mkfifo",
"mkfs.ext2",
"mkfs.minix",
"mkfs.vfat",
"mknod",
"mkpasswd",
"mkswap",
"mktemp",
"modinfo",
"modprobe",
"more",
"mount",
"mountpoint",
"mpstat",
"mt",
"mv",
"nameif",
"nbd-client",
"nc",
"netstat",
"nice",
"nl",
"nmeter",
"nohup",
"nproc",
"ntpd",
"od",
"openvt",
"partprobe",
"passwd",
"paste",
"patch",
"pgrep",
"pidof",
"ping",
"ping6",
"pipe_progress",
"pivot_root",
"pkill",
"pmap",
"popmaildir",
"poweroff",
"powertop",
"printenv",
"printf",
"ps",
"pscan",
"pstree",
"pwd",
"pwdx",
"raidautorun",
"rdate",
"rdev",
"readlink",
"readprofile",
"realpath",
"reboot",
"reformime",
"remove-shell",
"renice",
"reset",
"resize",
"rev",
"rm",
"rmdir",
"rmmod",
"route",
"rpm",
"rpm2cpio",
"rtcwake",
"run-parts",
"runlevel",
"runsv",
"runsvdir",
"rx",
"script",
"scriptreplay",
"sed",
"sendmail",
"seq",
"setarch",
"setconsole",
"setfont",
"setkeycodes",
"setlogcons",
"setpriv",
"setserial",
"setsid",
"setuidgid",
"sh",
"sha1sum",
"sha256sum",
"sha3sum",
"sha512sum",
"showkey",
"shred",
"shuf",
"slattach",
"sleep",
"smemcap",
"softlimit",
"sort",
"split",
"ssl_client",
"start-stop-daemon",
"stat",
"strings",
"stty",
"su",
"sulogin",
"sum",
"sv",
"svc",
"svlogd",
"swapoff",
"swapon",
"switch_root",
"sync",
"sysctl",
"syslogd",
"tac",
"tail",
"tar",
"taskset",
"tcpsvd",
"tee",
"telnet",
"telnetd",
"test",
"tftp",
"tftpd",
"time",
"timeout",
"top",
"touch",
"tr",
"traceroute",
"traceroute6",
"true",
"truncate",
"tty",
"ttysize",
"tunctl",
"tune2fs",
"ubiattach",
"ubidetach",
"ubimkvol",
"ubirename",
"ubirmvol",
"ubirsvol",
"ubiupdatevol",
"udhcpc",
"udhcpd",
"udpsvd",
"uevent",
"umount",
"uname",
"uncompress",
"unexpand",
"uniq",
"unix2dos",
"unlink",
"unlzma",
"unlzop",
"unxz",
"unzip",
"uptime",
"users",
"usleep",
"uudecode",
"uuencode",
"vconfig",
"vi",
"vlock",
"volname",
"w",
"wall",
"watch",
"watchdog",
"wc",
"wget",
"which",
"who",
"whoami",
"whois",
"xargs",
"xxd",
"xz",
"xzcat",
"yes",
"zcat",
"zcip",
]
BUSYBOX_ARCHIVE_BUILD = """\
filegroup(
name = "file",
srcs = ["bin/busybox"],
visibility = ["//visibility:public"]
)
"""
def busybox_layer(busybox, **kwargs):
tar(
srcs = [busybox],
mtree = [
"./busybox/ uid=0 gid=0 mode=0755 time=0.0 type=dir",
"./busybox/busybox uid=0 gid=0 mode=0755 time=0.0 type=file content=$(location {})".format(busybox),
] + [
"./busybox/{cmd} uid=0 gid=0 mode=0755 time=0.0 type=link link=/busybox/busybox".format(cmd = cmd)
for cmd in BUSYBOX_COMMANDS
],
**kwargs
)
================================================
FILE: java/BUILD
================================================
load(":config.bzl", "JAVA_ARCHITECTURES", "JAVA_BASE_PACKAGES", "JAVA_DISTROS")
load(":java.bzl", "java_base_image", "java_base_image_index", "java_image", "java_image_index")
package(default_visibility = ["//visibility:public"])
# publishable java base images
[
java_base_image(
distro,
arch,
JAVA_BASE_PACKAGES[distro],
)
for distro in JAVA_DISTROS
for arch in JAVA_ARCHITECTURES[distro]
]
[
java_base_image_index(
distro,
JAVA_ARCHITECTURES[distro],
)
for distro in JAVA_DISTROS
]
# publishable java temurin images from adoptium deb repository
ADOPTIUM_DEB_PER_DISTRO = [
("17", "debian13"),
("21", "debian13"),
("25", "debian13"),
]
[
java_image(distro, java_version, arch)
for java_version, distro in ADOPTIUM_DEB_PER_DISTRO
for arch in JAVA_ARCHITECTURES[distro]
]
# all image indexes
[
java_image_index(
distro,
java_version,
JAVA_ARCHITECTURES[distro],
)
for java_version, distro in ADOPTIUM_DEB_PER_DISTRO
]
================================================
FILE: java/README.md
================================================
# Documentation for `gcr.io/distroless/java`
## Image Contents
This image contains a minimal Linux, OpenJDK-based runtime.
Specifically, the image contains everything in the [base image](../base/README.md), plus:
* Temurin OpenJDK 17 (`gcr.io/distroless/java17-debian13`) and its dependencies.
* Temurin OpenJDK 21 (`gcr.io/distroless/java21-debian13`) and its dependencies
* Temurin OpenJDK 25 (`gcr.io/distroless/java25-debian13`) and its dependencies
## Usage
The entrypoint of this image is set to the equivalent of "java -jar", so this image expects users to supply a path to a JAR file in the CMD.
## Add another JAVA version
To support a new JAVA version you need to do the following steps:
- Add the new version to `ADOPTIUM_DEB_PER_DISTRO` in [java/BUILD](BUILD).
- Add the new version to `JAVA_MAJOR_VERSIONS` in [java/config.bzl](config.bzl).
- Add two yaml files in the [java/testdata](testdata) folder to prepare the tests, you can take inspiration on the other files.
You can then ensure everything works on this folder with `bazel build //java:...`
================================================
FILE: java/config.bzl
================================================
JAVA_DISTROS = ["debian13"]
JAVA_ARCHITECTURES = {
"debian13": ["amd64", "arm64", "s390x", "ppc64le", "riscv64"],
}
JAVA_MAJOR_VERSIONS = {
"debian13": ["17", "21", "25"],
}
JAVA_BASE_PACKAGES = {
# debian 13 - temurin ships with libharfbuzz already
"debian13": [
"zlib1g",
"libjpeg62-turbo",
"liblcms2-2",
"libfreetype6",
"fonts-dejavu-core",
"fontconfig-config",
"libexpat1",
"libfontconfig1",
"libuuid1",
"libbrotli1",
"libcrypt1",
"libstdc++6",
"libgcc-s1",
"gcc-14-base",
"libpng16-16t64",
"libbz2-1.0",
],
}
================================================
FILE: java/control
================================================
Package: Eclipse Temurin
Version: {{VERSION}}
Architecture: {{ARCHITECTURE}}
Maintainer: Adoptium Working Group <https://adoptium.net/>
Homepage: https://adoptium.net
SHA256: {{SHA256}}
Description: Eclipse Temurin is the name of the OpenJDK distribution from Adoptium.
================================================
FILE: java/java.bzl
================================================
"java image definitions"
load("@container_structure_test//:defs.bzl", "container_structure_test")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_image_index")
load("//common:variables.bzl", "DEBUG_MODE", "USERS")
load("//java:jre_ver.bzl", "jre_ver")
load("//private/oci:defs.bzl", oci_java_image = "java_image")
load("//private/util:deb.bzl", "deb")
load("//private/util:java_cacerts.bzl", "java_cacerts")
load("//private/util:tar.bzl", "tar")
def ca_certs(distro, arch):
"""java ca certs for a specific arch and distro
"""
if native.existing_rule("cacerts_java_" + arch + "_" + distro):
return
java_cacerts(
name = "cacerts_java_" + arch + "_" + distro,
archive = "//common:cacerts_" + distro + "_" + arch,
)
def java_base_image_index(distro, architectures):
"""java base image index for a distro
Args:
distro: name of distribution
architectures: all architectures included in index
"""
for mode in DEBUG_MODE:
for user in USERS:
oci_image_index(
name = "java_base" + mode + "_" + user + "_" + distro,
images = [
"java_base" + mode + "_" + user + "_" + arch + "_" + distro
for arch in architectures
],
)
def java_base_image(distro, arch, packages):
"""java base for a distro/arch
Args:
distro: name of distribution
arch: the target arch
packages: to add to the image (from a debianX_java repo)
"""
# pre-req
ca_certs(distro, arch)
for mode in DEBUG_MODE:
for user in USERS:
oci_image(
name = "java_base" + mode + "_" + user + "_" + arch + "_" + distro,
base = "//base:base_nossl" + mode + "_" + user + "_" + arch + "_" + distro,
env = {"LANG": "C.UTF-8"},
tars = [
deb.package(arch, distro, pkg, "java")
for pkg in packages
] + [
"//common:locale_" + distro + "_" + arch,
":cacerts_java_" + arch + "_" + distro,
],
)
for mode in DEBUG_MODE:
for user in USERS:
container_structure_test(
name = "java_base" + mode + "_" + user + "_" + arch + "_" + distro + "_test",
configs = ["testdata/java_base" + mode + ".yaml"],
image = ":java_base" + mode + "_" + user + "_" + arch + "_" + distro,
tags = [
arch,
"manual",
],
)
def java_image_index(distro, java_version, architectures):
"""java and debug image indexes (applies to all java_image builds)
Args:
distro: name of distribution
java_version: version of java
architectures: all architectures included in index
"""
for user in USERS:
for mode in DEBUG_MODE:
oci_image_index(
name = "java" + java_version + mode + "_" + user + "_" + distro,
images = [
"java" + java_version + mode + "_" + user + "_" + arch + "_" + distro
for arch in architectures
],
)
def java_image(distro, java_version, arch):
"""java images from adoptium temurin deb distribution
Args:
distro: name of distribution
java_version: version of java
arch: the target arch
"""
# intermediary rule to configure jre symlinks
tar(
name = "temurin_" + java_version + "_jre_" + arch + "_" + distro,
extension = "tar.gz",
symlinks = {
"/usr/bin/java": "/usr/lib/jvm/temurin-" + java_version + "-jre-" + arch + "/bin/java",
"/etc/ssl/certs/adoptium/cacerts": "/etc/ssl/certs/java/cacerts",
},
deps = [
deb.package(
arch,
distro,
"temurin-" + java_version + "-jre",
"adoptium",
),
],
)
# intermediary rules to configure jdk symlinks
tar(
name = "temurin_" + java_version + "_jdk_" + arch + "_" + distro,
extension = "tar.gz",
symlinks = {
"/usr/bin/java": "/usr/lib/jvm/temurin-" + java_version + "-jdk-" + arch + "/bin/java",
"/etc/ssl/certs/adoptium/cacerts": "/etc/ssl/certs/java/cacerts",
},
deps = [
deb.package(
arch,
distro,
"temurin-" + java_version + "-jdk",
"adoptium",
),
],
)
# standard image builds
for user in USERS:
oci_image(
name = "java" + java_version + "_" + user + "_" + arch + "_" + distro,
base = ":java_base_" + user + "_" + arch + "_" + distro,
# We expect users to use:
# cmd = ["/path/to/deploy.jar", "--option1", ...]
entrypoint = [
"/usr/bin/java",
"-jar",
],
env = {
"JAVA_VERSION": jre_ver(deb.version(
arch,
distro,
"temurin-" + java_version + "-jre",
"adoptium",
)),
},
tars = [
# we use system certs, but we might want to pull this out of the distro
# like we did for the github released temurin
":temurin_" + java_version + "_jre_" + arch + "_" + distro,
],
)
# debug image builds
for user in USERS:
oci_image(
name = "java" + java_version + "_debug_" + user + "_" + arch + "_" + distro,
base = ":java_base_debug_" + user + "_" + arch + "_" + distro,
# We expect users to use:
# cmd = ["/path/to/deploy.jar", "--option1", ...]
entrypoint = [
"/usr/bin/java",
"-jar",
],
env = {
"JAVA_VERSION": jre_ver(deb.version(
arch,
distro,
"temurin-" + java_version + "-jdk",
"adoptium",
)),
},
tars = [
# we use system certs, but we might want to pull this out of the distro
# like we did for the github released temurin
":cacerts_java_" + arch + "_" + distro,
":temurin_" + java_version + "_jdk_" + arch + "_" + distro,
],
)
java_tests(distro, java_version, arch)
def java_tests(distro, java_version, arch):
"""Tests for java images.
Args:
distro: name of distribution
java_version: version of java
arch: the target arch
"""
for user in USERS:
container_structure_test(
name = "java" + java_version + "_" + user + "_" + arch + "_" + distro + "_test",
configs = ["testdata/java" + java_version + "_" + distro + ".yaml"],
image = ":java" + java_version + "_" + user + "_" + arch + "_" + distro,
tags = [
arch,
"manual",
],
)
for user in USERS:
container_structure_test(
name = "java" + java_version + "_debug_" + user + "_" + arch + "_" + distro + "_test",
configs = ["testdata/java" + java_version + "_debug" + "_" + distro + ".yaml"],
image = ":java" + java_version + "_debug_" + user + "_" + arch + "_" + distro,
tags = [
arch,
"manual",
],
)
if arch == "amd64":
for user in USERS:
oci_java_image(
name = "check_certs_java" + java_version + "_" + user + "_" + distro,
srcs = ["testdata/CheckCerts.java"],
base = "//java:java" + java_version + "_" + user + "_amd64_" + distro,
main_class = "testdata.CheckCerts",
)
for user in USERS:
container_structure_test(
name = "check_certs_java" + java_version + "_" + user + "_" + distro + "_test",
configs = ["testdata/java_certs.yaml"],
image = ":check_certs_java" + java_version + "_" + user + "_" + distro,
tags = [
"amd64",
"manual",
],
)
for user in USERS:
oci_java_image(
name = "check_encoding_java" + java_version + "_" + user + "_" + distro,
srcs = ["testdata/CheckEncoding.java"],
base = "//java:java" + java_version + "_" + user + "_amd64_" + distro,
main_class = "testdata.CheckEncoding",
)
for user in USERS:
container_structure_test(
name = "check_encoding_java" + java_version + "_" + user + "_" + distro + "_test",
configs = ["testdata/java_encoding.yaml"],
image = ":check_encoding_java" + java_version + "_" + user + "_" + distro,
tags = [
"amd64",
"manual",
],
)
for user in USERS:
oci_java_image(
name = "check_libharfbuzz_java" + java_version + "_" + user + "_" + distro,
srcs = ["testdata/CheckLibharfbuzz.java"],
base = "//java:java" + java_version + "_" + user + "_amd64_" + distro,
main_class = "testdata.CheckLibharfbuzz",
)
for user in USERS:
container_structure_test(
name = "check_libharfbuzz_java" + java_version + "_" + user + "_" + distro + "_test",
configs = ["testdata/java_libharfbuzz.yaml"],
image = ":check_libharfbuzz_java" + java_version + "_" + user + "_" + distro,
tags = [
"amd64",
"manual",
],
)
================================================
FILE: java/jre_ver.bzl
================================================
def jre_ver(version):
"""Extract JRE version from a Debian openjdk downloaded filename.
Debian packages versions are of the form:
openjdk-11-jre*: 11.0.1+13-2~bpo9+1
"""
if version.startswith("11.") or version.startswith("17.") or version.startswith("21.") or version.startswith("25."):
v = version.split("+")[0].split(".")
return v[0] + "." + v[1] + "." + v[2]
fail("unrecognized openjdk package version: " + version)
================================================
FILE: java/testdata/CheckCerts.java
================================================
package testdata;
import java.io.IOException;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
public class CheckCerts {
public static void main(String[] args) throws IOException {
URL url = new URL("https://www.google.com/");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.connect();
conn.getServerCertificates(); // succeeds if peer verified
System.out.println("Successfully connected: " + conn.getResponseCode());
}
}
================================================
FILE: java/testdata/CheckEncoding.java
================================================
package testdata;
import java.nio.charset.Charset;
import java.util.Locale;
public class CheckEncoding {
public static void main(String[] args) {
System.out.println("LANG=" + System.getenv("LANG"));
System.out.println("Locale.getDefault()=" + Locale.getDefault());
System.out.println("Charset.defaultCharset()=" + Charset.defaultCharset());
System.out.println("file.encoding=" + System.getProperty("file.encoding"));
System.out.println("sun.jnu.encoding=" + System.getProperty("sun.jnu.encoding"));
}
}
================================================
FILE: java/testdata/CheckLibharfbuzz.java
================================================
package testdata;
import java.awt.GraphicsEnvironment;
import java.awt.Font;
public class CheckLibharfbuzz {
public static void main(String[] args) {
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
Font [] fonts = env.getAllFonts();
if ( fonts.length > 0 ) {
System.out.println(fonts.length + " fonts available");
}
}
}
================================================
FILE: java/testdata/java17_debian13.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
- name: java
command: "/usr/lib/jvm/temurin-17-jre-amd64/bin/java"
args: ["-version"]
expectedError: ['openjdk version "17.0.18']
- name: java-symlink
command: "/usr/bin/java"
args: ["-version"]
expectedError: ['openjdk version "17.0.18']
fileExistenceTests:
- name: certs
path: "/etc/ssl/certs/java/cacerts"
shouldExist: true
- name: certs
path: "/etc/ssl/certs/adoptium/cacerts"
permissions: 'Lrwxrwxrwx'
shouldExist: true
- name: certs
path: "/usr/lib/jvm/temurin-17-jre-amd64/lib/security/cacerts"
permissions: 'Lrwxrwxrwx'
shouldExist: true
- name: no-busybox
path: "/busybox/sh"
shouldExist: false
- name: no-shell
path: "/bin/sh"
shouldExist: false
- name: no-javac
path: "/usr/lib/jvm/temurin-17-jre-amd64/bin/javac"
shouldExist: false
- name: jexec-executable
path: "/usr/lib/jvm/temurin-17-jre-amd64/lib/jexec"
shouldExist: true
isExecutableBy: "any"
- name: jspawnhelper-executable
path: "/usr/lib/jvm/temurin-17-jre-amd64/lib/jspawnhelper"
shouldExist: true
isExecutableBy: "any"
metadataTest:
envVars:
- key: 'JAVA_VERSION'
value: '17.0.18'
================================================
FILE: java/testdata/java17_debug_debian13.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
- name: java
command: "/usr/lib/jvm/temurin-17-jdk-amd64/bin/java"
args: ["-version"]
expectedError: ['openjdk version "17.0.18"']
- name: java-symlink
command: "/usr/bin/java"
args: ["-version"]
expectedError: ['openjdk version "17.0.18"']
- name: javac
command: "/usr/lib/jvm/temurin-17-jdk-amd64/bin/javac"
args: ["-version"]
expectedOutput: ['javac 17.0.18']
fileExistenceTests:
- name: certs
path: "/etc/ssl/certs/java/cacerts"
shouldExist: true
- name: certs
path: "/etc/ssl/certs/adoptium/cacerts"
permissions: 'Lrwxrwxrwx'
shouldExist: true
- name: certs
path: "/usr/lib/jvm/temurin-17-jdk-amd64/lib/security/cacerts"
permissions: 'Lrwxrwxrwx'
shouldExist: true
- name: busybox
path: "/busybox/sh"
shouldExist: true
- name: no-shell
path: "/bin/sh"
shouldExist: false
metadataTest:
envVars:
- key: 'JAVA_VERSION'
value: '17.0.18'
================================================
FILE: java/testdata/java21_debian13.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
- name: java
command: "/usr/lib/jvm/temurin-21-jre-amd64/bin/java"
args: ["-version"]
expectedError: ['openjdk version "21.0.10"']
- name: java-symlink
command: "/usr/bin/java"
args: ["-version"]
expectedError: ['openjdk version "21.0.10"']
fileExistenceTests:
- name: certs
path: "/etc/ssl/certs/java/cacerts"
shouldExist: true
- name: certs
path: "/etc/ssl/certs/adoptium/cacerts"
permissions: 'Lrwxrwxrwx'
shouldExist: true
- name: certs
path: "/usr/lib/jvm/temurin-21-jre-amd64/lib/security/cacerts"
permissions: 'Lrwxrwxrwx'
shouldExist: true
- name: no-busybox
path: "/busybox/sh"
shouldExist: false
- name: no-shell
path: "/bin/sh"
shouldExist: false
- name: no-javac
path: "/usr/lib/jvm/temurin-21-jre-amd64/bin/javac"
shouldExist: false
- name: jexec-executable
path: "/usr/lib/jvm/temurin-21-jre-amd64/lib/jexec"
shouldExist: true
isExecutableBy: "any"
- name: jspawnhelper-executable
path: "/usr/lib/jvm/temurin-21-jre-amd64/lib/jspawnhelper"
shouldExist: true
isExecutableBy: "any"
metadataTest:
envVars:
- key: 'JAVA_VERSION'
value: '21.0.10'
================================================
FILE: java/testdata/java21_debug_debian13.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
- name: java
command: "/usr/lib/jvm/temurin-21-jdk-amd64/bin/java"
args: ["-version"]
expectedError: ['openjdk version "21.0.10"']
- name: java-symlink
command: "/usr/bin/java"
args: ["-version"]
expectedError: ['openjdk version "21.0.10"']
- name: javac
command: "/usr/lib/jvm/temurin-21-jdk-amd64/bin/javac"
args: ["-version"]
expectedOutput: ['javac 21.0.10']
fileExistenceTests:
- name: certs
path: "/etc/ssl/certs/java/cacerts"
shouldExist: true
- name: certs
path: "/etc/ssl/certs/adoptium/cacerts"
permissions: 'Lrwxrwxrwx'
shouldExist: true
- name: certs
path: "/usr/lib/jvm/temurin-21-jdk-amd64/lib/security/cacerts"
permissions: 'Lrwxrwxrwx'
shouldExist: true
- name: busybox
path: "/busybox/sh"
shouldExist: true
- name: no-shell
path: "/bin/sh"
shouldExist: false
metadataTest:
envVars:
- key: 'JAVA_VERSION'
value: '21.0.10'
================================================
FILE: java/testdata/java25_debian13.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
- name: java
command: "/usr/lib/jvm/temurin-25-jre-amd64/bin/java"
args: ["-version"]
expectedError: ['openjdk version "25.0.2"']
- name: java-symlink
command: "/usr/bin/java"
args: ["-version"]
expectedError: ['openjdk version "25.0.2"']
fileExistenceTests:
- name: certs
path: "/etc/ssl/certs/java/cacerts"
shouldExist: true
- name: certs
path: "/etc/ssl/certs/adoptium/cacerts"
permissions: 'Lrwxrwxrwx'
shouldExist: true
- name: certs
path: "/usr/lib/jvm/temurin-25-jre-amd64/lib/security/cacerts"
permissions: 'Lrwxrwxrwx'
shouldExist: true
- name: no-busybox
path: "/busybox/sh"
shouldExist: false
- name: no-shell
path: "/bin/sh"
shouldExist: false
- name: no-javac
path: "/usr/lib/jvm/temurin-25-jre-amd64/bin/javac"
shouldExist: false
- name: jexec-executable
path: "/usr/lib/jvm/temurin-25-jre-amd64/lib/jexec"
shouldExist: true
isExecutableBy: "any"
- name: jspawnhelper-executable
path: "/usr/lib/jvm/temurin-25-jre-amd64/lib/jspawnhelper"
shouldExist: true
isExecutableBy: "any"
metadataTest:
envVars:
- key: 'JAVA_VERSION'
value: '25.0.2'
================================================
FILE: java/testdata/java25_debug_debian13.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
- name: java
command: "/usr/lib/jvm/temurin-25-jdk-amd64/bin/java"
args: ["-version"]
expectedError: ['openjdk version "25.0.2"']
- name: java-symlink
command: "/usr/bin/java"
args: ["-version"]
expectedError: ['openjdk version "25.0.2"']
- name: javac
command: "/usr/lib/jvm/temurin-25-jdk-amd64/bin/javac"
args: ["-version"]
expectedOutput: ['javac 25.0.2']
fileExistenceTests:
- name: certs
path: "/etc/ssl/certs/java/cacerts"
shouldExist: true
- name: certs
path: "/etc/ssl/certs/adoptium/cacerts"
permissions: 'Lrwxrwxrwx'
shouldExist: true
- name: certs
path: "/usr/lib/jvm/temurin-25-jdk-amd64/lib/security/cacerts"
permissions: 'Lrwxrwxrwx'
shouldExist: true
- name: busybox
path: "/busybox/sh"
shouldExist: true
- name: no-shell
path: "/bin/sh"
shouldExist: false
metadataTest:
envVars:
- key: 'JAVA_VERSION'
value: '25.0.2'
================================================
FILE: java/testdata/java_base.yaml
================================================
schemaVersion: "2.0.0"
fileExistenceTests:
- name: certs
path: "/etc/ssl/certs/java/cacerts"
shouldExist: true
- name: no-busybox
path: "/busybox/sh"
shouldExist: false
- name: no-shell
path: "/bin/sh"
shouldExist: false
- name: no-jvm
path: "/usr/lib/jvm"
shouldExist: false
================================================
FILE: java/testdata/java_base_debug.yaml
================================================
schemaVersion: "2.0.0"
fileExistenceTests:
- name: certs
path: "/etc/ssl/certs/java/cacerts"
shouldExist: true
- name: busybox
path: "/busybox/sh"
shouldExist: true
- name: no-shell
path: "/bin/sh"
shouldExist: false
- name: no-jvm
path: "/usr/lib/jvm"
shouldExist: false
================================================
FILE: java/testdata/java_certs.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
- name: connect_to_https_google_com
# This is a bit ugly because structure tests can't test the default entrypoint yet.
command: ["/usr/bin/java",
"-cp",
"/*",
"testdata.CheckCerts"]
expectedOutput: ['Successfully connected: 200']
================================================
FILE: java/testdata/java_encoding.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
- name: check_encoding
command: ["/usr/bin/java",
"-cp",
"/*",
"testdata.CheckEncoding"]
expectedOutput: ['LANG=C.UTF-8',
'Locale.getDefault\(\)=en',
'Charset.defaultCharset\(\)=UTF-8',
'file.encoding=UTF-8',
'sun.jnu.encoding=UTF-8']
================================================
FILE: java/testdata/java_libharfbuzz.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
- name: check_libharfbuzz
command: ["/usr/bin/java",
"-cp",
"/*",
"testdata.CheckLibharfbuzz"]
expectedOutput: ['^\d+ fonts available']
================================================
FILE: knife
================================================
#!/usr/bin/env bash
set -o pipefail -o errexit -o nounset
# Copyright 2024 Google Inc. All rights reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
source knife.d/update_java_versions.sh
if [ $(uname) == "Darwin" ]; then
echo "WARNING: You are on a macos, you need to run 'brew install coreutils gnu-sed' to install required packages."
echo ""
export PATH="/opt/homebrew/opt/coreutils/libexec/gnubin:$PATH"
export PATH="/opt/homebrew/opt/gnu-sed/libexec/gnubin:$PATH"
fi
function cmd_lock_all() {
cmd_lock_snapshots
cmd_lock_non_snapshots
}
function cmd_lock_snapshots() {
echo "🚧 Querying for snapshot repos"
echo ""
local repos=$(grep -l snapshot.debian.org ./private/repos/deb/*.yaml | xargs -L 1 basename | cut -d. -f 1)
_cmd_lock "$repos"
}
function cmd_lock_non_snapshots() {
echo "🚧 Querying for non_snapshot repos"
echo ""
local repos=$(grep -lL snapshot.debian.org ./private/repos/deb/*.yaml | xargs -L 1 basename | cut -d. -f 1)
_cmd_lock "$repos"
update_java_versions_debian13
}
function _cmd_lock() {
local repos="$1"
for repo in $repos; do
for i in $(seq 10); do
echo "🔑 Locking $repo (attempt ${i})"
bazel run "@${repo}//:lock" && break || sleep 20;
if [[ $i -eq 10 ]]; then
echo ""
echo "Failed to lock $repo after 10 attempts" >&2
exit 1
fi
done
done
}
function find_latest_snapshot() {
local type="$1"
# If it's the first of the month, look at the last month, otherwise this -1 day has no effect since searches
# occur at the "month" level. This is an intentional buffer added to get the snapshots fully hydrated. We
# intentionally don't include complicated logic for the case where it's after the 1st and no snapshots are
# availalbe for the month (it's extremely unlikely for our updater to run into this situation unless the
# snapshot serving infrastructure is acting up).
local current="$(date -d '-1 day' +%Y-%m-%d)"
local tmp=$(mktemp)
local q=$(date -d "$current" +"year=%Y&month=%m")
if curl -fs "https://snapshot.debian.org/archive/debian/?$q" | grep -ohE "([0-9]+T[0-9]+Z)" > $tmp; then
# same logic as above, find the newest snapshot that isn't "today"
today=$(date +"%Y%m%dT")
cat $tmp | grep -v $today | tail -n1
fi
}
function cmd_update_snapshots() {
echo "🧐 Looking for updates... "
latest=$(find_latest_snapshot "debian")
latest_security=$(find_latest_snapshot "debian-security")
if [[ -z "$latest" || -z "$latest_security" ]]; then
echo ""
echo "could not find any snapshots for debian or debian-security"
exit 1
fi
echo ""
echo "🎯 Found snapshots"
echo " debian: $latest"
echo " security: $latest_security"
echo ""
# if tty ask for approval
if [ -t 1 ]; then
read -p "Do you want to continue? (y/n) " -n 1 -r
sleep 0.5
echo $'\n'
if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then
echo "Aborting..."
exit 0
fi
fi
for mpath in "./private/repos/deb/"*.yaml; do
if ! grep -q "snapshot.debian.org" "$mpath"; then
echo "ignoring non-snapshot repo $mpath"
continue
fi
current=$(grep -oE "debian/([0-9]+T[0-9]+Z)" $mpath | cut -d/ -f2 | head -n1)
current_security=$(grep -oE "debian-security/([0-9]+T[0-9]+Z)" $mpath | cut -d/ -f2 | head -n1)
if [[ "$current" == "$latest" && "$current_security" == "$latest_security" ]]; then
echo "🎖️ $mpath is up to date."
continue
fi
echo "🗞️ $mpath"
if [[ "$current" != "$latest" ]]; then
sed -i -E "s/(debian\/)([0-9]+T[0-9]+Z)/\1$latest/" "$mpath"
echo " debian: $current -> $latest"
fi
if [[ "$current_security" != "$latest_security" ]]; then
sed -i -E "s/(debian-security\/)([0-9]+T[0-9]+Z)/\1$latest_security/" "$mpath"
echo " debian-security: $current_security -> $latest_security"
fi
echo ""
done
echo ""
echo "👌 Done..."
}
# write DISTROLESS_DIFF to GITHUB_ENV if changes are made
function cmd_github_update_snapshots() {
local tmp=$(mktemp -d)
jq -nr 'inputs.packages[] | .key + " " + .sha256' ./private/repos/deb/*.lock.json | sort > "$tmp/old.hashes"
cmd_update_snapshots
cmd_lock_snapshots
jq -nr 'inputs.packages[] | .key + " " + .sha256' ./private/repos/deb/*.lock.json | sort > "$tmp/new.hashes"
diff "$tmp/old.hashes" "$tmp/new.hashes" | tee "$tmp/diff" || printf "DISTROLESS_DIFF<<EOF\n$(<$tmp/diff)\nEOF" >> "$GITHUB_ENV"
}
function cmd_lint () {
local mode="fix"
if [[ "${1:-}" == "--check" ]]; then
mode="check"
fi
echo "🧹 Linting"
echo ""
if ! which buildifier > /dev/null; then
echo "🧱 No buildifier executable was found."
echo " Did you follow the ./CONTRIBUTING.md ?"
exit 1
fi
buildifier -mode=$mode $(find . -type f \( -name 'BUILD*' -o -name 'WORKSPACE*' -o -name '*.bzl' \))
}
function cmd_update_node_archives () {
if ! which jq > /dev/null; then
echo "🧱 No jq executable was found"
exit 1
fi
if ! which curl > /dev/null; then
echo "🧱 No curl executable was found"
exit 1
fi
if ! which node > /dev/null; then
echo "🧱 No node executable was found"
exit 1
fi
versions=()
for major in 20 22 24; do
latest_version=$(curl -sSL https://nodejs.org/dist/index.json | jq -r --arg major "v$major" '
map(select(.version | startswith($major)))
| sort_by(.date) | reverse | .[0].version
')
latest_nov=$(echo "$latest_version" | sed 's/^v//')
versions+=("$latest_nov")
done
joined_versions=$(IFS=, ; echo "${versions[*]}")
node knife.d/update_node_archives.js "$joined_versions"
}
function cmd_test () {
echo "🧪 Testing"
echo ""
local arch=$(uname -m)
if [ ${arch} == "x86_64" ]; then
arch="amd64"
fi
echo "💡 only including image tests for $arch"
echo ""
arch_specific_targets=$(bazel query "attr(\"tags\", "$arch", \"//...\")")
# Run all tests tagged with "amd64"
bazel test --test_timeout=900 //... $arch_specific_targets
}
function cmd_deb_versions () {
echo "🔧 Printing .deb Versions (bookworm) from private/repos/deb/bookworm*.lock.json"
echo ""
jq -n '[inputs.packages[]] | group_by(.arch) | map({(.[0].arch): map({package: .name, version: .version})})' private/repos/deb/bookworm*.lock.json
}
case "${1:-"~~nocmd"}" in
lock)
cmd_lock_all
;;
update-snapshots)
cmd_update_snapshots
;;
update-non-snapshots)
cmd_lock_non_snapshots
;;
lint)
cmd_lint "${@:2}"
;;
github-update-snapshots)
cmd_github_update_snapshots
;;
test)
cmd_test
;;
deb-versions)
cmd_deb_versions
;;
update-node-archives)
cmd_update_node_archives
;;
~~nocmd) # no command provided
echo "provide a command: lock, update-snapshots, github-update-snapshots, update-non-snapshots, test, deb-versions, update-node-archives"
exit 1
;;
*) # unknown command
echo "unknown command $1"
exit 1
;;
esac
================================================
FILE: knife.d/update_java_versions.sh
================================================
set -o pipefail -o errexit -o nounset
# Copyright 2024 Google Inc. All rights reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# a collection of functions to use when updating java archives from the knife utility
function update_java_versions_debian13() {
local java_versions=("17" "21" "25")
for java_version in "${java_versions[@]}"; do
local version=$(jq -r --arg jv "temurin-${java_version}-jre" '.packages.[] | select((.arch=="amd64") and (.name==$jv)) | .version | split(".") | .[0:3] | join(".")' private/repos/deb/trixie_adoptium.lock.json)
local major_version=$(echo "$version" | cut -d. -f 1)
sed -i -r -e "s/${major_version}\\.[0-9]+\\.[0-9]+/${version}/g" java/testdata/java${major_version}*debian13.yaml
done
}
================================================
FILE: knife.d/update_node_archives.js
================================================
#!/usr/bin/env node
const crypto = require("crypto");
const https = require("https");
const fs = require("fs");
if (process.argv.length < 3) {
console.error("Usage: node nodeChecksum.js <nodejs_version>");
process.exit(1);
}
const versions = process.argv[2].split(",");
const architectures = ["amd64", "arm64", "arm", "ppc64le", "s390x"];
const nodeVersions = {};
const calculateChecksum = (url) => {
return new Promise((resolve, reject) => {
https
.get(url, (res) => {
const hash = crypto.createHash("sha256");
res.on("data", (data) => {
hash.update(data);
});
res.on("end", () => {
resolve(hash.digest("hex"));
});
})
.on("error", (err) => {
reject(`Error downloading file: ${err.message}`);
});
});
};
const fetchChecksums = async () => {
for (const nodeVersion of versions) {
const major = parseInt(nodeVersion.split(".")[0]);
nodeVersions[nodeVersion] = {};
await Promise.all(
architectures.map(async (key) => {
let arch = key;
if (major > 22 && key === "arm") {
return;
}
if (key === "amd64") {
arch = "x64";
} else if (key === "arm") {
arch = "armv7l";
}
const url = `https://nodejs.org/dist/v${nodeVersion}/node-v${nodeVersion}-linux-${arch}.tar.gz`;
try {
const checksum = await calculateChecksum(url);
nodeVersions[nodeVersion][key] = {
checksum,
suffix: arch,
};
} catch (error) {
console.error(error);
}
})
);
}
};
fetchChecksums().then(() => {
let nodeArchives = `"node"
BUILD_TMPL = """\\
# GENERATED BY node_archive.bzl
load("@distroless//private/pkg:debian_spdx.bzl", "debian_spdx")
load("@distroless//private/util:merge_providers.bzl", "merge_providers")
load("@distroless//private/util:tar.bzl", "tar")
tar(
name = "data",
extension = "tar.gz",
srcs = glob(
[
"output/bin/node",
"output/LICENSE",
],
),
package_dir = "/nodejs",
strip_prefix = "external/{canonical_name}/output"
)
tar(
name = "_control",
extension = "tar.gz",
srcs = ["control"]
)
debian_spdx(
name = "spdx",
control = ":_control",
data = ":data",
package_name = "{package_name}",
spdx_id = "{spdx_id}",
sha256 = "{sha256}",
urls = [{urls}]
)
merge_providers(
name = "{name}",
srcs = [":data", ":spdx"],
visibility = ["//visibility:public"],
)
"""
def _impl(rctx):
rctx.report_progress("Fetching {}".format(rctx.attr.package_name))
rctx.download_and_extract(
url = rctx.attr.urls,
sha256 = rctx.attr.sha256,
type = rctx.attr.type,
stripPrefix = rctx.attr.strip_prefix,
output = "output",
)
rctx.template(
"control",
rctx.attr.control,
substitutions = {
"{{VERSION}}": rctx.attr.version,
"{{ARCHITECTURE}}": rctx.attr.architecture,
"{{SHA256}}": rctx.attr.sha256,
},
)
rctx.file(
"BUILD.bazel",
content = BUILD_TMPL.format(
canonical_name = rctx.attr.name,
name = rctx.attr.name.split("~")[-1],
package_name = rctx.attr.package_name,
spdx_id = rctx.attr.name,
urls = ",".join(['"%s"' % url for url in rctx.attr.urls]),
sha256 = rctx.attr.sha256,
),
)
node_archive = repository_rule(
implementation = _impl,
attrs = {
"urls": attr.string_list(mandatory = True),
"sha256": attr.string(mandatory = True),
"type": attr.string(default = ".tar.gz"),
"strip_prefix": attr.string(),
"package_name": attr.string(default = "nodejs"),
"version": attr.string(mandatory = True),
"architecture": attr.string(mandatory = True),
# control is only used to populate the sbom, see https://github.com/GoogleContainerTools/distroless/issues/1373
# for why writing debian control files to the image is incompatible with scanners.
"control": attr.label(),
},
)
def _node_impl(module_ctx):
mod = module_ctx.modules[0]
if len(module_ctx.modules) > 1:
fail("node.archive should be called only once")
if not mod.is_root:
fail("node.archive should be called from root module only.")
# Node (https://nodejs.org/en/about/releases/)
# Follow Node's maintainence schedule and support all LTS versions that are not end of life`;
for (const nodeVersion of versions) {
const major = parseInt(nodeVersion.split(".")[0]);
for (const key of architectures) {
if (major > 22 && key === "arm") {
continue;
}
const arch = nodeVersions[nodeVersion][key];
const url = `https://nodejs.org/dist/v${nodeVersion}/node-v${nodeVersion}-linux-${arch.suffix}.tar.gz`;
nodeArchives += "\n";
nodeArchives += `
node_archive(
name = "nodejs${major}_${key}",
sha256 = "${arch.checksum}",
strip_prefix = "node-v${nodeVersion}-linux-${arch.suffix}/",
urls = ["${url}"],
version = "${nodeVersion}",
architecture = "${key}",
control = "//nodejs:control",
)`;
}
const testData = `schemaVersion: "2.0.0"
commandTests:
- name: nodejs
command: "/nodejs/bin/node"
args: ["--version"]
expectedOutput: ["v${nodeVersion}"]
`;
fs.writeFile(`nodejs/testdata/nodejs${major}.yaml`, testData, (err) => {
if (err) {
console.error(err);
}
});
}
nodeArchives += `
return module_ctx.extension_metadata(
root_module_direct_deps = [`;
for (const nodeVersion of versions) {
const major = parseInt(nodeVersion.split(".")[0]);
for (const arch of architectures) {
if (major > 22 && arch === "arm") {
continue;
}
nodeArchives += `
"nodejs${major}_${arch}",`;
}
}
nodeArchives += `
],
root_module_direct_dev_deps = [],
)
_archive = tag_class(attrs = {})
node = module_extension(
implementation = _node_impl,
tag_classes = {
"archive": _archive,
},
)
`;
// Write output to node_archives.bzl file
fs.writeFile("private/extensions/node.bzl", nodeArchives, (err) => {
if (err) {
console.error(err);
}
});
});
================================================
FILE: nodejs/BUILD
================================================
load(":config.bzl", "NODEJS_ARCHITECTURES", "NODEJS_DISTROS", "NODEJS_MAJOR_VERSIONS")
load(":nodejs.bzl", "nodejs_image", "nodejs_image_index")
package(default_visibility = ["//visibility:public"])
[
nodejs_image(
arch = arch,
distro = distro,
major_version = major_version,
)
for distro in NODEJS_DISTROS
for major_version in NODEJS_MAJOR_VERSIONS
for arch in NODEJS_ARCHITECTURES[distro][major_version]
]
[
nodejs_image_index(
architectures = NODEJS_ARCHITECTURES[distro][major_version],
distro = distro,
major_version = major_version,
)
for distro in NODEJS_DISTROS
for major_version in NODEJS_MAJOR_VERSIONS
]
================================================
FILE: nodejs/README.md
================================================
# Documentation for `gcr.io/distroless/nodejs`
## Image Contents
These images contain a minimal Linux, Node.js-based runtime. The supported versions match the [Node.js LTS releases](https://nodejs.org/en/about/previous-releases).
Specifically, these images contain everything in the [cc image](../cc/README.md), plus one of:
- Node.js v20 (`gcr.io/distroless/nodejs20-debian13`) and its dependencies.
- Node.js v22 (`gcr.io/distroless/nodejs22-debian13`) and its dependencies.
- Node.js v24 (`gcr.io/distroless/nodejs24-debian13`) and its dependencies.
## Usage
The entrypoint of this image is set to "node", so this image expects users to supply a path to a .js file in the CMD.
See the Node.js [Hello World](../examples/nodejs/) directory for an example.
================================================
FILE: nodejs/config.bzl
================================================
NODEJS_DISTROS = ["debian13"]
NODEJS_ARCHITECTURES = {
"debian13": {
"20": ["amd64", "arm64", "arm", "s390x", "ppc64le"],
"22": ["amd64", "arm64", "arm", "s390x", "ppc64le"],
"24": ["amd64", "arm64", "s390x", "ppc64le"],
},
}
NODEJS_MAJOR_VERSIONS = ["20", "22", "24"]
================================================
FILE: nodejs/control
================================================
Package: nodejs
Version: {{VERSION}}
Architecture: {{ARCHITECTURE}}
Maintainer: OpenJS Foundation <https://openjsf.org/>
Homepage: https://nodejs.org
SHA256: {{SHA256}}
Description: Node.js event-based server-side javascript engine
Node.js is similar in design to and influenced by systems like
Ruby's Event Machine or Python's Twisted.
.
It takes the event model a bit further - it presents the event
loop as a language construct instead of as a library.
.
Node.js is bundled with several useful libraries to handle server tasks :
System, Events, Standard I/O, Modules, Timers, Child Processes, POSIX,
HTTP, Multipart Parsing, TCP, DNS, Assert, Path, URL, Query Strings.
================================================
FILE: nodejs/nodejs.bzl
================================================
"nodejs image definitions"
load("@container_structure_test//:defs.bzl", "container_structure_test")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_image_index")
load("//common:variables.bzl", "DEBUG_MODE", "USERS")
load("//private/util:tar.bzl", "tar")
def nodejs_image_index(distro, major_version, architectures):
"""nodejs image index for a distro.
Args:
distro: name of distribution
major_version: version of nodejs
architectures: all architectures included in index
"""
for mode in DEBUG_MODE:
for user in USERS:
oci_image_index(
name = "nodejs" + major_version + mode + "_" + user + "_" + distro,
images = [
"nodejs" + major_version + mode + "_" + user + "_" + arch + "_" + distro
for arch in architectures
],
)
def _check_certificates_tar():
# only create once
if native.existing_rule("check_certificate"):
return
tar(
name = "check_certificate",
extension = "tar.gz",
srcs = ["testdata/check_certificate.js"],
)
def nodejs_image(distro, major_version, arch):
"""nodejs and debug image with tests.
Args:
distro: name of distribution
major_version: version of nodejs
arch: the target arch
"""
for mode in DEBUG_MODE:
for user in USERS:
oci_image(
name = "nodejs" + major_version + mode + "_" + user + "_" + arch + "_" + distro,
base = "//cc:cc" + mode + "_" + user + "_" + arch + "_" + distro,
entrypoint = ["/nodejs/bin/node"],
tars = [
"@nodejs" + major_version + "_" + arch,
],
)
_check_certificates_tar()
for mode in DEBUG_MODE:
for user in USERS:
container_structure_test(
name = "nodejs" + major_version + mode + "_" + user + "_" + arch + "_" + distro + "_test",
configs = [
"testdata/nodejs" + major_version + ".yaml",
"testdata/check_headers.yaml",
"testdata/check_npm.yaml",
],
image = "nodejs" + major_version + mode + "_" + user + "_" + arch + "_" + distro,
tags = [
arch,
"manual",
],
)
for mode in DEBUG_MODE:
for user in USERS:
oci_image(
name = "check_certificate_nodejs" + major_version + mode + "_" + user + "_" + arch + "_" + distro,
base = "nodejs" + major_version + mode + "_" + user + "_" + arch + "_" + distro,
tars = [
":check_certificate",
],
)
for mode in DEBUG_MODE:
for user in USERS:
container_structure_test(
name = "check_certificate_nodejs" + major_version + mode + "_" + user + "_" + arch + "_" + distro + "_test",
configs = ["testdata/check_certificate.yaml"],
image = "check_certificate_nodejs" + major_version + mode + "_" + user + "_" + arch + "_" + distro,
tags = [
arch,
"manual",
],
)
================================================
FILE: nodejs/testdata/check_certificate.js
================================================
const https = require('https')
const options = {
hostname: 'www.google.com',
port: 443,
path: '/',
method: 'GET'
}
const req = https.request(options, res => {
console.log(`statusCode: ${res.statusCode}`)
res.resume()
})
req.end()
================================================
FILE: nodejs/testdata/check_certificate.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
- name: nodejs
command: "/nodejs/bin/node"
args: ["/check_certificate.js"]
expectedOutput: ['statusCode: 200']
================================================
FILE: nodejs/testdata/check_headers.yaml
================================================
schemaVersion: "2.0.0"
fileExistenceTests:
- name: npm
path: '/nodejs/include/node/node.h'
shouldExist: false
================================================
FILE: nodejs/testdata/check_npm.yaml
================================================
schemaVersion: "2.0.0"
fileExistenceTests:
- name: npm
path: '/nodejs/lib/node_modules/npm'
shouldExist: false
- name: corepack
path: '/nodejs/lib/node_modules/corepack'
shouldExist: false
- name: npm
path: '/nodejs/bin/npm'
shouldExist: false
- name: corepack
path: '/nodejs/bin/corepack'
shouldExist: false
- name: npx
path: '/nodejs/bin/npx'
shouldExist: false
================================================
FILE: nodejs/testdata/nodejs20.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
- name: nodejs
command: "/nodejs/bin/node"
args: ["--version"]
expectedOutput: ["v20.20.1"]
================================================
FILE: nodejs/testdata/nodejs22.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
- name: nodejs
command: "/nodejs/bin/node"
args: ["--version"]
expectedOutput: ["v22.22.1"]
================================================
FILE: nodejs/testdata/nodejs24.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
- name: nodejs
command: "/nodejs/bin/node"
args: ["--version"]
expectedOutput: ["v24.14.0"]
================================================
FILE: private/extensions/BUILD.bazel
================================================
================================================
FILE: private/extensions/busybox.bzl
================================================
"busybox"
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
load("//experimental/busybox:commands.bzl", "BUSYBOX_ARCHIVE_BUILD")
def _busybox_impl(module_ctx):
mod = module_ctx.modules[0]
if len(module_ctx.modules) > 1:
fail("busybox.archive should be called only once")
if not mod.is_root:
fail("busybox.archive should be called from root module only.")
# To update amd64 busybox binary (#1014)
# Get the latest commit hash from dist-amd64 branch of docker-library repo. You can also view it
# at https://github.com/docker-library/official-images/blob/master/library/busybox
# Substitute it in the link: https://github.com/docker-library/busybox/raw/<latest-commit-hash>/latest/musl/busybox.tar.gz
# Update the sha256 value. Since github api doesn't give sha256 value, it can be obtained using sha256sum command.
http_archive(
name = "busybox_amd64",
sha256 = "8e7bef4a92afca21c56c9f85e4e63885b320f1da8f8f82a1cf87af359faf57d3",
urls = ["https://raw.githubusercontent.com/docker-library/busybox/e5b5178110ca0332364a77d5eb4fff87b0e2ba3f/latest/musl/amd64/rootfs.tar.gz"],
build_file_content = BUSYBOX_ARCHIVE_BUILD,
)
# To update arm busybox binary
# Get the latest commit hash from dist-arm32v7 branch of docker-library repo. You can also view it
# at https://github.com/docker-library/official-images/blob/master/library/busybox
# Substitute it in the link: https://github.com/docker-library/busybox/raw/<latest-commit-hash>/latest/musl/arm32v7/rootfs.tar.gz
# Update the sha256 value. Since github api doesn't give sha256 value, it can be obtained using sha256sum command.
http_archive(
name = "busybox_arm",
sha256 = "3c873527f998bba56151b7c7d19c133390a44ef894242e7ad5536de1ac0c347e",
urls = ["https://raw.githubusercontent.com/docker-library/busybox/a4fac83861d137e9dcfce70b31d0b8fafea9346e/latest/musl/arm32v7/rootfs.tar.gz"],
build_file_content = BUSYBOX_ARCHIVE_BUILD,
)
# To update arm64 busybox binary (#657)
# Get the latest commit hash from dist-arm64v8 branch of docker-library repo. You can also view it
# at https://github.com/docker-library/official-images/blob/master/library/busybox
# Substitute it in the link: https://github.com/docker-library/busybox/raw/<latest-commit-hash>/latest/musl/busybox.tar.gz
# Update the sha256 value. Since github api doesn't give sha256 value, it can be obtained using sha256sum command.
http_archive(
name = "busybox_arm64",
sha256 = "07b73337cea2a5d87c21278f251e1e742e253232ebd035d6a4a6d2cb4612e462",
urls = ["https://raw.githubusercontent.com/docker-library/busybox/f30de561de9bf38da10058620e9c4c383ec8a905/latest/musl/arm64v8/rootfs.tar.gz"],
build_file_content = BUSYBOX_ARCHIVE_BUILD,
)
# To update s390x busybox binary
# Get the latest commit hash from dist-s390x branch of docker-library repo. You can also view it
# at https://github.com/docker-library/official-images/blob/master/library/busybox
# Substitute it in the link: https://github.com/docker-library/busybox/raw/<latest-commit-hash>/latest/musl/s390x/rootfs.tar.gz
# Update the sha256 value. Since github api doesn't give sha256 value, it can be obtained using sha256sum command.
http_archive(
name = "busybox_s390x",
sha256 = "3bd977f75e22a5e164e2e6ac3556cee55053022e9ad8bba89614e7a8680791d1",
urls = ["https://raw.githubusercontent.com/docker-library/busybox/87c948a4fac6f84195e795486a8d650b0c9cc10d/latest/musl/s390x/rootfs.tar.gz"],
build_file_content = BUSYBOX_ARCHIVE_BUILD,
)
# To update ppc64le busybox binary (#723)
# Get the latest commit hash from dist-ppc64le branch of docker-library repo. You can also view it
# at https://github.com/docker-library/official-images/blob/master/library/busybox
# Substitute it in the link: https://github.com/docker-library/busybox/raw/<latest-commit-hash>/latest/musl/busybox.tar.gz
# Update the sha256 value. Since github api doesn't give sha256 value, it can be obtained using sha256sum command.
http_archive(
name = "busybox_ppc64le",
sha256 = "f3f5d636887b8d56aabe29047a58ef074511f5252c722b1e476b7f79fd35fb7f",
urls = ["https://raw.githubusercontent.com/docker-library/busybox/149c39d6036d77a55679c6b9e0c946ba2ad38555/latest/musl/ppc64le/rootfs.tar.gz"],
build_file_content = BUSYBOX_ARCHIVE_BUILD,
)
# To update riscv64 busybox binary
# Get the latest commit hash from dist-riscv64 branch of docker-library repo. You can also view it
# at https://github.com/docker-library/official-images/blob/master/library/busybox
# Substitute it in the link: https://github.com/docker-library/busybox/raw/<latest-commit-hash>/latest/musl/busybox.tar.gz
# Update the sha256 value. Since github api doesn't give sha256 value, it can be obtained using sha256sum command.
http_archive(
name = "busybox_riscv64",
sha256 = "17737180c892fb9eac75524ae789040f0b01415e6e05e063bc2b8f982b340d97",
urls = ["https://raw.githubusercontent.com/docker-library/busybox/eabe7cf678f2abbc0477a8603428519771ede877/latest/musl/riscv64/rootfs.tar.gz"],
build_file_content = BUSYBOX_ARCHIVE_BUILD,
)
return module_ctx.extension_metadata(
root_module_direct_deps = [
"busybox_amd64",
"busybox_arm",
"busybox_arm64",
"busybox_s390x",
"busybox_ppc64le",
"busybox_riscv64",
],
root_module_direct_dev_deps = [],
)
_archive = tag_class(attrs = {})
busybox = module_extension(
implementation = _busybox_impl,
tag_classes = {
"archive": _archive,
},
)
================================================
FILE: private/extensions/node.bzl
================================================
"node"
BUILD_TMPL = """\
# GENERATED BY node_archive.bzl
load("@distroless//private/pkg:debian_spdx.bzl", "debian_spdx")
load("@distroless//private/util:merge_providers.bzl", "merge_providers")
load("@distroless//private/util:tar.bzl", "tar")
tar(
name = "data",
extension = "tar.gz",
srcs = glob(
[
"output/bin/node",
"output/LICENSE",
],
),
package_dir = "/nodejs",
strip_prefix = "external/{canonical_name}/output"
)
tar(
name = "_control",
extension = "tar.gz",
srcs = ["control"]
)
debian_spdx(
name = "spdx",
control = ":_control",
data = ":data",
package_name = "{package_name}",
spdx_id = "{spdx_id}",
sha256 = "{sha256}",
urls = [{urls}]
)
merge_providers(
name = "{name}",
srcs = [":data", ":spdx"],
visibility = ["//visibility:public"],
)
"""
def _impl(rctx):
rctx.report_progress("Fetching {}".format(rctx.attr.package_name))
rctx.download_and_extract(
url = rctx.attr.urls,
sha256 = rctx.attr.sha256,
type = rctx.attr.type,
stripPrefix = rctx.attr.strip_prefix,
output = "output",
)
rctx.template(
"control",
rctx.attr.control,
substitutions = {
"{{VERSION}}": rctx.attr.version,
"{{ARCHITECTURE}}": rctx.attr.architecture,
"{{SHA256}}": rctx.attr.sha256,
},
)
rctx.file(
"BUILD.bazel",
content = BUILD_TMPL.format(
canonical_name = rctx.attr.name,
name = rctx.attr.name.split("~")[-1],
package_name = rctx.attr.package_name,
spdx_id = rctx.attr.name,
urls = ",".join(['"%s"' % url for url in rctx.attr.urls]),
sha256 = rctx.attr.sha256,
),
)
node_archive = repository_rule(
implementation = _impl,
attrs = {
"urls": attr.string_list(mandatory = True),
"sha256": attr.string(mandatory = True),
"type": attr.string(default = ".tar.gz"),
"strip_prefix": attr.string(),
"package_name": attr.string(default = "nodejs"),
"version": attr.string(mandatory = True),
"architecture": attr.string(mandatory = True),
# control is only used to populate the sbom, see https://github.com/GoogleContainerTools/distroless/issues/1373
# for why writing debian control files to the image is incompatible with scanners.
"control": attr.label(),
},
)
def _node_impl(module_ctx):
mod = module_ctx.modules[0]
if len(module_ctx.modules) > 1:
fail("node.archive should be called only once")
if not mod.is_root:
fail("node.archive should be called from root module only.")
# Node (https://nodejs.org/en/about/releases/)
# Follow Node's maintainence schedule and support all LTS versions that are not end of life
node_archive(
name = "nodejs20_amd64",
sha256 = "6362e50804cdcc110592201f67beda93bcd702fdcbe1c42840a50d590e3af0ce",
strip_prefix = "node-v20.20.1-linux-x64/",
urls = ["https://nodejs.org/dist/v20.20.1/node-v20.20.1-linux-x64.tar.gz"],
version = "20.20.1",
architecture = "amd64",
control = "//nodejs:control",
)
node_archive(
name = "nodejs20_arm64",
sha256 = "d6947e10ddc124284aee92981cc739cd4581a6b4bf89520792f2b582600195fa",
strip_prefix = "node-v20.20.1-linux-arm64/",
urls = ["https://nodejs.org/dist/v20.20.1/node-v20.20.1-linux-arm64.tar.gz"],
version = "20.20.1",
architecture = "arm64",
control = "//nodejs:control",
)
node_archive(
name = "nodejs20_arm",
sha256 = "6d7b0f1f3a88004b251fd4d2ee2f5c4faca15587a3cdaaefae11e655b9e01cde",
strip_prefix = "node-v20.20.1-linux-armv7l/",
urls = ["https://nodejs.org/dist/v20.20.1/node-v20.20.1-linux-armv7l.tar.gz"],
version = "20.20.1",
architecture = "arm",
control = "//nodejs:control",
)
node_archive(
name = "nodejs20_ppc64le",
sha256 = "d885a22c1a08bd2d2f91c290770f3a9762075c4ec222fcd9d9fc921b0c8294b9",
strip_prefix = "node-v20.20.1-linux-ppc64le/",
urls = ["https://nodejs.org/dist/v20.20.1/node-v20.20.1-linux-ppc64le.tar.gz"],
version = "20.20.1",
architecture = "ppc64le",
control = "//nodejs:control",
)
node_archive(
name = "nodejs20_s390x",
sha256 = "7f0688d98fce89a9d7c623a3b0d2eab8374333f31461c8cb8b9d34712c45a1ba",
strip_prefix = "node-v20.20.1-linux-s390x/",
urls = ["https://nodejs.org/dist/v20.20.1/node-v20.20.1-linux-s390x.tar.gz"],
version = "20.20.1",
architecture = "s390x",
control = "//nodejs:control",
)
node_archive(
name = "nodejs22_amd64",
sha256 = "07c8aafa60644fb81adefa1ee7da860eb1920851ffdc9a37020ab0be47fbc10e",
strip_prefix = "node-v22.22.1-linux-x64/",
urls = ["https://nodejs.org/dist/v22.22.1/node-v22.22.1-linux-x64.tar.gz"],
version = "22.22.1",
architecture = "amd64",
control = "//nodejs:control",
)
node_archive(
name = "nodejs22_arm64",
sha256 = "1d1690e9aba47e887a275abc6d8f7317e571a0700deaef493f768377e99155f5",
strip_prefix = "node-v22.22.1-linux-arm64/",
urls = ["https://nodejs.org/dist/v22.22.1/node-v22.22.1-linux-arm64.tar.gz"],
version = "22.22.1",
architecture = "arm64",
control = "//nodejs:control",
)
node_archive(
name = "nodejs22_arm",
sha256 = "2b592d21609ef299d1e3918bb806ed62ba715d4109b0f8ec11b132af9fa42d70",
strip_prefix = "node-v22.22.1-linux-armv7l/",
urls = ["https://nodejs.org/dist/v22.22.1/node-v22.22.1-linux-armv7l.tar.gz"],
version = "22.22.1",
architecture = "arm",
control = "//nodejs:control",
)
node_archive(
name = "nodejs22_ppc64le",
sha256 = "18f9ab7da4f3a04ec213590b14e5d78b60bfb5c6b8bf53541e7eaf1adf9d270a",
strip_prefix = "node-v22.22.1-linux-ppc64le/",
urls = ["https://nodejs.org/dist/v22.22.1/node-v22.22.1-linux-ppc64le.tar.gz"],
version = "22.22.1",
architecture = "ppc64le",
control = "//nodejs:control",
)
node_archive(
name = "nodejs22_s390x",
sha256 = "6128f9d54a1b43258144d7ac074a82d3b6c96d8e9cb3a8e14e4722a66990adbe",
strip_prefix = "node-v22.22.1-linux-s390x/",
urls = ["https://nodejs.org/dist/v22.22.1/node-v22.22.1-linux-s390x.tar.gz"],
version = "22.22.1",
architecture = "s390x",
control = "//nodejs:control",
)
node_archive(
name = "nodejs24_amd64",
sha256 = "dbf5b8665dec15e59e6359a517fefb47b23fdb9152d8def975b9bca3dfc6d355",
strip_prefix = "node-v24.14.0-linux-x64/",
urls = ["https://nodejs.org/dist/v24.14.0/node-v24.14.0-linux-x64.tar.gz"],
version = "24.14.0",
architecture = "amd64",
control = "//nodejs:control",
)
node_archive(
name = "nodejs24_arm64",
sha256 = "f44740cd218de8127f1c44c41510a3a740fa5c9c8d1cdce1c3bedada79f3cde7",
strip_prefix = "node-v24.14.0-linux-arm64/",
urls = ["https://nodejs.org/dist/v24.14.0/node-v24.14.0-linux-arm64.tar.gz"],
version = "24.14.0",
architecture = "arm64",
control = "//nodejs:control",
)
node_archive(
name = "nodejs24_ppc64le",
sha256 = "83b263f9c2ea946c0c4a15c3caea6470dc49fe0beb6f33dfd29aa9128250637a",
strip_prefix = "node-v24.14.0-linux-ppc64le/",
urls = ["https://nodejs.org/dist/v24.14.0/node-v24.14.0-linux-ppc64le.tar.gz"],
version = "24.14.0",
architecture = "ppc64le",
control = "//nodejs:control",
)
node_archive(
name = "nodejs24_s390x",
sha256 = "8fa220a1f7b7769605c2e929fdbf736822997bf4cf88a3db05188eabd7712328",
strip_prefix = "node-v24.14.0-linux-s390x/",
urls = ["https://nodejs.org/dist/v24.14.0/node-v24.14.0-linux-s390x.tar.gz"],
version = "24.14.0",
architecture = "s390x",
control = "//nodejs:control",
)
return module_ctx.extension_metadata(
root_module_direct_deps = [
"nodejs20_amd64",
"nodejs20_arm64",
"nodejs20_arm",
"nodejs20_ppc64le",
"nodejs20_s390x",
"nodejs22_amd64",
"nodejs22_arm64",
"nodejs22_arm",
"nodejs22_ppc64le",
"nodejs22_s390x",
"nodejs24_amd64",
"nodejs24_arm64",
"nodejs24_ppc64le",
"nodejs24_s390x",
],
root_module_direct_dev_deps = [],
)
_archive = tag_class(attrs = {})
node = module_extension(
implementation = _node_impl,
tag_classes = {
"archive": _archive,
},
)
================================================
FILE: private/extensions/version.bzl
================================================
"generates version information from lockfiles"
# buildifier: disable=bzl-visibility
load("@rules_distroless//apt/private:version.bzl", _version = "version")
_VERSIONS_TMPL = """\
"versions repo"
# AUTO GENERATED. DO NOT EDIT.
_versions = {}
# buildifier: disable=function-docstring
def version(dist, arch, name):
if dist not in _versions:
fail("unknown dist {{}}".format(dist))
if name not in _versions[dist]:
fail("unknown package {{}}".format(name))
if arch not in _versions[dist][name]:
fail("unknown arch {{}}".format(arch))
return struct(**_versions[dist][name][arch])
"""
def _parse_version(raw):
(epoch, upstream, revision) = _version.parse(raw)
return struct(raw = raw, epoch = epoch, upstream = upstream, revision = revision)
def _version_repo_impl(rctx):
rctx.file("versions.bzl", _VERSIONS_TMPL.format(json.decode(rctx.attr.versions)))
rctx.file("BUILD.bazel", "exports_files(['versions.bzl'])")
version_repo = repository_rule(
implementation = _version_repo_impl,
attrs = {
"versions": attr.string(),
},
)
def _version_impl(module_ctx):
versions = dict()
for mod in module_ctx.modules:
for from_lock in mod.tags.from_lock:
lock = json.decode(module_ctx.read(from_lock.lock))
repo = from_lock.repo_name
if lock["version"] != 1:
fail("unknown lock version")
if repo not in versions:
versions[repo] = dict()
for pkg in lock["packages"]:
if pkg["name"] not in versions[repo]:
versions[repo][pkg["name"]] = dict()
versions[repo][pkg["name"]][pkg["arch"]] = _parse_version(pkg["version"])
version_repo(
name = "versions",
versions = json.encode(versions),
)
return module_ctx.extension_metadata(
root_module_direct_deps = ["versions"],
root_module_direct_dev_deps = [],
)
_from_lock = tag_class(attrs = {
"repo_name": attr.string(mandatory = True),
"lock": attr.label(mandatory = True),
})
version = module_extension(
implementation = _version_impl,
tag_classes = {
"from_lock": _from_lock,
},
)
================================================
FILE: private/oci/BUILD.bazel
================================================
exports_files(["sign_and_push.sh.tpl"])
================================================
FILE: private/oci/cc_image.bzl
================================================
"cc_image rule for creating C++ container images"
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
load("@rules_oci//oci:defs.bzl", "oci_image")
load("//private/util:tar.bzl", "tar")
def cc_image(name, srcs, base):
cc_binary(
name = "%s_binary" % name,
srcs = srcs,
)
tar(
name = "%s_layer" % name,
extension = "tar.gz",
srcs = [
":%s_binary" % name,
],
)
oci_image(
name = name,
base = base,
entrypoint = [
"/%s_binary" % name,
],
tars = [
":%s_layer" % name,
],
)
================================================
FILE: private/oci/defs.bzl
================================================
load(":cc_image.bzl", _cc_image = "cc_image")
load(":go_image.bzl", _go_image = "go_image")
load(":java_image.bzl", _java_image = "java_image")
load(":rust_image.bzl", _rust_image = "rust_image")
load(":sign_and_push.bzl", _sign_and_push_all = "sign_and_push_all")
java_image = _java_image
cc_image = _cc_image
rust_image = _rust_image
go_image = _go_image
sign_and_push_all = _sign_and_push_all
================================================
FILE: private/oci/digest.bzl
================================================
"generate digest for oci_image and oci_image_index"
load("@aspect_bazel_lib//lib:copy_file.bzl", "copy_file")
load("@aspect_bazel_lib//lib:directory_path.bzl", "directory_path")
load("@aspect_bazel_lib//lib:jq.bzl", "jq")
# Normally we'd use the `.digest` target that rules_oci creates for every oci_image but
# we also use oci_image_index which does not have a digest target. This was fixed in
# https://github.com/bazel-contrib/rules_oci/pull/742 but it on the 2.x releases of rules_oci
# TODO: Remove this once we upgrade to rules_oci 2.x
def digest(name, image, **kwargs):
# `oci_image_rule` and `oci_image_index_rule` produce a directory as default output.
# Label for the [name]/index.json file
directory_path(
name = "_{}_index_json".format(name),
directory = image,
path = "index.json",
**kwargs
)
copy_file(
name = "_{}_index_json_cp".format(name),
src = "_{}_index_json".format(name),
out = "_{}_index.json".format(name),
**kwargs
)
# Matches the [name].digest target produced by rules_docker container_image
jq(
name = name,
args = ["--raw-output"],
srcs = ["_{}_index.json".format(name)],
filter = """.manifests[0].digest""",
out = name + ".json.sha256", # path chosen to match rules_docker for easy migration
**kwargs
)
================================================
FILE: private/oci/go_image.bzl
================================================
"go_image rule for creating Go container images"
load("@rules_go//go:def.bzl", "go_binary")
load("@rules_oci//oci:defs.bzl", "oci_image")
load("//private/util:tar.bzl", "tar")
def go_image(name, srcs, base, arch = "amd64", os = "linux"):
go_binary(
name = "{}_binary".format(name),
srcs = srcs,
goarch = arch,
goos = os,
pure = "on",
)
tar(
name = "{}_layer".format(name),
extension = "tar.gz",
srcs = ["{}_binary".format(name)],
)
oci_image(
name = name,
base = base,
entrypoint = ["/{}_binary".format(name)],
tars = [
"{}_layer".format(name),
],
)
================================================
FILE: private/oci/java_image.bzl
================================================
"java_image rule for creating Java container images"
load("@rules_oci//oci:defs.bzl", "oci_image")
load("//private/util:tar.bzl", "tar")
def java_image(name, srcs, main_class, base):
native.java_binary(
name = "%s_binary" % name,
srcs = srcs,
main_class = main_class,
)
tar(
name = "%s_layer" % name,
extension = "tar.gz",
srcs = [
":%s_binary" % name,
],
)
oci_image(
name = name,
base = base,
cmd = [
"/%s_binary.jar" % name,
],
tars = [
":%s_layer" % name,
],
)
================================================
FILE: private/oci/rust_image.bzl
================================================
"rust_image rule for creating Rust container images"
load("@rules_oci//oci:defs.bzl", "oci_image")
load("@rules_rust//rust:defs.bzl", "rust_binary")
load("//private/util:tar.bzl", "tar")
def rust_image(name, srcs, base, tags):
rust_binary(
name = "%s_binary" % name,
srcs = srcs,
tags = tags,
)
tar(
name = "%s_layer" % name,
extension = "tar.gz",
srcs = [
":%s_binary" % name,
],
tags = tags,
)
oci_image(
name = name,
base = base,
entrypoint = [
"/%s_binary" % name,
],
tars = [
":%s_layer" % name,
],
tags = tags,
)
================================================
FILE: private/oci/sign_and_push.bzl
================================================
"rules for signing, attesting and pushing images"
load("@bazel_skylib//rules:write_file.bzl", "write_file")
load("//private/pkg:oci_image_spdx.bzl", "oci_image_spdx")
load(":digest.bzl", "digest")
PUSH_AND_SIGN_CMD = """\
# Push {IMAGE}
repository="$(stamp "{REPOSITORY}")"
tag="$(stamp "{TAG}")"
digest="$(cat {DIGEST})"
echo "Pushing $repository@$digest"
{CRANE} push {IMAGE} "$repository@$digest"
# Check for signature and skip signing/sbom if present
if ! {COSIGN} verify "$repository@$digest" --certificate-oidc-issuer https://accounts.google.com --certificate-identity "${{KEYLESS}}" > /dev/null; then
{COSIGN} attest "$repository@$digest" --predicate "{SBOM}" --type "spdx" --yes
{COSIGN} sign "$repository@$digest" --yes
fi
{CRANE} tag "$repository@$digest" "$tag"
"""
TAG_CMD = """\
# Tag {IMAGE}
from="$(stamp "{FROM}")"
tag="$(stamp "{TAG}")"
{CRANE} tag "$from" "$tag"
"""
def _sign_and_push_impl(ctx):
cmds = []
runfiles = ctx.runfiles(files = ctx.files.targets + [ctx.version_file, ctx.file._crane, ctx.file._cosign])
for (image, target) in ctx.attr.targets.items():
files = target[DefaultInfo].files.to_list()
all_refs = ctx.attr.refs[image]
for ref in all_refs:
repository_and_tag = ref.split(":")
cmds.append(
PUSH_AND_SIGN_CMD.format(
IMAGE = files[0].short_path,
SBOM = files[1].short_path,
DIGEST = files[2].short_path,
CRANE = ctx.file._crane.short_path,
COSIGN = ctx.file._cosign.short_path,
REPOSITORY = repository_and_tag[0],
T
gitextract_vcjsnv1p/
├── .bazelignore
├── .bazelrc
├── .bazelversion
├── .cloudbuild/
│ ├── cloudbuild.yaml
│ ├── lifecycle_tag.sh
│ ├── lifecycle_tag.yaml
│ └── release.sh
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── actionable-debian-cve.md
│ │ ├── actionable-non-debian-cve.md
│ │ └── bug_report.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── buildifier.yaml
│ ├── check-ldconfig.yaml
│ ├── ci.bazelrc
│ ├── ci.yaml
│ ├── config-diff.yaml
│ ├── examples.yaml
│ ├── image-check.yaml
│ ├── scorecards-analysis.yml
│ ├── update-deb-package-non-snapshots.yml
│ ├── update-deb-package-snapshots.yml
│ └── update-node-archives.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .prettierignore
├── BUILD
├── CONTRIBUTING.md
├── LICENSE
├── MODULE.bazel
├── PACKAGE_METADATA.md
├── README.md
├── RELEASES.md
├── SECURITY.md
├── SUPPORT_POLICY.md
├── WORKSPACE
├── base/
│ ├── BUILD
│ ├── README.md
│ ├── base.bzl
│ ├── config.bzl
│ ├── test.sh
│ └── testdata/
│ ├── base.yaml
│ └── debug.yaml
├── cc/
│ ├── BUILD
│ ├── README.md
│ ├── cc.bzl
│ └── config.bzl
├── common/
│ ├── BUILD.bazel
│ └── variables.bzl
├── cosign.pub
├── distro.bzl
├── downloader.cfg
├── examples/
│ ├── BUILD
│ ├── cc/
│ │ ├── BUILD
│ │ ├── Dockerfile
│ │ ├── hello.c
│ │ ├── hello_cc.cc
│ │ └── testdata/
│ │ ├── hello_cc_debian12.yaml
│ │ ├── hello_cc_debian13.yaml
│ │ ├── hello_debian12.yaml
│ │ └── hello_debian13.yaml
│ ├── go/
│ │ ├── BUILD
│ │ ├── Dockerfile
│ │ ├── go.mod
│ │ ├── main.go
│ │ └── main_test.go
│ ├── java/
│ │ ├── BUILD
│ │ ├── Dockerfile
│ │ ├── HelloJava.java
│ │ └── testdata/
│ │ ├── hello_nonroot_debian13.yaml
│ │ └── hello_root_debian13.yaml
│ ├── nodejs/
│ │ ├── BUILD
│ │ ├── Dockerfile
│ │ ├── hello.js
│ │ ├── hello_http.js
│ │ ├── node-express/
│ │ │ ├── Dockerfile
│ │ │ ├── hello_express.js
│ │ │ └── package.json
│ │ ├── package.json
│ │ └── testdata/
│ │ └── hello.yaml
│ ├── nonroot/
│ │ ├── BUILD
│ │ └── testdata/
│ │ ├── user.go
│ │ └── user.yaml
│ ├── python3/
│ │ ├── BUILD
│ │ ├── Dockerfile
│ │ └── hello.py
│ ├── python3-requirements/
│ │ ├── Dockerfile
│ │ ├── README.md
│ │ ├── psutil_example.py
│ │ └── requirements.txt
│ ├── rust/
│ │ ├── .dockerignore
│ │ ├── BUILD
│ │ ├── Cargo.toml
│ │ ├── Dockerfile
│ │ └── src/
│ │ └── main.rs
│ └── test.sh
├── experimental/
│ ├── BUILD
│ └── busybox/
│ ├── BUILD
│ └── commands.bzl
├── java/
│ ├── BUILD
│ ├── README.md
│ ├── config.bzl
│ ├── control
│ ├── java.bzl
│ ├── jre_ver.bzl
│ └── testdata/
│ ├── CheckCerts.java
│ ├── CheckEncoding.java
│ ├── CheckLibharfbuzz.java
│ ├── java17_debian13.yaml
│ ├── java17_debug_debian13.yaml
│ ├── java21_debian13.yaml
│ ├── java21_debug_debian13.yaml
│ ├── java25_debian13.yaml
│ ├── java25_debug_debian13.yaml
│ ├── java_base.yaml
│ ├── java_base_debug.yaml
│ ├── java_certs.yaml
│ ├── java_encoding.yaml
│ └── java_libharfbuzz.yaml
├── knife
├── knife.d/
│ ├── update_java_versions.sh
│ └── update_node_archives.js
├── nodejs/
│ ├── BUILD
│ ├── README.md
│ ├── config.bzl
│ ├── control
│ ├── nodejs.bzl
│ └── testdata/
│ ├── check_certificate.js
│ ├── check_certificate.yaml
│ ├── check_headers.yaml
│ ├── check_npm.yaml
│ ├── nodejs20.yaml
│ ├── nodejs22.yaml
│ └── nodejs24.yaml
├── private/
│ ├── extensions/
│ │ ├── BUILD.bazel
│ │ ├── busybox.bzl
│ │ ├── node.bzl
│ │ └── version.bzl
│ ├── oci/
│ │ ├── BUILD.bazel
│ │ ├── cc_image.bzl
│ │ ├── defs.bzl
│ │ ├── digest.bzl
│ │ ├── go_image.bzl
│ │ ├── java_image.bzl
│ │ ├── rust_image.bzl
│ │ ├── sign_and_push.bzl
│ │ └── sign_and_push.sh.tpl
│ ├── pkg/
│ │ ├── BUILD.bazel
│ │ ├── debian_spdx.bzl
│ │ ├── debian_spdx.go
│ │ ├── oci_image_spdx.bzl
│ │ ├── oci_image_spdx.go
│ │ └── test/
│ │ └── oci_image/
│ │ ├── BUILD.bazel
│ │ ├── fat_image_sbom.spdx.json
│ │ ├── image_amd64.spdx.json
│ │ └── image_arm64.spdx.json
│ ├── repos/
│ │ └── deb/
│ │ ├── BUILD.bazel
│ │ ├── bookworm.lock.json
│ │ ├── bookworm.yaml
│ │ ├── bookworm_python.lock.json
│ │ ├── bookworm_python.yaml
│ │ ├── deb.MODULE.bazel
│ │ ├── package.BUILD.tmpl
│ │ ├── trixie.lock.json
│ │ ├── trixie.yaml
│ │ ├── trixie_adoptium.lock.json
│ │ ├── trixie_adoptium.yaml
│ │ ├── trixie_java.lock.json
│ │ ├── trixie_java.yaml
│ │ ├── trixie_python.lock.json
│ │ └── trixie_python.yaml
│ ├── stamp.bash
│ ├── tools/
│ │ ├── diff/
│ │ │ ├── BUILD.bazel
│ │ │ └── diff.bash
│ │ └── lifecycle/
│ │ ├── BUILD.bazel
│ │ ├── defs.bzl
│ │ ├── tag.bzl
│ │ ├── tag.sh
│ │ └── tag.sh.README.md
│ └── util/
│ ├── BUILD
│ ├── deb.bzl
│ ├── extract.bzl
│ ├── java_cacerts.bzl
│ ├── merge_providers.bzl
│ └── tar.bzl
├── python3/
│ ├── BUILD
│ ├── README.md
│ ├── config.bzl
│ ├── ldconfig/
│ │ ├── ld.so.cache.amd64
│ │ ├── ld.so.cache.arm64
│ │ └── ldconfig.sh
│ ├── ldconfig.bzl
│ ├── python.bzl
│ └── testdata/
│ ├── debian12.yaml
│ ├── debian13.yaml
│ └── python3.yaml
└── static/
├── BUILD
├── config.bzl
├── nsswitch.conf
├── static.bzl
└── testdata/
├── check_certs.go
├── debian12.yaml
├── debian13.yaml
├── debug.yaml
└── static.yaml
SYMBOL INDEX (26 symbols across 15 files)
FILE: examples/cc/hello.c
function main (line 3) | int main() {
FILE: examples/cc/hello_cc.cc
function main (line 3) | int main() {
FILE: examples/go/main.go
function main (line 19) | func main() {
FILE: examples/go/main_test.go
function Test1 (line 7) | func Test1(t *testing.T) {
FILE: examples/java/HelloJava.java
class HelloJava (line 3) | public class HelloJava {
method main (line 4) | public static void main(String[] args) {
FILE: examples/nonroot/testdata/user.go
function main (line 25) | func main() {
FILE: examples/python3-requirements/psutil_example.py
function mib (line 8) | def mib(total_bytes):
function main (line 13) | def main():
FILE: examples/python3/hello.py
function main (line 24) | def main(args):
FILE: examples/rust/src/main.rs
function main (line 1) | fn main() {
FILE: java/testdata/CheckCerts.java
class CheckCerts (line 7) | public class CheckCerts {
method main (line 9) | public static void main(String[] args) throws IOException {
FILE: java/testdata/CheckEncoding.java
class CheckEncoding (line 6) | public class CheckEncoding {
method main (line 8) | public static void main(String[] args) {
FILE: java/testdata/CheckLibharfbuzz.java
class CheckLibharfbuzz (line 6) | public class CheckLibharfbuzz {
method main (line 7) | public static void main(String[] args) {
FILE: private/pkg/debian_spdx.go
function pkgId (line 18) | func pkgId(id string) string {
function parseDebControl (line 28) | func parseDebControl(r io.Reader) (map[string]string, error) {
function main (line 72) | func main() {
FILE: private/pkg/oci_image_spdx.go
function pkgId (line 15) | func pkgId(id string) string {
type ArrayFlags (line 24) | type ArrayFlags
method String (line 26) | func (i *ArrayFlags) String() string {
method Set (line 30) | func (i *ArrayFlags) Set(value string) error {
function main (line 35) | func main() {
FILE: static/testdata/check_certs.go
function main (line 23) | func main() {
Condensed preview — 204 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (467K chars).
[
{
"path": ".bazelignore",
"chars": 0,
"preview": ""
},
{
"path": ".bazelrc",
"chars": 1664,
"preview": "# Bazel settings that apply to this repository.\n# Take care to document any settings that you expect users to apply.\n# S"
},
{
"path": ".bazelversion",
"chars": 5,
"preview": "7.4.0"
},
{
"path": ".cloudbuild/cloudbuild.yaml",
"chars": 583,
"preview": "timeout: 10800s # 3 hours\n\noptions:\n machineType: E2_HIGHCPU_32\n logging: CLOUD_LOGGING_ONLY\n\nartifacts:\n objects:\n "
},
{
"path": ".cloudbuild/lifecycle_tag.sh",
"chars": 1074,
"preview": "#!/usr/bin/env bash\nset -o errexit -o nounset -o xtrace -o pipefail\n\nBAZELISK_VERSION=\"1.27.0\"\nBAZELISK_SHA256=\"e1508323"
},
{
"path": ".cloudbuild/lifecycle_tag.yaml",
"chars": 349,
"preview": "timeout: 1800s # 30 minutes\n\noptions:\n machineType: E2_MEDIUM\n logging: CLOUD_LOGGING_ONLY\n\nsteps:\n - name: gcr.io/cl"
},
{
"path": ".cloudbuild/release.sh",
"chars": 1565,
"preview": "#!/usr/bin/env bash\nset -o errexit -o xtrace -o pipefail\n\nBAZEL_REMOTE_VERSION=\"2.6.1\"\nBAZEL_REMOTE_SHA256=\"025d53aeb03a"
},
{
"path": ".github/ISSUE_TEMPLATE/actionable-debian-cve.md",
"chars": 652,
"preview": "---\nname: Actionable debian CVE\nabout: A CVE from a debian package where a fix is available\ntitle: ''\nlabels: ''\nassigne"
},
{
"path": ".github/ISSUE_TEMPLATE/actionable-non-debian-cve.md",
"chars": 336,
"preview": "---\nname: Actionable non debian CVE\nabout: A CVE is an package imported into distroless that is not from debian\ntitle: '"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 472,
"preview": "---\nname: Bug report\nabout: There is an issue with how distroless is built\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**De"
},
{
"path": ".github/dependabot.yml",
"chars": 291,
"preview": "version: 2\nupdates:\n- package-ecosystem: \"gomod\"\n directory: \"/\"\n schedule:\n interval: \"daily\"\n- package-ecosystem:"
},
{
"path": ".github/workflows/buildifier.yaml",
"chars": 547,
"preview": "name: Buildifier\n\non:\n pull_request:\n branches: [\"main\"]\n\npermissions:\n contents: read\n\njobs:\n autoformat:\n nam"
},
{
"path": ".github/workflows/check-ldconfig.yaml",
"chars": 467,
"preview": "name: Check ldconfig cache\n\non:\n pull_request:\n branches: [\"main\"]\n push:\n branches: [\"main\"]\n\npermissions:\n co"
},
{
"path": ".github/workflows/ci.bazelrc",
"chars": 201,
"preview": "build --announce_rc\nbuild --repository_cache=~/.cache/bazel-repo\n# intentionally disable build cache, it gets too big\n# "
},
{
"path": ".github/workflows/ci.yaml",
"chars": 2327,
"preview": "name: CI\n\non:\n workflow_dispatch: # Allow manual runs.\n pull_request:\n branches: [\"main\"]\n push:\n branches: [\"m"
},
{
"path": ".github/workflows/config-diff.yaml",
"chars": 2757,
"preview": "name: Config Check\n\non:\n workflow_dispatch:\n pull_request:\n branches: [\"main\"]\n\nconcurrency:\n group: ${{ github.wo"
},
{
"path": ".github/workflows/examples.yaml",
"chars": 249,
"preview": "name: Examples\n\non:\n pull_request:\n branches: [ 'main' ]\n\npermissions:\n contents: read\n\njobs:\n examples:\n name:"
},
{
"path": ".github/workflows/image-check.yaml",
"chars": 3983,
"preview": "name: Image Check\n\non:\n workflow_dispatch:\n pull_request:\n branches: [\"main\"]\n\nconcurrency:\n group: ${{ github.wor"
},
{
"path": ".github/workflows/scorecards-analysis.yml",
"chars": 1593,
"preview": "name: Scorecards supply-chain security\non: \n # Only the default branch is supported.\n branch_protection_rule:\n schedu"
},
{
"path": ".github/workflows/update-deb-package-non-snapshots.yml",
"chars": 2013,
"preview": "name: update-non-snapshots\non:\n # will send emails to last editor of this cron syntax (distroless-bot)\n schedule:\n "
},
{
"path": ".github/workflows/update-deb-package-snapshots.yml",
"chars": 2362,
"preview": "name: update-snapshots\non:\n # will send emails to last editor of this cron syntax (distroless-bot)\n schedule:\n - cr"
},
{
"path": ".github/workflows/update-node-archives.yml",
"chars": 1844,
"preview": "name: update-node-packages\non:\n # will send emails to last editor of this cron syntax (distroless-bot)\n schedule:\n "
},
{
"path": ".gitignore",
"chars": 411,
"preview": "# Ignore backup files.\n*~\n# Ignore Vim swap files.\n.*.swp\n# Ignore files generated by IDEs.\n/.classpath\n/.factorypath\n.i"
},
{
"path": ".pre-commit-config.yaml",
"chars": 974,
"preview": "# See CONTRIBUTING.md for instructions.\n# See https://pre-commit.com for more information\n# See https://pre-commit.com/h"
},
{
"path": ".prettierignore",
"chars": 28,
"preview": "private/pkg/test/oci_image/*"
},
{
"path": "BUILD",
"chars": 10257,
"preview": "load(\"//base:config.bzl\", \"BASE_ARCHITECTURES\", \"BASE_DISTROS\")\nload(\"//cc:config.bzl\", \"CC_ARCHITECTURES\", \"CC_DISTROS\""
},
{
"path": "CONTRIBUTING.md",
"chars": 2744,
"preview": "# How to become a contributor and submit your own code\n\n## Contributor License Agreements\n\nWe'd love to accept your patc"
},
{
"path": "LICENSE",
"chars": 11358,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "MODULE.bazel",
"chars": 2045,
"preview": "\"distroless\"\n\nmodule(name = \"distroless\")\n\nbazel_dep(name = \"bazel_skylib\", version = \"1.8.2\")\nbazel_dep(name = \"aspect_"
},
{
"path": "PACKAGE_METADATA.md",
"chars": 2568,
"preview": "# Package Metadata\n\n## `dpkg` Metadata Structure Standard\n\n### Overview\n\nThis document defines the standard structure fo"
},
{
"path": "README.md",
"chars": 11379,
"preview": "# \"Distroless\" Container Images.\n\n[\n\ncosign_register_toolchains(name = \"oci_cosign"
},
{
"path": "base/BUILD",
"chars": 964,
"preview": "load(\":base.bzl\", \"base_image\", \"base_image_index\", \"base_nossl_image\", \"base_nossl_image_index\")\nload(\":config.bzl\", \"B"
},
{
"path": "base/README.md",
"chars": 1293,
"preview": "# Documentation for `gcr.io/distroless/base`, `gcr.io/distroless/base-nossl` and `gcr.io/distroless/static`\n\n## Image Co"
},
{
"path": "base/base.bzl",
"chars": 3674,
"preview": "\"defines a function to replicate the container images for different distributions\"\n\nload(\"@container_structure_test//:de"
},
{
"path": "base/config.bzl",
"chars": 497,
"preview": "BASE_DISTROS = [\"debian12\", \"debian13\"]\nBASE_ARCHITECTURES = {\n \"debian12\": [\"amd64\", \"arm64\", \"arm\", \"s390x\", \"ppc64"
},
{
"path": "base/test.sh",
"chars": 180,
"preview": "$RUNFILES_DIR/runtimes_common/structure_tests/ext_run.sh \\\n -i bazel/base:cc \\\n -t $RUNFILES_DIR/distroless/base/base."
},
{
"path": "base/testdata/base.yaml",
"chars": 1631,
"preview": "schemaVersion: \"1.0.0\"\nfileExistenceTests:\n# Basic FS sanity checks.\n- name: root\n path: '/'\n shouldExist: true\n- name"
},
{
"path": "base/testdata/debug.yaml",
"chars": 232,
"preview": "schemaVersion: \"1.0.0\"\nfileExistenceTests:\n# Basic FS sanity checks.\n- name: busybox\n path: '/busybox'\n shouldExist: t"
},
{
"path": "cc/BUILD",
"chars": 486,
"preview": "load(\":cc.bzl\", \"cc_image\", \"cc_image_index\")\nload(\":config.bzl\", \"CC_ARCHITECTURES\", \"CC_DISTROS\", \"CC_PACKAGES\")\n\npack"
},
{
"path": "cc/README.md",
"chars": 406,
"preview": "# Documentation for `gcr.io/distroless/cc`\n\n## Image Contents\n\nThis image contains a minimal Linux, glibc runtime for \"m"
},
{
"path": "cc/cc.bzl",
"chars": 1236,
"preview": "load(\"@rules_oci//oci:defs.bzl\", \"oci_image\", \"oci_image_index\")\nload(\"//common:variables.bzl\", \"DEBUG_MODE\", \"USERS\")\nl"
},
{
"path": "cc/config.bzl",
"chars": 439,
"preview": "CC_DISTROS = [\"debian12\", \"debian13\"]\nCC_ARCHITECTURES = {\n \"debian12\": [\"amd64\", \"arm64\", \"arm\", \"s390x\", \"ppc64le\"]"
},
{
"path": "common/BUILD.bazel",
"chars": 3804,
"preview": "load(\"@aspect_bazel_lib//lib:tar.bzl\", \"tar\")\nload(\"@rules_distroless//distroless:defs.bzl\", \"cacerts\", \"group\", \"home\","
},
{
"path": "common/variables.bzl",
"chars": 608,
"preview": "\"common variables\"\n\ndef quote(str):\n return '''\"{}\"'''.format(str)\n\nOS_RELEASE = dict(\n PRETTY_NAME = \"Distroless\""
},
{
"path": "cosign.pub",
"chars": 178,
"preview": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWZzVzkb8A+DbgDpaJId/bOmV8n7Q\nOqxYbK0Iro6GzSmOzxkn+N2AKawL"
},
{
"path": "distro.bzl",
"chars": 254,
"preview": "VERSIONS = [\n (\"debian12\", \"bookworm\", \"12\"),\n (\"debian13\", \"trixie\", \"13\"),\n]\n\nVARIANTS = {\n \"arm\": \"v7\",\n "
},
{
"path": "downloader.cfg",
"chars": 140,
"preview": "rewrite (mirrors\\.kernel\\.org)/gnu/(.*) https://ftp.gnu.org/gnu/$2\nrewrite (mirrors\\.kernel\\.org)/gnu/(.*) https://ftpmi"
},
{
"path": "examples/BUILD",
"chars": 54,
"preview": "package(default_visibility = [\"//visibility:public\"])\n"
},
{
"path": "examples/cc/BUILD",
"chars": 1192,
"preview": "# Public notice: this file is for internal documentation, testing, and\n# reference only. Note that repo maintainers can "
},
{
"path": "examples/cc/Dockerfile",
"chars": 161,
"preview": "FROM gcc:6 AS build-env\nCOPY . /app\nWORKDIR /app\nRUN cc hello.c -o hello\n\nFROM gcr.io/distroless/cc\nCOPY --from=build-en"
},
{
"path": "examples/cc/hello.c",
"chars": 89,
"preview": "#include <stdio.h>\n\nint main() {\n printf(\"Hello from distroless C!\\n\");\n return 0;\n}\n"
},
{
"path": "examples/cc/hello_cc.cc",
"chars": 108,
"preview": "#include <iostream>\n\nint main() {\n std::cout << \"Hello from distroless C++!\" << std::endl;\n return 0;\n}\n"
},
{
"path": "examples/cc/testdata/hello_cc_debian12.yaml",
"chars": 152,
"preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n - name: hello_cc\n command: ['/hello_cc_debian12_binary']\n expectedOutput: ["
},
{
"path": "examples/cc/testdata/hello_cc_debian13.yaml",
"chars": 152,
"preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n - name: hello_cc\n command: ['/hello_cc_debian13_binary']\n expectedOutput: ["
},
{
"path": "examples/cc/testdata/hello_debian12.yaml",
"chars": 142,
"preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n - name: hello\n command: ['/hello_debian12_binary']\n expectedOutput: ['Hello"
},
{
"path": "examples/cc/testdata/hello_debian13.yaml",
"chars": 142,
"preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n - name: hello\n command: ['/hello_debian13_binary']\n expectedOutput: ['Hello"
},
{
"path": "examples/go/BUILD",
"chars": 638,
"preview": "# Public notice: this file is for internal documentation, testing, and\n# reference only. Note that repo maintainers can "
},
{
"path": "examples/go/Dockerfile",
"chars": 235,
"preview": "FROM golang:1.22 as build\n\nWORKDIR /go/src/app\nCOPY . .\n\nRUN go mod download\nRUN go vet -v\nRUN go test -v\n\nRUN CGO_ENABL"
},
{
"path": "examples/go/go.mod",
"chars": 71,
"preview": "module github.com/GoogleContainerTools/distroless/examples/go\n\ngo 1.18\n"
},
{
"path": "examples/go/main.go",
"chars": 678,
"preview": "// Copyright 2017 Google Inc. All rights reserved.\n\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n/"
},
{
"path": "examples/go/main_test.go",
"chars": 66,
"preview": "package main\n\nimport (\n\t\"testing\"\n)\n\nfunc Test1(t *testing.T) {\n}\n"
},
{
"path": "examples/java/BUILD",
"chars": 1266,
"preview": "# Public notice: this file is for internal documentation, testing, and\n# reference only. Note that repo maintainers can "
},
{
"path": "examples/java/Dockerfile",
"chars": 261,
"preview": "FROM eclipse-temurin:17-jdk AS build-env\nCOPY . /app/examples\nWORKDIR /app\nRUN javac examples/*.java\nRUN jar cfe main.ja"
},
{
"path": "examples/java/HelloJava.java",
"chars": 140,
"preview": "package examples;\n\npublic class HelloJava {\n public static void main(String[] args) {\n System.out.println(\"Hel"
},
{
"path": "examples/java/testdata/hello_nonroot_debian13.yaml",
"chars": 155,
"preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n - name: hello\n command: ['/usr/bin/java', '-cp', '/*', 'examples.HelloJava']\n "
},
{
"path": "examples/java/testdata/hello_root_debian13.yaml",
"chars": 155,
"preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n - name: hello\n command: ['/usr/bin/java', '-cp', '/*', 'examples.HelloJava']\n "
},
{
"path": "examples/nodejs/BUILD",
"chars": 1774,
"preview": "# Public notice: this file is for internal documentation, testing, and\n# reference only. Note that repo maintainers can "
},
{
"path": "examples/nodejs/Dockerfile",
"chars": 178,
"preview": "FROM node:22 AS build-env\nCOPY . /app\nWORKDIR /app\n\nRUN npm ci --omit=dev\n\nFROM gcr.io/distroless/nodejs22-debian13\nCOPY"
},
{
"path": "examples/nodejs/hello.js",
"chars": 29,
"preview": "\nconsole.log(\"Hello World\");\n"
},
{
"path": "examples/nodejs/hello_http.js",
"chars": 482,
"preview": "// Load the http module to create an http server.\nvar http = require('http');\n\n// Configure our HTTP server to respond w"
},
{
"path": "examples/nodejs/node-express/Dockerfile",
"chars": 201,
"preview": "FROM node:22 AS build-env\nADD . /app\nWORKDIR /app\nRUN npm install --omit=dev\n\nFROM gcr.io/distroless/nodejs22-debian13\nC"
},
{
"path": "examples/nodejs/node-express/hello_express.js",
"chars": 208,
"preview": "const express = require('express')\nconst app = express()\nconst port = 3000\n\napp.get('/', (req, res) => res.send('Hello W"
},
{
"path": "examples/nodejs/node-express/package.json",
"chars": 313,
"preview": "{\n \"name\": \"distroless-express\",\n \"version\": \"1.0.0\",\n \"description\": \"Distroless express node.js\",\n \"repository\": {"
},
{
"path": "examples/nodejs/package.json",
"chars": 209,
"preview": "{\n \"name\": \"nodejs\",\n \"version\": \"1.0.0\",\n \"description\": \"Example\",\n \"main\": \"hello.js\",\n \"scripts\": {\n \"test\":"
},
{
"path": "examples/nodejs/testdata/hello.yaml",
"chars": 136,
"preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n - name: hello\n command: [\"/nodejs/bin/node\", \"/hello.js\"]\n expectedOutput: "
},
{
"path": "examples/nonroot/BUILD",
"chars": 1963,
"preview": "# Public notice: this file is for internal documentation, testing, and\n# reference only. Note that repo maintainers can "
},
{
"path": "examples/nonroot/testdata/user.go",
"chars": 1180,
"preview": "// Copyright 2017 Google Inc. All rights reserved.\n\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n/"
},
{
"path": "examples/nonroot/testdata/user.yaml",
"chars": 126,
"preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n - name: user\n command: ['/user']\n expectedOutput: ['User: nonroot', 'Uid: 1"
},
{
"path": "examples/python3/BUILD",
"chars": 1045,
"preview": "# Public notice: this file is for internal documentation, testing, and\n# reference only. Note that repo maintainers can "
},
{
"path": "examples/python3/Dockerfile",
"chars": 159,
"preview": "FROM python:3-slim AS build-env\nCOPY . /app\nWORKDIR /app\n\nFROM gcr.io/distroless/python3\nCOPY --from=build-env /app /app"
},
{
"path": "examples/python3/hello.py",
"chars": 1045,
"preview": "#!/bin/python\n\n# Copyright 2017 Google Inc. All rights reserved.\n\n# Licensed under the Apache License, Version 2.0 (the "
},
{
"path": "examples/python3-requirements/Dockerfile",
"chars": 985,
"preview": "# Build a virtualenv using the appropriate Debian release\n# * Install python3-venv for the built-in Python3 venv module "
},
{
"path": "examples/python3-requirements/README.md",
"chars": 1135,
"preview": "# Python 3 with requirements.txt\n\nThis is a Python 3 application that specifies third-party dependencies using requireme"
},
{
"path": "examples/python3-requirements/psutil_example.py",
"chars": 514,
"preview": "#!/usr/bin/env python3\n\n# Distroless's test.sh runs pylint on all python files, but this module will not exist\n# pylint:"
},
{
"path": "examples/python3-requirements/requirements.txt",
"chars": 85,
"preview": "# this version of psutil does not ship binary wheels: must be compiled\npsutil==5.6.6\n"
},
{
"path": "examples/rust/.dockerignore",
"chars": 14,
"preview": ".git/\ntarget/\n"
},
{
"path": "examples/rust/BUILD",
"chars": 599,
"preview": "# Public notice: this file is for internal documentation, testing, and\n# reference only. Note that repo maintainers can "
},
{
"path": "examples/rust/Cargo.toml",
"chars": 129,
"preview": "[package]\nname = \"hello-world-distroless\"\nversion = \"0.1.0\"\nauthors = [\"Bryant Hagadorn <blhagadorn@gmail.com>\"]\n\n[depen"
},
{
"path": "examples/rust/Dockerfile",
"chars": 212,
"preview": "FROM rust:1 as build-env\nWORKDIR /app\nCOPY . /app\nRUN cargo build --release\n\nFROM gcr.io/distroless/cc-debian12\nCOPY --f"
},
{
"path": "examples/rust/src/main.rs",
"chars": 44,
"preview": "fn main() {\n println!(\"Hello World!\");\n}\n"
},
{
"path": "examples/test.sh",
"chars": 520,
"preview": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nfor d in examples/*; do\n\t# Skip non-directories.\n\tif [[ ! -d \"$d\" ]]; then conti"
},
{
"path": "experimental/BUILD",
"chars": 0,
"preview": ""
},
{
"path": "experimental/busybox/BUILD",
"chars": 345,
"preview": "load(\"//:distro.bzl\", BUSYBOX_ARCHITECTURES = \"ALL_ARCHITECTURES\")\nload(\":commands.bzl\", \"busybox_layer\")\n\npackage(defau"
},
{
"path": "experimental/busybox/commands.bzl",
"chars": 5978,
"preview": "load(\"@aspect_bazel_lib//lib:tar.bzl\", \"tar\")\n\nBUSYBOX_COMMANDS = [\n \"[\",\n \"[[\",\n \"acpid\",\n \"add-shell\",\n "
},
{
"path": "java/BUILD",
"chars": 1051,
"preview": "load(\":config.bzl\", \"JAVA_ARCHITECTURES\", \"JAVA_BASE_PACKAGES\", \"JAVA_DISTROS\")\nload(\":java.bzl\", \"java_base_image\", \"ja"
},
{
"path": "java/README.md",
"chars": 1073,
"preview": "# Documentation for `gcr.io/distroless/java`\n\n## Image Contents\n\nThis image contains a minimal Linux, OpenJDK-based runt"
},
{
"path": "java/config.bzl",
"chars": 663,
"preview": "JAVA_DISTROS = [\"debian13\"]\nJAVA_ARCHITECTURES = {\n \"debian13\": [\"amd64\", \"arm64\", \"s390x\", \"ppc64le\", \"riscv64\"],\n}\n"
},
{
"path": "java/control",
"chars": 270,
"preview": "Package: Eclipse Temurin\nVersion: {{VERSION}}\nArchitecture: {{ARCHITECTURE}}\nMaintainer: Adoptium Working Group <https:/"
},
{
"path": "java/java.bzl",
"chars": 10092,
"preview": "\"java image definitions\"\n\nload(\"@container_structure_test//:defs.bzl\", \"container_structure_test\")\nload(\"@rules_oci//oci"
},
{
"path": "java/jre_ver.bzl",
"chars": 468,
"preview": "def jre_ver(version):\n \"\"\"Extract JRE version from a Debian openjdk downloaded filename.\n\n Debian packages versi"
},
{
"path": "java/testdata/CheckCerts.java",
"chars": 489,
"preview": "package testdata;\n\nimport java.io.IOException;\nimport java.net.URL;\nimport javax.net.ssl.HttpsURLConnection;\n\npublic cla"
},
{
"path": "java/testdata/CheckEncoding.java",
"chars": 530,
"preview": "package testdata;\n\nimport java.nio.charset.Charset;\nimport java.util.Locale;\n\npublic class CheckEncoding {\n\n public sta"
},
{
"path": "java/testdata/CheckLibharfbuzz.java",
"chars": 403,
"preview": "package testdata;\n\nimport java.awt.GraphicsEnvironment;\nimport java.awt.Font;\n\npublic class CheckLibharfbuzz {\n publi"
},
{
"path": "java/testdata/java17_debian13.yaml",
"chars": 1227,
"preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n - name: java\n command: \"/usr/lib/jvm/temurin-17-jre-amd64/bin/java\"\n args: "
},
{
"path": "java/testdata/java17_debug_debian13.yaml",
"chars": 987,
"preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n - name: java\n command: \"/usr/lib/jvm/temurin-17-jdk-amd64/bin/java\"\n args: "
},
{
"path": "java/testdata/java21_debian13.yaml",
"chars": 1229,
"preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n - name: java\n command: \"/usr/lib/jvm/temurin-21-jre-amd64/bin/java\"\n args: "
},
{
"path": "java/testdata/java21_debug_debian13.yaml",
"chars": 987,
"preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n - name: java\n command: \"/usr/lib/jvm/temurin-21-jdk-amd64/bin/java\"\n args: "
},
{
"path": "java/testdata/java25_debian13.yaml",
"chars": 1226,
"preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n - name: java\n command: \"/usr/lib/jvm/temurin-25-jre-amd64/bin/java\"\n args: "
},
{
"path": "java/testdata/java25_debug_debian13.yaml",
"chars": 983,
"preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n - name: java\n command: \"/usr/lib/jvm/temurin-25-jdk-amd64/bin/java\"\n args: "
},
{
"path": "java/testdata/java_base.yaml",
"chars": 316,
"preview": "schemaVersion: \"2.0.0\"\nfileExistenceTests:\n - name: certs\n path: \"/etc/ssl/certs/java/cacerts\"\n shouldExist: true"
},
{
"path": "java/testdata/java_base_debug.yaml",
"chars": 312,
"preview": "schemaVersion: \"2.0.0\"\nfileExistenceTests:\n - name: certs\n path: \"/etc/ssl/certs/java/cacerts\"\n shouldExist: true"
},
{
"path": "java/testdata/java_certs.yaml",
"chars": 324,
"preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n - name: connect_to_https_google_com\n # This is a bit ugly because structure te"
},
{
"path": "java/testdata/java_encoding.yaml",
"chars": 408,
"preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n - name: check_encoding\n command: [\"/usr/bin/java\",\n \"-cp\",\n "
},
{
"path": "java/testdata/java_libharfbuzz.yaml",
"chars": 225,
"preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n - name: check_libharfbuzz\n command: [\"/usr/bin/java\",\n \"-cp\",\n "
},
{
"path": "knife",
"chars": 7751,
"preview": "#!/usr/bin/env bash\nset -o pipefail -o errexit -o nounset\n\n# Copyright 2024 Google Inc. All rights reserved.\n\n# Licensed"
},
{
"path": "knife.d/update_java_versions.sh",
"chars": 1251,
"preview": "set -o pipefail -o errexit -o nounset\n\n# Copyright 2024 Google Inc. All rights reserved.\n\n# Licensed under the Apache Li"
},
{
"path": "knife.d/update_node_archives.js",
"chars": 6417,
"preview": "#!/usr/bin/env node\nconst crypto = require(\"crypto\");\nconst https = require(\"https\");\nconst fs = require(\"fs\");\n\nif (pro"
},
{
"path": "nodejs/BUILD",
"chars": 702,
"preview": "load(\":config.bzl\", \"NODEJS_ARCHITECTURES\", \"NODEJS_DISTROS\", \"NODEJS_MAJOR_VERSIONS\")\nload(\":nodejs.bzl\", \"nodejs_image"
},
{
"path": "nodejs/README.md",
"chars": 764,
"preview": "# Documentation for `gcr.io/distroless/nodejs`\n\n## Image Contents\n\nThese images contain a minimal Linux, Node.js-based r"
},
{
"path": "nodejs/config.bzl",
"chars": 301,
"preview": "NODEJS_DISTROS = [\"debian13\"]\nNODEJS_ARCHITECTURES = {\n \"debian13\": {\n \"20\": [\"amd64\", \"arm64\", \"arm\", \"s390x\""
},
{
"path": "nodejs/control",
"chars": 681,
"preview": "Package: nodejs\nVersion: {{VERSION}}\nArchitecture: {{ARCHITECTURE}}\nMaintainer: OpenJS Foundation <https://openjsf.org/>"
},
{
"path": "nodejs/nodejs.bzl",
"chars": 3336,
"preview": "\"nodejs image definitions\"\n\nload(\"@container_structure_test//:defs.bzl\", \"container_structure_test\")\nload(\"@rules_oci//o"
},
{
"path": "nodejs/testdata/check_certificate.js",
"chars": 244,
"preview": "const https = require('https')\nconst options = {\n hostname: 'www.google.com',\n port: 443,\n path: '/',\n method: 'GET'"
},
{
"path": "nodejs/testdata/check_certificate.yaml",
"chars": 163,
"preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n - name: nodejs\n command: \"/nodejs/bin/node\" \n args: [\"/check_certificate.js"
},
{
"path": "nodejs/testdata/check_headers.yaml",
"chars": 120,
"preview": "schemaVersion: \"2.0.0\"\nfileExistenceTests:\n - name: npm\n path: '/nodejs/include/node/node.h'\n shouldExist: false\n"
},
{
"path": "nodejs/testdata/check_npm.yaml",
"chars": 414,
"preview": "schemaVersion: \"2.0.0\"\nfileExistenceTests:\n - name: npm\n path: '/nodejs/lib/node_modules/npm'\n shouldExist: false"
},
{
"path": "nodejs/testdata/nodejs20.yaml",
"chars": 143,
"preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n - name: nodejs\n command: \"/nodejs/bin/node\"\n args: [\"--version\"]\n expect"
},
{
"path": "nodejs/testdata/nodejs22.yaml",
"chars": 143,
"preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n - name: nodejs\n command: \"/nodejs/bin/node\"\n args: [\"--version\"]\n expect"
},
{
"path": "nodejs/testdata/nodejs24.yaml",
"chars": 143,
"preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n - name: nodejs\n command: \"/nodejs/bin/node\"\n args: [\"--version\"]\n expect"
},
{
"path": "private/extensions/BUILD.bazel",
"chars": 0,
"preview": ""
},
{
"path": "private/extensions/busybox.bzl",
"chars": 5833,
"preview": "\"busybox\"\n\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\", \"http_file\")\nload(\"//experimental/busybo"
},
{
"path": "private/extensions/node.bzl",
"chars": 8938,
"preview": "\"node\"\n\nBUILD_TMPL = \"\"\"\\\n# GENERATED BY node_archive.bzl\nload(\"@distroless//private/pkg:debian_spdx.bzl\", \"debian_spdx\""
},
{
"path": "private/extensions/version.bzl",
"chars": 2232,
"preview": "\"generates version information from lockfiles\"\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_distroless//apt/privat"
},
{
"path": "private/oci/BUILD.bazel",
"chars": 40,
"preview": "exports_files([\"sign_and_push.sh.tpl\"])\n"
},
{
"path": "private/oci/cc_image.bzl",
"chars": 626,
"preview": "\"cc_image rule for creating C++ container images\"\n\nload(\"@rules_cc//cc:cc_binary.bzl\", \"cc_binary\")\nload(\"@rules_oci//oc"
},
{
"path": "private/oci/defs.bzl",
"chars": 397,
"preview": "load(\":cc_image.bzl\", _cc_image = \"cc_image\")\nload(\":go_image.bzl\", _go_image = \"go_image\")\nload(\":java_image.bzl\", _jav"
},
{
"path": "private/oci/digest.bzl",
"chars": 1386,
"preview": "\"generate digest for oci_image and oci_image_index\"\n\nload(\"@aspect_bazel_lib//lib:copy_file.bzl\", \"copy_file\")\nload(\"@as"
},
{
"path": "private/oci/go_image.bzl",
"chars": 694,
"preview": "\"go_image rule for creating Go container images\"\n\nload(\"@rules_go//go:def.bzl\", \"go_binary\")\nload(\"@rules_oci//oci:defs."
},
{
"path": "private/oci/java_image.bzl",
"chars": 633,
"preview": "\"java_image rule for creating Java container images\"\n\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\")\nload(\"//private/util"
},
{
"path": "private/oci/rust_image.bzl",
"chars": 703,
"preview": "\"rust_image rule for creating Rust container images\"\n\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\")\nload(\"@rules_rust//r"
},
{
"path": "private/oci/sign_and_push.bzl",
"chars": 5352,
"preview": "\"rules for signing, attesting and pushing images\"\n\nload(\"@bazel_skylib//rules:write_file.bzl\", \"write_file\")\nload(\"//pri"
},
{
"path": "private/oci/sign_and_push.sh.tpl",
"chars": 765,
"preview": "#!/usr/bin/env bash\nset -o pipefail -o errexit -o nounset\n\nKEYLESS=\"${KEYLESS:-}\"\n\nwhile (( $# > 0 )); do\n case $1 in\n "
},
{
"path": "private/pkg/BUILD.bazel",
"chars": 592,
"preview": "load(\"@rules_go//go:def.bzl\", \"go_binary\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ngo_binary(\n name ="
},
{
"path": "private/pkg/debian_spdx.bzl",
"chars": 1991,
"preview": "SPDX_CMD = \"\"\"\\\ntmp=\"$(mktemp -d)\"\n\ntar -xf \"$1\" -C \"$tmp\" \"./control\" || tar -xf \"$1\" -C \"$tmp\" \"control\"\n\nif tar -xf "
},
{
"path": "private/pkg/debian_spdx.go",
"chars": 4474,
"preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\n\tspdx_json \"github.com/spdx/tool"
},
{
"path": "private/pkg/oci_image_spdx.bzl",
"chars": 2006,
"preview": "\"an aspect rule that generates spdx by collecting spdx output group\"\n\ndef _image_aspect_impl(target, ctx):\n output = "
},
{
"path": "private/pkg/oci_image_spdx.go",
"chars": 3538,
"preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\tspdx_json \"github.com/spdx/tools-golang/json\"\n\t\"github.c"
},
{
"path": "private/pkg/test/oci_image/BUILD.bazel",
"chars": 1135,
"preview": "load(\"@aspect_bazel_lib//lib:write_source_files.bzl\", \"write_source_files\")\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\""
},
{
"path": "private/pkg/test/oci_image/fat_image_sbom.spdx.json",
"chars": 7918,
"preview": "{\"spdxVersion\":\"SPDX-2.3\",\"dataLicense\":\"CC0-1.0\",\"SPDXID\":\"SPDXRef-DOCUMENT\",\"name\":\"//private/pkg/test/oci_image:fat_i"
},
{
"path": "private/pkg/test/oci_image/image_amd64.spdx.json",
"chars": 3790,
"preview": "{\"spdxVersion\":\"SPDX-2.3\",\"dataLicense\":\"CC0-1.0\",\"SPDXID\":\"SPDXRef-DOCUMENT\",\"name\":\"//private/pkg/test/oci_image:image"
},
{
"path": "private/pkg/test/oci_image/image_arm64.spdx.json",
"chars": 3966,
"preview": "{\"spdxVersion\":\"SPDX-2.3\",\"dataLicense\":\"CC0-1.0\",\"SPDXID\":\"SPDXRef-DOCUMENT\",\"name\":\"//private/pkg/test/oci_image:image"
},
{
"path": "private/repos/deb/BUILD.bazel",
"chars": 0,
"preview": ""
},
{
"path": "private/repos/deb/bookworm.lock.json",
"chars": 24525,
"preview": "{\n\t\"packages\": [\n\t\t{\n\t\t\t\"arch\": \"amd64\",\n\t\t\t\"dependencies\": [],\n\t\t\t\"key\": \"base-files_12.4-p-deb12u13_amd64\",\n\t\t\t\"name\":"
},
{
"path": "private/repos/deb/bookworm.yaml",
"chars": 605,
"preview": "# debian 12\nversion: 1\n\nsources:\n - channel: bookworm main\n url: https://snapshot.debian.org/archive/debian/20260314"
},
{
"path": "private/repos/deb/bookworm_python.lock.json",
"chars": 18065,
"preview": "{\n\t\"packages\": [\n\t\t{\n\t\t\t\"arch\": \"amd64\",\n\t\t\t\"dependencies\": [],\n\t\t\t\"key\": \"libbz2-1.0_1.0.8-5-p-b1_amd64\",\n\t\t\t\"name\": \"l"
},
{
"path": "private/repos/deb/bookworm_python.yaml",
"chars": 877,
"preview": "# debian 12, limited architectures, python only\nversion: 1\n\nsources:\n - channel: bookworm main\n url: https://snapsho"
},
{
"path": "private/repos/deb/deb.MODULE.bazel",
"chars": 1055,
"preview": "\"debian dependencies\"\n\nREPOS = [\n \"trixie\",\n \"trixie_java\",\n \"trixie_adoptium\",\n \"trixie_python\",\n \"bookw"
},
{
"path": "private/repos/deb/package.BUILD.tmpl",
"chars": 1022,
"preview": "\"\"\"Generated by distroless. DO NOT EDIT!\"\"\"\n\nload(\"@@//private/pkg:debian_spdx.bzl\", \"debian_spdx\")\nload(\"@@//private/ut"
},
{
"path": "private/repos/deb/trixie.lock.json",
"chars": 35457,
"preview": "{\n\t\"packages\": [\n\t\t{\n\t\t\t\"arch\": \"amd64\",\n\t\t\t\"dependencies\": [],\n\t\t\t\"key\": \"base-files_13.8-p-deb13u4_amd64\",\n\t\t\t\"name\": "
},
{
"path": "private/repos/deb/trixie.yaml",
"chars": 656,
"preview": "# debian 13\nversion: 1\n\nsources:\n - channel: trixie main\n url: https://snapshot.debian.org/archive/debian/20260314T2"
},
{
"path": "private/repos/deb/trixie_adoptium.lock.json",
"chars": 11537,
"preview": "{\n\t\"packages\": [\n\t\t{\n\t\t\t\"arch\": \"amd64\",\n\t\t\t\"dependencies\": [],\n\t\t\t\"key\": \"temurin-17-jre_17.0.18.0.0-p-8-1_amd64\",\n\t\t\t\""
},
{
"path": "private/repos/deb/trixie_adoptium.yaml",
"chars": 346,
"preview": "# debian 13, java from adoptium repositories\nversion: 1\n\nsources:\n # adoptium\n - channel: trixie main\n url: https:/"
},
{
"path": "private/repos/deb/trixie_java.lock.json",
"chars": 37596,
"preview": "{\n\t\"packages\": [\n\t\t{\n\t\t\t\"arch\": \"amd64\",\n\t\t\t\"dependencies\": [],\n\t\t\t\"key\": \"fontconfig-config_2.15.0-2.3_amd64\",\n\t\t\t\"name"
},
{
"path": "private/repos/deb/trixie_java.yaml",
"chars": 881,
"preview": "# debian 13, limited architectures, java only\nversion: 1\n\nsources:\n - channel: trixie main\n url: https://snapshot.de"
},
{
"path": "private/repos/deb/trixie_python.lock.json",
"chars": 18501,
"preview": "{\n\t\"packages\": [\n\t\t{\n\t\t\t\"arch\": \"amd64\",\n\t\t\t\"dependencies\": [],\n\t\t\t\"key\": \"libbz2-1.0_1.0.8-6_amd64\",\n\t\t\t\"name\": \"libbz2"
},
{
"path": "private/repos/deb/trixie_python.yaml",
"chars": 891,
"preview": "# debian 13, limited architectures, python only\nversion: 1\n\nsources:\n - channel: trixie main\n url: https://snapshot."
},
{
"path": "private/stamp.bash",
"chars": 512,
"preview": "#!/usr/bin/env bash\nset -o pipefail -o errexit -o nounset\n\n# Do not error if the user doesn't have gcloud installed.\nif "
},
{
"path": "private/tools/diff/BUILD.bazel",
"chars": 288,
"preview": "sh_binary(\n name = \"diff\",\n srcs = [\"diff.bash\"],\n args = [\n \"--head-ref\",\n \"test\",\n \"--ba"
},
{
"path": "private/tools/diff/diff.bash",
"chars": 6011,
"preview": "#!/usr/bin/env bash\nset -o pipefail -o errexit -o nounset\n\n# ./private/tools/diff/diff.bash --head-ref test --base-ref t"
},
{
"path": "private/tools/lifecycle/BUILD.bazel",
"chars": 74,
"preview": "\"rules to deal with auto_vm lifecycle tagging\"\n\nexports_files([\"tag.sh\"])\n"
},
{
"path": "private/tools/lifecycle/defs.bzl",
"chars": 115,
"preview": "load(\":tag.bzl\", _attach_lifecycle_tags = \"attach_lifecycle_tags\")\n\nattach_lifecycle_tags = _attach_lifecycle_tags\n"
},
{
"path": "private/tools/lifecycle/tag.bzl",
"chars": 2102,
"preview": "\"rules for attaching lifecycle tags to older published images\"\n\nload(\"@bazel_skylib//rules:write_file.bzl\", \"write_file\""
},
{
"path": "private/tools/lifecycle/tag.sh",
"chars": 3446,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\ntrap 'echo \"Ctrl+C caught, exiting...\"; exit 130' INT\n\nfunction exec_tag() {\n lo"
},
{
"path": "private/tools/lifecycle/tag.sh.README.md",
"chars": 1801,
"preview": "# Container Image Lifecycle Tagger (Distroless Admin Tooling)\n\nA Bash utility to bulk-tag container images in a registry"
},
{
"path": "private/util/BUILD",
"chars": 0,
"preview": ""
},
{
"path": "private/util/deb.bzl",
"chars": 1364,
"preview": "\"utility functions for constructing debian package labels\"\n\nload(\"@versions//:versions.bzl\", \"version\")\n\nDIST_ALIAS = di"
},
{
"path": "private/util/extract.bzl",
"chars": 1105,
"preview": "\"a utility that extracts files from tar\"\n\nload(\"@aspect_bazel_lib//lib:tar.bzl\", \"tar_lib\")\n\ndef _tar_extract_file_impl("
},
{
"path": "private/util/java_cacerts.bzl",
"chars": 426,
"preview": "\"java ca certificates\"\n\nload(\"@rules_distroless//distroless:defs.bzl\", \"java_keystore\")\nload(\":extract.bzl\", \"tar_extrac"
},
{
"path": "private/util/merge_providers.bzl",
"chars": 1098,
"preview": "\"a utility rule that merges DefaultInfo and OutputGroupInfo providers\"\n\nSKIP = \"_hidden_top_level_INTERNAL_\"\n\ndef _impl("
},
{
"path": "private/util/tar.bzl",
"chars": 2022,
"preview": "\"Wrapper around tar rules for creating tar archives\"\n\nload(\"@aspect_bazel_lib//lib:tar.bzl\", _aspect_tar = \"tar\")\nload(\""
},
{
"path": "python3/BUILD",
"chars": 1187,
"preview": "load(\"//private/util:tar.bzl\", \"tar\")\nload(\":config.bzl\", \"PYTHON_ARCHITECTURES\", \"PYTHON_DISTROS\", \"PYTHON_PACKAGES\")\nl"
},
{
"path": "python3/README.md",
"chars": 799,
"preview": "# Documentation for `gcr.io/distroless/python3`\n\n## Image Contents\n\nThis image contains a minimal Linux, Python-based ru"
},
{
"path": "python3/config.bzl",
"chars": 1371,
"preview": "PYTHON_DISTROS = [\"debian12\", \"debian13\"]\nPYTHON_ARCHITECTURES = {\n \"debian12\": [\"amd64\", \"arm64\"],\n \"debian13\": ["
},
{
"path": "python3/ldconfig/ldconfig.sh",
"chars": 1644,
"preview": "#!/usr/bin/env bash\nset -eo pipefail\n\nMODE=$1\nshift\n\nif [[ \"${MODE}\" != \"update\" && \"${MODE}\" != \"check\" ]]; then\n echo"
},
{
"path": "python3/ldconfig.bzl",
"chars": 2426,
"preview": "\"Macros for generating ldconfig cache for python images\"\n\nload(\"@rules_oci//oci:defs.bzl\", \"oci_load\")\n\ndef python3_ldco"
},
{
"path": "python3/python.bzl",
"chars": 3139,
"preview": "\"python3 image definitions\"\n\nload(\"@container_structure_test//:defs.bzl\", \"container_structure_test\")\nload(\"@rules_oci//"
},
{
"path": "python3/testdata/debian12.yaml",
"chars": 2349,
"preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n - name: version\n command: [\"/usr/bin/python3\", \"--version\"]\n expectedOutput"
},
{
"path": "python3/testdata/debian13.yaml",
"chars": 2281,
"preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n - name: version\n command: [\"/usr/bin/python3\", \"--version\"]\n expectedOutput"
},
{
"path": "python3/testdata/python3.yaml",
"chars": 830,
"preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n - name: hello\n command: [\"/usr/bin/python3\", \"-c\", \"print('Hello World')\"]\n "
},
{
"path": "static/BUILD",
"chars": 925,
"preview": "load(\"//private/util:tar.bzl\", \"tar\")\nload(\":config.bzl\", \"STATIC_ARCHITECTURES\", \"STATIC_DISTROS\", \"STATIC_PACKAGES\")\nl"
},
{
"path": "static/config.bzl",
"chars": 468,
"preview": "STATIC_DISTROS = [\"debian12\", \"debian13\"]\nSTATIC_ARCHITECTURES = {\n \"debian12\": [\"amd64\", \"arm64\", \"arm\", \"s390x\", \"p"
},
{
"path": "static/nsswitch.conf",
"chars": 497,
"preview": "# /etc/nsswitch.conf\n#\n# Example configuration of GNU Name Service Switch functionality.\n# If you have the `glibc-doc-re"
},
{
"path": "static/static.bzl",
"chars": 5435,
"preview": "\"defines a function to build static different distributions\"\n\nload(\"@container_structure_test//:defs.bzl\", \"container_st"
},
{
"path": "static/testdata/check_certs.go",
"chars": 894,
"preview": "// Copyright 2017 Google Inc. All rights reserved.\n\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n/"
},
{
"path": "static/testdata/debian12.yaml",
"chars": 170,
"preview": "schemaVersion: \"1.0.0\"\nfileContentTests:\n- name: 'os-release contents'\n path: '/etc/os-release'\n expectedContents: ['."
},
{
"path": "static/testdata/debian13.yaml",
"chars": 168,
"preview": "schemaVersion: \"1.0.0\"\nfileContentTests:\n- name: 'os-release contents'\n path: '/etc/os-release'\n expectedContents: ['."
}
]
// ... and 4 more files (download for full content)
About this extraction
This page contains the full source code of the GoogleContainerTools/distroless GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 204 files (402.4 KB), approximately 145.1k tokens, and a symbol index with 26 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.