Opinionated Docker base images.
This repository offers:
- a set of Docker base images that can be used as a foundation for building other Docker images.
- GitHub Actions and reusable workflows to help you build and publish your own "Docker base images" repository.
A Docker image with all the tools needed to validate an helm chart
- helm chart-testing (aka ct)
- helm kubeconform plugin
A tailored build of VS code-server instance. Used for our labs and workshops.
An image with an opiniated mydumper command as entrypoint
An opinionated Super-Linter image with safer local defaults and toolchain-conflict guards
A Docker image for running testcontainers tests with Node.js
Actions that you can plug directly into your own Docker images repository.
Orchestrated workflows you can plug directly into your own Docker images repository.
Workflows require GitHub.com and GitHub Actions runner 2.336.0 or newer.
- Release
Contributions are welcome! Please see the contributing guidelines for more details.
All actions follow this consistent structure:
actions/{category}/{action-name}/
├── action.yml # Action definition with inputs/outputs
├── README.md # Usage documentation
└── *.js # Optional Node.js scripts (e.g., prepare-site.js)
- Consistent branding: All actions use
author: hoverkraft,icon: <specific-icon>,color: blue - Composite actions: Use
using: "composite"with GitHub Script for complex logic - Pinned dependencies: Always pin action versions with SHA (e.g.,
@ed597411d8f924073f98dfc5c65a23a2325f34cd) - Input validation: Validate inputs early in GitHub Script steps
- Class-based architecture: Use classes like
AssetManagerfor complex functionality - Path utilities: Extensive use of Node.js
pathmodule for cross-platform compatibility - Regular expression patterns: Define constants for reusable patterns (
MARKDOWN_IMAGE_REGEX,HTML_IMAGE_REGEX) - Caching: Implement Map-based caching for expensive operations
- Docker
- Make
make lint # Run Super Linter (dockerized)
make lint-fix # Auto-fix issues where possible
# Run tests for a specific image
make test ci-helm # Build and test ci-helm image
make test mydumper # Build and test mydumper image
# Run tests for all images
make test-all # Build and test all images
# Use GitHub Actions locally with `act`
gh act -W .github/workflows/workflow-file-to-test.ymlTesting Infrastructure:
Tests use testcontainers for Node.js to validate Docker images. The test framework:
- Requires only Docker and Make (no local Node.js installation needed for
make test) - Runs tests in a containerized environment for consistency
- Tests are colocated with their respective images (e.g.,
images/ci-helm/ci-helm.test.js) - Each test validates: command availability, file existence, metadata, and environment variables
- All tests share a single Node.js module in
images/testcontainers-node/
How tests are executed in this repository:
make test <image>builds the image and runs tests inside theimages/testcontainers-noderunner image.- Each test run also writes a JUnit report to
images/<image>/junit.xml. - The harness injects:
TESTED_IMAGE_REF: the image ref to test (e.g.ci-helm:latestlocally, or an OCI ref in CI)
Writing good image tests (recommended):
- Keep tests black-box: validate behavior via commands, files, users, workdir, and env.
- Make tests deterministic: avoid network calls and time-based assertions when possible.
- Start the container once per suite (
before/after) and always clean up. - Prefer
container.exec(cmd, { env, workingDir })over shell string interpolation for env handling. - Use a long-running command like
sleep infinityso you can run multipleexecchecks.
Basic example (images/<image>/<image>.test.js):
import { after, before, describe, it } from "node:test";
import assert from "node:assert";
import { GenericContainer } from "testcontainers";
describe("My Image", () => {
const testedImageRef = process.env.TESTED_IMAGE_REF;
let container;
if (!testedImageRef) {
throw new Error("TESTED_IMAGE_REF environment variable is required");
}
before(async () => {
container = await new GenericContainer(testedImageRef)
.withCommand(["sleep", "infinity"])
.start();
});
after(async () => {
await container?.stop();
});
it("has the expected tool", async () => {
const { exitCode, output } = await container.exec(["mytool", "--version"], {
env: { MY_FLAG: "1" },
workingDir: "/",
});
assert.strictEqual(exitCode, 0);
assert.match(output, /mytool/i);
});
it("runs as the expected user", async () => {
const { exitCode, output } = await container.exec(["id", "-un"]);
assert.strictEqual(exitCode, 0);
assert.strictEqual(output.trim(), "myuser");
});
});- Dockerfile: Uses Super Linter slim image for consistent code quality
- Tests: Located in each image directory (e.g.,
images/*/<image>.test.js) using testcontainers for Node.js - Workflows: Private workflows prefixed with
__(e.g.,__main-ci.yml)
Always follow these patterns when creating/modifying actions:
-
Pinned Dependencies: Use exact SHA commits for all action dependencies:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
-
Consistent Branding: Every action.yml must include:
author: hoverkraft branding: icon: <specific-icon> color: blue
-
Input Validation: Always validate inputs early in GitHub Script steps:
const urlInput = ${{ toJson(inputs.url ) }}; if (!urlInput) { return core.setFailed("URL input is required."); }
For actions/github-script steps (composite actions):
- Prefer small, focused scripts with early input validation.
- Use Node.js built-ins via
node:(e.g.,node:path). - Keep outputs stable and JSON-encoded when returning structured data.
- Favor readable helpers (small functions) over clever one-liners.
actions/ # Composite actions (using actions/github-script)
├── get-available-images/
│ ├── action.yml
│ └── README.md
└── ***/
├── action.yml
└── README.md
.github/workflows/ # Reusable workflows (+ their .md docs)
├── continuous-integration.yml
├── continuous-integration.md
├── ***.yml
├── ***.md
└── __*.yml # Private/internal workflows
images/ # Docker images (each image is self-contained)
└── <image>/
├── Dockerfile
├── README.md
├── <image-name>.test.js # Node.js tests (run via make/CI)
└── tests/ # Optional fixtures used by some tests
- Validate that pull request title respects conventional commit
- Run linters against modified files
- Build images that have been modified
- Tag with Pull request number and commit sha
- Remove all tags create during Pull request builds
Internal - Release runs every Monday at 08:25 UTC and supports manual dispatch on the default branch. CI must succeed before publishing. The workflow builds and publishes images changed since their latest image-specific tags, then creates their GitHub releases. It then plans the repository release and publishes it only when there are release changes.
Image tags use <image>-<version> and repository tags use <version>, following
Semantic Versioning. Both release types include an AI-generated summary and breaking
changes section above the full changelog. A summary failure prevents that release
from being published; rerun the workflow after resolving the failure.
Configure these repository or organization settings:
- Variable
CI_BOT_APP_CLIENT_IDand secretCI_BOT_APP_PRIVATE_KEYfor a GitHub App installed on this repository with contents write and pull requests read permissions. Its token publishes the repository tag so the main CI documentation jobs run. - Secret
OPENAI_API_KEYfor AI summaries. The provider's model defaults come from the pinned release action. - Allow
GITHUB_TOKENto publish packages to GHCR and create image releases.
Reusable image release callers can enable AI summaries through the optional
changelog-summary secret in Release.
🏢 Hoverkraft [email protected]
- Site: https://hoverkraft.cloud
- GitHub: @hoverkraft-tech
This project is licensed under the MIT License.
SPDX-License-Identifier: MIT
Copyright © 2025 hoverkraft-tech
For more details, see the license.