From 785a9ce7f544be3dd5d672ec446bfd6ededd8336 Mon Sep 17 00:00:00 2001 From: konard Date: Tue, 28 Apr 2026 13:26:05 +0000 Subject: [PATCH 1/8] Initial commit with task details Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: https://github.com/link-foundation/box/issues/80 --- .gitkeep | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 0000000..933b5ef --- /dev/null +++ b/.gitkeep @@ -0,0 +1 @@ +# .gitkeep file auto-generated at 2026-04-28T13:26:05.669Z for PR creation at branch issue-80-bded956c66f7 for issue https://github.com/link-foundation/box/issues/80 \ No newline at end of file From 2fee3fc862e9e03aef76be39f2641ebad839b214 Mon Sep 17 00:00:00 2001 From: konard Date: Tue, 28 Apr 2026 13:34:49 +0000 Subject: [PATCH 2/8] docs: add case study and research for issue #80 (super-box / Docker-in-Box) - Compile issue text, requirements list, and solution plans in docs/case-studies/issue-80/CASE-STUDY.md - Collect upstream evidence (Docker DinD/DooD/Sysbox/Tecnativa/devcontainers/OWASP/Quarkslab) in research.md - Preserve original issue text in issue.md - Recommend nested DinD as the default solution: it satisfies "docker ps only shows my containers" by construction, and document Sysbox as the recommended secure runtime - Rejected DooD with host /var/run/docker.sock as a default for host-safety reasons Removes the .gitkeep marker file used during PR bootstrap (per its own commit message). --- .gitkeep | 1 - docs/case-studies/issue-80/CASE-STUDY.md | 307 +++++++++++++++++++++++ docs/case-studies/issue-80/issue.md | 13 + docs/case-studies/issue-80/research.md | 245 ++++++++++++++++++ 4 files changed, 565 insertions(+), 1 deletion(-) delete mode 100644 .gitkeep create mode 100644 docs/case-studies/issue-80/CASE-STUDY.md create mode 100644 docs/case-studies/issue-80/issue.md create mode 100644 docs/case-studies/issue-80/research.md diff --git a/.gitkeep b/.gitkeep deleted file mode 100644 index 933b5ef..0000000 --- a/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# .gitkeep file auto-generated at 2026-04-28T13:26:05.669Z for PR creation at branch issue-80-bded956c66f7 for issue https://github.com/link-foundation/box/issues/80 \ No newline at end of file diff --git a/docs/case-studies/issue-80/CASE-STUDY.md b/docs/case-studies/issue-80/CASE-STUDY.md new file mode 100644 index 0000000..586d6e6 --- /dev/null +++ b/docs/case-studies/issue-80/CASE-STUDY.md @@ -0,0 +1,307 @@ +# Case Study: Issue #80 — `konard/super-box` (Docker‑in‑Box) + +## Executive Summary + +Issue [#80](https://github.com/link-foundation/box/issues/80) requests a new image variant — provisionally +`konard/super-box` — that is a strict superset of `konard/box` (Ubuntu 24.04, non‑root `box` user, multi‑arch +amd64+arm64, all language runtimes from the existing modular pipeline) plus the ability to **launch and +control Docker containers from inside the box**. As a stretch goal, the issue asks that each `super-box` +instance see only the containers it created (`docker ps -a` should be naturally scoped) so the host system +remains "somewhat safe". + +This document is the case‑study deliverable from the issue. It is research only — no code, no Dockerfile. +Implementation will land in a follow‑up PR once the chosen solution plan is approved. + +The companion file [`research.md`](./research.md) collects the upstream evidence (Docker docs, Sysbox, +Tecnativa proxy, Devcontainers Features, OWASP, Quarkslab, GitLab Runner, etc.) that the conclusions below +rest on. The original issue text is preserved in [`issue.md`](./issue.md). + +--- + +## 1. Problem Statement + +The existing `konard/box` image is an "everything‑languages" development environment but cannot run Docker +inside itself. Workflows that need to build/launch other containers (CI runners, AI agents that orchestrate +sandbox containers, dev‑container‑style scenarios, integration tests against `docker compose`, etc.) cannot +use `konard/box` directly. They either fall back to a different base or to fragile DooD setups that bind‑mount +the host `/var/run/docker.sock`. + +Adding a `super-box` variant that bundles Docker (CLI + daemon + Compose + Buildx) addresses that gap while +keeping the rest of the language matrix intact. + +The host‑safety stretch goal — "each box only sees containers it created in `docker ps -a`" — is non‑trivial +because Docker has no native multi‑tenant view of `docker ps`. Section 5 below explains how nested DinD +provides this property naturally as a side effect of each box owning its own daemon. + +--- + +## 2. Requirements Extracted From the Issue + +The issue text is short. We unpack it into explicit, testable requirements so each can be addressed by the +solution plan. + +### Functional Requirements + +| ID | Requirement | Source phrase | +|---|---|---| +| **FR‑80.1** | Publish a new image (working name `konard/super-box`) that is a **superset** of `konard/box`. | "does all the same" | +| **FR‑80.2** | The image MUST include the Docker daemon (`dockerd`), the Docker CLI, `containerd`, `runc`, Buildx, and Compose v2 so that users can `docker build`, `docker run`, `docker compose up` from inside the running container. | "built up on docker with elevated permissions, that allows to control docker from inside dockers" | +| **FR‑80.3** | The image MUST start the inner Docker daemon automatically (or via a documented entrypoint) so an interactive `docker run -it konard/super-box` is immediately usable. | "control docker from inside dockers" | +| **FR‑80.4** | The image MUST be multi‑arch (`linux/amd64` + `linux/arm64`), matching the rest of the box matrix. | Project convention (REQUIREMENTS.md FR‑4) | +| **FR‑80.5** | The image MUST be published to both Docker Hub (`konard/super-box`) and ghcr.io (`ghcr.io/link-foundation/super-box`), matching the rest of the box matrix. | Project convention (REQUIREMENTS.md FR‑5) | + +### Security / Isolation Requirements (Stretch) + +| ID | Requirement | Source phrase | +|---|---|---| +| **FR‑80.6** | Each `super-box` instance SHOULD see only the containers it has itself created when running `docker ps -a`. | "each docker container only has access to dockers in `docker ps -a`, which were created by that docker" | +| **FR‑80.7** | The host system SHOULD remain reasonably safe — a compromise of the box SHOULD NOT trivially imply host root. | "so host system is somewhat safe" | +| **FR‑80.8** | The README MUST document the privilege model and the recommended secure invocation. | Implicit (project documents NFR‑2 in REQUIREMENTS.md). | + +### Non‑Functional / Process Requirements + +| ID | Requirement | Source / rationale | +|---|---|---| +| **NFR‑80.1** | The image MUST run as a **non‑root** user by default (`box`), consistent with the rest of the project. | REQUIREMENTS.md NFR‑2 | +| **NFR‑80.2** | The image MUST integrate with the existing modular pipeline (`build-essentials → languages → full`) and reuse `COPY --from` to avoid duplicating language installs. | ARCHITECTURE.md ("Modular Design") | +| **NFR‑80.3** | The image MUST follow the per‑image change‑detection pattern of the release workflow so unrelated branches don't trigger unnecessary super‑box rebuilds. | REQUIREMENTS.md CI‑3 | +| **DOC‑80.1** | A case study MUST be compiled in `docs/case-studies/issue-80/` containing the issue text, requirements, solution plans, and references. | Issue body explicit ask | +| **DOC‑80.2** | The README MUST be updated with the new image table entries and a security banner. | Project convention (issue #71 set the precedent for image tables) | + +--- + +## 3. Why This Is Hard + +Three things make a "Docker inside a container" image more interesting than a normal Dockerfile: + +1. **Privilege.** A real Docker daemon needs `CAP_SYS_ADMIN` and access to `/dev`, network namespaces, and + either overlay or fuse‑overlayfs. The standard solution is `--privileged`, which removes most container + isolation. ([OWASP Docker Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html)) +2. **Storage drivers.** `dockerd` writing to `/var/lib/docker` on top of an overlay filesystem (the container's + writable layer) is the classic "overlay on overlay" failure case. Workarounds: a tmpfs at + `/var/lib/docker`, a named volume, vfs as a fallback, or fuse‑overlayfs in rootless mode. + ([jpetazzo's classic warning](https://github.com/jpetazzo/dind)) +3. **Per‑caller views of the API.** Docker has no notion of "tenants" on `docker.sock`. Anyone reachable on + that socket sees *all* containers on that daemon. Achieving FR‑80.6 therefore requires either a separate + daemon per box (nested DinD) or a filtering proxy in front of a shared daemon. + +--- + +## 4. Solution Space (one section per requirement) + +### 4.1 Solution plan for FR‑80.1, FR‑80.2, FR‑80.3 — "Docker available inside the box" + +Three viable patterns; one recommended. + +| Option | What it is | Privilege | Verdict | +|---|---|---|---| +| **A. Nested Docker‑in‑Docker (DinD)** — `dockerd` runs inside `super-box` | Install `docker-ce`, `containerd.io`, `docker-buildx-plugin`, `docker-compose-plugin` from Docker's apt repo and start `dockerd` from the entrypoint. | `--privileged` (or Sysbox) | **Recommended.** Matches the issue's wording ("docker from inside dockers") literally and gives FR‑80.6 for free. | +| **B. Docker‑outside‑of‑Docker (DooD)** — host socket bind‑mounted in | Only the Docker CLI is shipped; users run `docker run -v /var/run/docker.sock:/var/run/docker.sock konard/super-box`. | none, but `docker` group inside ≡ host root | **Rejected as default.** Violates FR‑80.7 (mounting host socket is a documented host‑takeover vector — [Quarkslab](https://blog.quarkslab.com/why-is-exposing-the-docker-socket-a-really-bad-idea.html)) and breaks FR‑80.6 (any sibling container is visible). | +| **C. Rootless DinD** — `docker:dind-rootless` recipe | `dockerd` runs as user `box` inside a user namespace. | `--privileged` still recommended; escape lands in user‑ns, not host root | **Ship as a secondary tag** (`konard/super-box-rootless`) once core image is solid. | + +**Recommended plan for FR‑80.1/2/3:** Option A as the default tag, Option C as a secondary tag. The reference +recipe to crib from is [`cruizba/ubuntu-dind`](https://github.com/cruizba/ubuntu-dind) (Ubuntu, multi‑arch, +Compose + Buildx, has a `start-docker.sh` entrypoint). Implementation skeleton: + +1. Create `ubuntu/24.04/super-box/install.sh` that adds Docker's apt repository and installs + `docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin`. +2. Create `ubuntu/24.04/super-box/Dockerfile` that: + - `FROM ${ESSENTIALS_IMAGE}` (parallel to `full-box`), + - copies the language layers via `COPY --from=...-stage` exactly like `full-box/Dockerfile` does, + - runs `super-box/install.sh`, + - drops a `/usr/local/bin/super-box-entrypoint.sh` that starts `dockerd` (in DinD mode) and then `exec`s + the existing `entrypoint.sh`. +3. Wire the new image into `.github/workflows/release.yml` as a sibling job to `build-full-amd64/arm64`, + reusing the same change‑detection inputs. +4. Tag matrix: `latest`, `{version}`, `{version}-amd64`, `{version}-arm64`, `latest-amd64`, `latest-arm64`, + plus `rootless` variants when option C lands. + +### 4.2 Solution plan for FR‑80.4 (multi‑arch) and NFR‑80.2/3 (pipeline integration) + +This is the cheap part — the existing pipeline already builds one extra image (`full-box`) the same way. Add +`super-box` as a peer matrix entry. Native ARM64 runners (`ubuntu-24.04-arm`, see REQUIREMENTS.md CI‑1) are +mandatory: the Docker daemon and `containerd` are compilation‑heavy in qemu and would blow the 120‑minute +ARM64 budget. + +Change detection should treat changes under `ubuntu/24.04/super-box/**` as triggering only the super‑box +build, exactly like other language images. Change to `essentials-box` already cascades. + +### 4.3 Solution plan for FR‑80.5 (registries) + +Mirrors the rest of the project; nothing special. Push `konard/super-box` and +`ghcr.io/link-foundation/super-box` from the workflow with the same retry logic added in +[PR #79](https://github.com/link-foundation/box/pull/79) (issue #78). + +### 4.4 Solution plan for FR‑80.6 — "`docker ps` only shows my containers" + +Only two approaches actually deliver this guarantee. Both are listed; one is recommended. + +| Option | What it is | Strength of guarantee | Verdict | +|---|---|---|---| +| **D. Nested DinD (default of 4.1.A)** — each `super-box` runs its own `dockerd` | The inner daemon literally only knows about containers the box created. `docker ps` is naturally scoped. | Strong: separate Linux namespaces, separate state directory, separate API socket. One box cannot enumerate or kill another box's containers because they live on different daemons. | **Recommended.** Free side‑effect of 4.1.A. | +| **E. Shared host daemon + filtering proxy** | Bind‑mount `docker.sock` into a [`Tecnativa/docker-socket-proxy`](https://github.com/Tecnativa/docker-socket-proxy) that is then chained through [`FoxxMD/docker-proxy-filter`](https://github.com/FoxxMD/docker-proxy-filter) to filter `/containers/json` by a per‑box label. | Medium: proxy enforces the view, but anyone who escapes the proxy reaches the host daemon. | Document only as an optional pattern for users who have a strong reason to share the host daemon. | + +**Conclusion for FR‑80.6:** picking 4.1.A (nested DinD) satisfies FR‑80.6 implicitly; no socket proxy or +authz plugin is needed for the default tag. Mention the authz alternatives in `research.md` for completeness: + +- [`twistlock/authz`](https://github.com/twistlock/authz) — regex‑based ACL plugin. +- [`casbin/docker-casbin-plugin`](https://github.com/casbin/docker-casbin-plugin) — Casbin RBAC/ABAC. +- [Docker authz plugin API docs](https://docs.docker.com/engine/extend/plugins_authorization/). + +These are useful when you cannot accept a separate daemon per tenant; we can. + +### 4.5 Solution plan for FR‑80.7 — "host stays somewhat safe" + +The default DinD tag still requires `--privileged`, which is **not** "host‑safe" in the strict sense. +Two complementary mitigations: + +1. **Document Sysbox as the recommended secure runtime.** [Sysbox](https://github.com/nestybox/sysbox) is a + drop‑in OCI runtime that runs system containers (including a nested `dockerd`) **without** `--privileged` + and without exposing host devices. The README should include + `docker run --runtime=sysbox-runc konard/super-box` as the recommended invocation for production / shared + hosts and link to [Sysbox installation](https://github.com/nestybox/sysbox/blob/master/docs/user-guide/install-package.md). +2. **Ship a `konard/super-box-rootless` tag** (Option C) so users who cannot install Sysbox still have a + meaningful step up from full DinD. Note the Ubuntu 24.04 caveat: + `kernel.apparmor_restrict_unprivileged_userns=1` breaks rootless DinD until the user installs the + AppArmor profile or flips the sysctl ([spad.uk write‑up](https://www.spad.uk/posts/rootless-dind-noble/)). + +### 4.6 Solution plan for FR‑80.8 / DOC‑80.2 — README updates + +The README must include: + +- A new "Docker‑in‑Box" section under "Docker Images" listing `konard/super-box` and the GHCR equivalent in + the same multi‑arch table format established by issue #71. +- A security banner with at least four lines (privilege model, do‑not‑mount‑host‑socket warning, recommended + Sysbox invocation, `docker ps` scoping behaviour). Concrete wording is in + [`research.md` §4](./research.md). + +### 4.7 Solution plan for NFR‑80.1 — non‑root by default + +Even in DinD mode the box user can stay non‑root for the *user shell*. The pattern is: + +1. The container starts as root just long enough for the entrypoint to run `dockerd` (root‑owned by design). +2. The entrypoint then `su`/`gosu`/`runuser`s into `box` for the interactive shell. +3. `box` is added to the inner `docker` group so that `docker` CLI calls from the user shell talk to the + inner `dockerd` over `/var/run/docker.sock`. + +This matches `cruizba/ubuntu-dind`'s behaviour and the standard `docker:dind` recipe. + +--- + +## 5. Reference Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ konard/super-box (Ubuntu 24.04) │ +│ │ +│ ┌─────────────────────────────┐ │ +│ │ user shell (UID 1000 box) │ → docker CLI, compose, │ +│ │ + all language runtimes │ buildx, full-box langs │ +│ └──────────────┬──────────────┘ │ +│ │ unix socket /var/run/docker.sock │ +│ ▼ │ +│ ┌─────────────────────────────┐ │ +│ │ inner dockerd (root) │ → /var/lib/docker │ +│ │ containerd, runc, buildkit │ (in container layer or │ +│ │ │ mounted volume) │ +│ └──────────────┬──────────────┘ │ +│ │ spawns │ +│ ▼ │ +│ ┌─────────────────────────────┐ │ +│ │ child containers │ ← only these show in │ +│ │ (created by this super-box)│ `docker ps -a` (FR-6) │ +│ └─────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ + │ runs under + ▼ + host kernel (Linux) + host dockerd (NOT shared) +``` + +Key property: the inner `dockerd` does not know about the host `dockerd`, and vice versa. The host socket is +**not** bind‑mounted. `docker ps -a` inside the box returns only the inner daemon's containers, satisfying +FR‑80.6 by construction. + +--- + +## 6. Existing Components Worth Reusing + +Documented in detail in [`research.md`](./research.md) §3. Highlights: + +- [`cruizba/ubuntu-dind`](https://github.com/cruizba/ubuntu-dind) — closest analogue; Ubuntu noble, multi‑arch, + bundled Compose + Buildx, has a `start-docker.sh`. Use as the recipe template. +- [`docker:dind` / `docker:dind-rootless`](https://hub.docker.com/_/docker) — official; canonical reference + for the entrypoint and storage‑driver handling. +- [`devcontainers/features` `docker-in-docker`](https://github.com/devcontainers/features/tree/main/src/docker-in-docker) + — installer script with documented options (Compose version, dind storage driver, etc.) we can mirror. +- [`nestybox/sysbox`](https://github.com/nestybox/sysbox) — the secure runtime to recommend. +- [`Tecnativa/docker-socket-proxy`](https://github.com/Tecnativa/docker-socket-proxy) + + [`FoxxMD/docker-proxy-filter`](https://github.com/FoxxMD/docker-proxy-filter) — only relevant if we ever + ship a "shared host daemon" mode; not needed for the default plan. +- [`twistlock/authz`](https://github.com/twistlock/authz) / + [`casbin/docker-casbin-plugin`](https://github.com/casbin/docker-casbin-plugin) — authz plugin alternatives + for completeness. + +We do **not** need to vendor any of these; the install steps from `cruizba/ubuntu-dind` and +`devcontainers/features` are short enough to inline into `ubuntu/24.04/super-box/install.sh`. + +--- + +## 7. Implementation Plan (sequenced) + +A separate PR after this case study is approved. Sketch: + +1. **Add `ubuntu/24.04/super-box/{install.sh,Dockerfile}`** — DinD recipe on top of essentials, mirroring + `full-box`'s `COPY --from` language merge. Entrypoint starts `dockerd`, then drops to `box`. +2. **Add release‑workflow job** `build-super-box-amd64/arm64` and `manifest-super-box`. Use the existing + change‑detection pattern; add `super-box/**` to the per‑image filter. +3. **Smoke test** in CI: `docker run --privileged konard/super-box docker run hello-world` on both arches. +4. **Update README.md** — new image rows, security banner, link to this case study. +5. **Update REQUIREMENTS.md / ARCHITECTURE.md** — add a short "super‑box" subsection to the modular‑design + section. +6. **Bump VERSION** so the existing release workflow publishes the new image. +7. **(Optional, follow‑up)** Add `konard/super-box-rootless` tag once the default tag has shipped a release + cycle. + +Each step is a separate commit, all on branch `issue-80-bded956c66f7`. + +--- + +## 8. Risks & Open Questions + +- **Inner `/var/lib/docker` storage strategy.** Default to overlay2 inside the container layer and accept that + pulled images vanish on `docker rm`; document `-v sb-data:/var/lib/docker` as the recommended persistent + pattern. Decide before implementation: do we set `tmpfs:/var/lib/docker` by default to avoid surprising + layer growth? +- **Image size.** `super-box` will be `full-box` plus ~150–200 MB for `docker-ce` + `containerd` + Buildx + + Compose. Acceptable, but should be measured by `scripts/measure-disk-space.sh` and reported in the README + size table. +- **GitHub Actions compatibility.** `actions/checkout` and `docker/build-push-action` inside `super-box` + should "just work", but it should be tested explicitly because some users will use `super-box` as a + self‑hosted runner image. +- **arm64 build time.** Adding Docker increases build time. Stay within REQUIREMENTS.md NFR‑1 (120 min for + ARM64). Likely fine — `docker-ce` is published as binary debs by Docker Inc., no compilation needed. +- **Naming.** "super-box" is the issue's working name. Open question for the implementation PR: keep it as + `super-box`, or use a more conventional `box-dind` / `box-docker`? The case study uses `super-box` + throughout; the implementation PR can pick a final name. + +--- + +## 9. References + +- [Issue #80](https://github.com/link-foundation/box/issues/80) +- [`research.md`](./research.md) — upstream evidence supporting the conclusions in this document +- Project conventions: + - [REQUIREMENTS.md](../../../REQUIREMENTS.md) — FR‑1…5, NFR‑1…3, CI‑1…4 + - [ARCHITECTURE.md](../../../ARCHITECTURE.md) — modular design, COPY‑‑from assembly +- Docker / Sysbox / proxy upstreams (full URLs in `research.md`): + - [docker:dind / docker:dind-rootless](https://hub.docker.com/_/docker) + - [`cruizba/ubuntu-dind`](https://github.com/cruizba/ubuntu-dind) + - [`nestybox/sysbox`](https://github.com/nestybox/sysbox) + - [`devcontainers/features` docker-in-docker](https://github.com/devcontainers/features/tree/main/src/docker-in-docker) + - [`Tecnativa/docker-socket-proxy`](https://github.com/Tecnativa/docker-socket-proxy) + - [`FoxxMD/docker-proxy-filter`](https://github.com/FoxxMD/docker-proxy-filter) + - [Docker authz plugin API](https://docs.docker.com/engine/extend/plugins_authorization/) + - [OWASP Docker Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html) + - [Quarkslab on `/var/run/docker.sock`](https://blog.quarkslab.com/why-is-exposing-the-docker-socket-a-really-bad-idea.html) + - [GitLab: using Docker build (DinD service)](https://docs.gitlab.com/ci/docker/using_docker_build/) diff --git a/docs/case-studies/issue-80/issue.md b/docs/case-studies/issue-80/issue.md new file mode 100644 index 0000000..de0a496 --- /dev/null +++ b/docs/case-studies/issue-80/issue.md @@ -0,0 +1,13 @@ +# Issue #80 — Original Text + +> **Title:** Add super-box image, that does all the same, but also built up on docker with elevated permissions, that allows to control docker from inside dockers +> +> **Labels:** documentation, enhancement +> **Author:** @konard +> **URL:** https://github.com/link-foundation/box/issues/80 +> +> --- +> +> If possible, we need to make sure each docker container only has access to dockers in `docker ps -a`, which were created by that docker, so host system is somewhat safe, if possible. +> +> We need to collect data related about the issue to this repository, make sure we compile that data to `./docs/case-studies/issue-{id}` folder, and use it to do deep case study analysis (also make sure to search online for additional facts and data), list of each and all requirements from the issue, and propose possible solutions and solution plans for each requirement (we should also check known existing components/libraries, that solve similar problem or can help in solutions). diff --git a/docs/case-studies/issue-80/research.md b/docs/case-studies/issue-80/research.md new file mode 100644 index 0000000..f0d0b21 --- /dev/null +++ b/docs/case-studies/issue-80/research.md @@ -0,0 +1,245 @@ +# Research: Building a `konard/super-box` (Docker-in-Box) Image + +Background research for [issue #80](https://github.com/link-foundation/box/issues/80). The goal is a `super-box` +image that is a drop-in superset of `konard/box` (Ubuntu 24.04, non-root `box` user, multi-arch amd64+arm64, +published to Docker Hub and ghcr.io) plus the ability to launch and control Docker containers from inside it. +Where possible we also want each `super-box` instance to only "see" containers it created, so the host stays +reasonably safe. + +This document is research-only. No code, no Dockerfile. + +--- + +## 1) Patterns for nested containers + +### 1a. Docker-in-Docker (DinD) with `docker:dind` and `--privileged` + +The official [`docker`](https://hub.docker.com/_/docker) image ships a `dind` variant that runs a full `dockerd` +inside the container. It is the canonical pattern documented by Docker and used by GitLab Runner +([GitLab docs on `docker:dind`](https://docs.gitlab.com/ci/docker/using_docker_build/)). + +- Privilege: requires `--privileged` (or a very wide cap-add + AppArmor/seccomp unconfined profile). +- Security posture: weak. `--privileged` essentially removes container/host isolation; container root effectively + has host root via device access, capability set, and disabled LSM profiles + ([GitLab forum](https://forum.gitlab.com/t/docker-in-docker-dind-privileged-true/73526), + [OWASP Docker Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html)). +- Performance: an extra storage driver layer (overlay-on-overlay) and a second daemon. Noticeable but acceptable + for CI. +- Ergonomics: best-known pattern; tons of examples; works on plain Docker without extra runtimes. +- Gotchas: storage-driver issues on overlay-on-overlay, MTU/DNS quirks for the inner daemon, + TLS handshake noise from `dockerd`, and the fact that a privileged DinD is roughly equivalent to giving the + container root on the host + ([jpetazzo's classic warning](https://github.com/jpetazzo/dind#warning-the-resulting-images-are-not-meant-to-replace-real-vms)). + +### 1b. Docker-out-of-Docker (DooD) by mounting `/var/run/docker.sock` + +Mount the host socket into the container; the container's `docker` CLI talks to the host daemon. Used by VS Code's +[`docker-outside-of-docker` Dev Container Feature](https://github.com/devcontainers/features/tree/main/src/docker-outside-of-docker). + +- Privilege: no `--privileged`, but membership in the `docker` group inside the container is *de facto* root on the + host + ([raesene "The Dangers of docker.sock"](https://raesene.github.io/blog/2016/03/06/The-Dangers-Of-Docker.sock/), + [Quarkslab analysis](https://blog.quarkslab.com/why-is-exposing-the-docker-socket-a-really-bad-idea.html)). +- Security posture: very weak from a host-safety standpoint (full daemon API access). +- Performance: negligible overhead. +- Ergonomics: any container the inner CLI starts is a *sibling* on the host, not a child. Bind-mounting + workspace paths is awkward because paths are host paths, not container paths. +- Gotchas: read-only socket does not help much (`docker inspect` still leaks secrets); any compromise of the + super-box is a host compromise. + +### 1c. Rootless Docker-in-Docker (`docker:dind-rootless`) + +The same official image but with `dockerd` running as a non-root user inside a user namespace +([Docker rootless docs](https://docs.docker.com/engine/security/rootless/), +[`Dockerfile-dind-rootless.template`](https://github.com/docker-library/docker/blob/master/Dockerfile-dind-rootless.template)). + +- Privilege: still requires `--privileged` (or carefully crafted seccomp/AppArmor exemptions and unprivileged + user-namespace support on the host); rootless reduces, but does not eliminate, the need to relax the LSM mask + ([Docker rootless tips](https://docs.docker.com/engine/security/rootless/tips/)). +- Security posture: stronger than 1a. A daemon escape lands you as a remapped UID, not host root. +- Performance: comparable to DinD; some networking restrictions (slirp4netns is slower than bridge). +- Ergonomics: fewer caps in the inner daemon (no AppArmor inside, no overlay-fs without fuse-overlayfs). +- Gotchas: Ubuntu 23.10+ restricts unprivileged user namespaces by AppArmor, which breaks rootless DinD until you + install the right profile or set `kernel.apparmor_restrict_unprivileged_userns=0` + ([spad.uk write-up](https://www.spad.uk/posts/rootless-dind-noble/)). + Ubuntu 24.04 ("noble") inherits this restriction, which directly affects this project. + +### 1d. Sysbox runtime (Nestybox / Docker) + +[`nestybox/sysbox`](https://github.com/nestybox/sysbox) is an OCI runtime (`sysbox-runc`) that turns containers into +"system containers" capable of running `systemd`, `dockerd`, and `kubelet` *without* `--privileged` and without +exposing the host socket +([Sysbox DinD guide](https://github.com/nestybox/sysbox/blob/master/docs/user-guide/dind.md), +[Nestybox blog "Secure DinD"](https://blog.nestybox.com/2019/09/14/dind.html)). + +- Privilege: none beyond what Sysbox itself grants; container root maps to an unprivileged host UID via user-ns. +- Security posture: strongest of the practical "real Docker inside" options. +- Performance: comparable to native; uses shiftfs/idmapped mounts to avoid `chown` storms. +- Ergonomics: identical UX to running `docker:dind`, just `--runtime=sysbox-runc`. +- Gotchas: requires the host operator to install Sysbox; not available on Docker Desktop or hosted CI by default; + Nestybox was acquired by Docker Inc. in 2022 but the project remains community-driven. + +### 1e. Podman-in-Podman + +Podman is daemonless and supports rootless nesting natively +([Podman rootless tutorial](https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md), +[issue #15419 nested rootless](https://github.com/containers/podman/issues/15419)). + +- Privilege: none; containers run in the user's namespace. +- Security posture: very strong if subuid/subgid ranges are sized correctly. +- Performance: good; fuse-overlayfs adds some overhead. +- Ergonomics: `podman` CLI is mostly Docker-compatible (`alias docker=podman`), but Compose/Buildx parity is not 1:1; + some images that hard-code `/var/run/docker.sock` will need work. +- Gotchas: nested user namespaces require enough subuid/subgid range; "potentially insufficient UIDs/GIDs" errors are + the canonical failure mode. + +### 1f. Kaniko / BuildKit (build-only) + +Not full daemons, but solve the most common "I need DinD just to build" use case. + +- [`GoogleContainerTools/kaniko`](https://github.com/GoogleContainerTools/kaniko) — daemonless, no privileges, no + nested containers; archived January 2025 with maintenance picked up by Chainguard. +- [`moby/buildkit`](https://github.com/moby/buildkit) (and `buildkit:rootless`) — modern replacement for Kaniko, + used under the hood by `docker buildx`; supports rootless. +- For `super-box`, BuildKit/Buildx are useful as a *complement* (faster, safer image builds), but neither lets + you `docker run` arbitrary containers, which is the user-facing requirement of this issue. + +--- + +## 2) Per-container scoping of `docker ps -a` + +### 2a. Built-in Docker + +- **Label filters** (`docker ps --filter label=...`, + [docs](https://docs.docker.com/engine/cli/filter/)) are *advisory*: they hide rows for the user, but the daemon + still serves the full list to anyone with socket access. A super-box that sets a unique label on every container + it creates and aliases `docker ps` to filter by that label is a UX convenience, *not* a security boundary. +- **userns-remap** ([docs](https://docs.docker.com/engine/security/userns-remap/)) is daemon-wide, not per-container + caller, so it does not partition the visibility of `docker ps`. +- **Docker contexts** are CLI-side connection profiles; they do not change what a given daemon shows. + +Conclusion: **Docker has no native multi-tenant view of `docker ps`.** Anyone who can reach the socket sees all +containers on that daemon. + +### 2b. Socket proxies in front of `docker.sock` + +- [`Tecnativa/docker-socket-proxy`](https://github.com/Tecnativa/docker-socket-proxy) — HAProxy with environment-flag + ACLs on API endpoints (e.g. `CONTAINERS=1 POST=0`). Endpoint-level only; cannot filter response bodies. +- [`linuxserver/docker-socket-proxy`](https://github.com/linuxserver/docker-socket-proxy) — fork of the Tecnativa + proxy. +- [`titpetric/docker-proxy-acl`](https://github.com/titpetric/docker-proxy-acl) and + [`qdm12/docker-proxy-acl-alpine`](https://github.com/qdm12/docker-proxy-acl-alpine) — minimal allowlist proxies for + endpoints (sometimes referred to as "jpillora-style" though jpillora's project is no longer the canonical one). +- [`FoxxMD/docker-proxy-filter`](https://github.com/FoxxMD/docker-proxy-filter) — sits behind a Tecnativa-style proxy + and **rewrites** API responses, filtering `/containers/json` results by name and by label, returning 404 for + inspect/exec/logs on containers that do not match + ([explanatory blog post](https://blog.foxxmd.dev/posts/restricting-socket-proxy-by-container/)). This is the + closest off-the-shelf primitive for the requested behaviour. +- [`DataDog/docker-filter`](https://github.com/DataDog/docker-filter) — older, archived, similar idea (read-only + filtering proxy). + +### 2c. Authorization plugins + +Docker's authz plugin API ([docs](https://docs.docker.com/engine/extend/plugins_authorization/)) lets a daemon defer +each request to an external service. Two reference implementations: + +- [`twistlock/authz`](https://github.com/twistlock/authz) — simple regex/policy file, user-based. +- [`casbin/docker-casbin-plugin`](https://github.com/casbin/docker-casbin-plugin) — Casbin engine, supports + ACL/RBAC/ABAC. + +Plugins can deny calls but they have limited ability to *rewrite* responses, so trimming `docker ps` output is +awkward. Better suited to "block create/exec on containers you do not own" than to producing a per-tenant view. + +### 2d. Nested DinD (each box has its own daemon) + +Because every super-box runs its own `dockerd` (option 1a/1c/1d), the inner daemon **only knows about containers +it created**. `docker ps -a` inside a super-box trivially returns only that box's children. This is the strongest +isolation guarantee for the host-safety goal: the host daemon is never reachable, the inner daemon's API is only +reachable inside the box, and one box cannot enumerate or kill another box's containers because they are on +different daemons. The cost is a privileged-or-Sysbox runtime requirement. + +### 2e. Recommendation for strongest isolation + +Ranked best-to-worst against the "host stays safe AND `docker ps` is naturally scoped" goal: + +1. **Sysbox + nested DinD** — natural per-box scoping, no `--privileged`, no host socket. +2. **Rootless DinD (`docker:dind-rootless`)** — natural per-box scoping, still needs `--privileged` but escape lands + in user-ns, not host root. +3. **Privileged DinD (`docker:dind`)** — natural per-box scoping, but a container escape is host root. +4. **DooD with `Tecnativa proxy` + `FoxxMD docker-proxy-filter`, scoped by a label set at container creation + time** — host daemon is shared, scoping is enforced by the proxy. Weaker because anyone who can bypass the + proxy reaches the host daemon. +5. **DooD with raw `docker.sock`** — *do not ship*. No isolation. + +For a public image, Sysbox is the most defensible default but cannot be assumed to exist on the user's host. A +practical answer is: ship `super-box` as DinD by default (works everywhere with `--privileged`), document Sysbox +as the recommended secure runtime, and explicitly call out DooD as "convenience mode only". + +--- + +## 3) Existing similar images / projects + +- [`docker:dind`](https://hub.docker.com/_/docker) — official; Alpine-based; canonical reference. +- [`docker:dind-rootless`](https://hub.docker.com/_/docker) — official rootless variant; UID 1000. +- [`cruizba/ubuntu-dind`](https://github.com/cruizba/ubuntu-dind) — Ubuntu-based DinD with focal/jammy/noble tags, + Buildx and Compose pre-installed, multi-arch (amd64/arm64). Closest analogue to what `super-box` wants. +- [`nestybox/ubuntu-bionic-systemd-docker`](https://hub.docker.com/r/nestybox/ubuntu-bionic-systemd-docker) and + [`nestybox/dockerfiles`](https://github.com/nestybox/dockerfiles) — Sysbox-blessed system container images + ("works only with `--runtime=sysbox-runc`"). +- [`devcontainers/features` `docker-in-docker`](https://github.com/devcontainers/features/tree/main/src/docker-in-docker) + — installer scripts that add `dockerd` to a Dev Container; designed for `--privileged` runs. +- [`devcontainers/features` `docker-outside-of-docker`](https://github.com/devcontainers/features/tree/main/src/docker-outside-of-docker) + — installer scripts that add the Docker CLI and forward the host socket. +- [`myoung34/docker-github-actions-runner`](https://github.com/myoung34/docker-github-actions-runner) — Ubuntu-based + self-hosted GitHub Actions runner image that bundles Docker; useful template for "Docker plus a non-root user + plus an entrypoint that starts `dockerd`". +- [`actions-runner-controller/runner-images`](https://github.com/actions-runner-controller/runner-images) — official + ARC runner images; same pattern at larger scale. +- [`tcardonne/docker-github-runner`](https://github.com/tcardonne/docker-github-runner) — alternative Ubuntu runner + with Docker. +- [`jpetazzo/dind`](https://github.com/jpetazzo/dind) — historical reference and the original "warning, this is + dangerous" essay by the author of DinD. +- [GitLab Runner DinD docs](https://docs.gitlab.com/ci/docker/using_docker_build/) — battle-tested DinD service + pattern with `privileged = true` and a `tls`/non-tls split. +- [`moby/buildkit`](https://github.com/moby/buildkit) and + [`GoogleContainerTools/kaniko`](https://github.com/GoogleContainerTools/kaniko) — for build-only scenarios. + +The closest thing to "drop-in basis for `super-box`" is `cruizba/ubuntu-dind`: Ubuntu, multi-arch, includes Compose +and Buildx, and exposes a `start-docker.sh` entrypoint. The simplest path is to use the same recipe +(`apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin` plus an +init script) on top of `konard/box-essentials` and add the runtime layers via the existing `COPY --from` merge. + +--- + +## 4) Risk summary and minimum README warnings + +For a publicly published `konard/super-box` image, the README and the image description should make at least the +following points unambiguous: + +- **Default mode requires `--privileged`.** Running this image with `docker run --privileged konard/super-box` is + effectively equivalent to giving the container root on the host kernel. Do not run untrusted code or + user-supplied workflows in this mode unless you accept that escape == host compromise + ([OWASP Docker Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html)). +- **Do not bind-mount the host `docker.sock` into this image.** Users following old DooD examples will be tempted + to. Mounting the host socket is documented by Docker, OWASP, and Quarkslab as a host-takeover vector + ([Quarkslab](https://blog.quarkslab.com/why-is-exposing-the-docker-socket-a-really-bad-idea.html)). +- **Prefer Sysbox where available.** Document + `docker run --runtime=sysbox-runc konard/super-box` as the recommended secure invocation and link to + [Sysbox installation](https://github.com/nestybox/sysbox/blob/master/docs/user-guide/install-package.md). +- **Rootless variant is best-effort on Ubuntu 24.04 hosts** because of the `kernel.apparmor_restrict_unprivileged_userns` + restriction; document the workaround + ([spad.uk](https://www.spad.uk/posts/rootless-dind-noble/)). +- **`docker ps` inside the box only shows that box's containers** (because each box has its own daemon). State this + explicitly so users do not assume they have visibility into the host or sibling boxes. +- **Storage isn't shared between runs by default.** The inner `/var/lib/docker` lives in the container's writable + layer; pulled images vanish on `docker rm`. Recommend a named volume (`-v sb-data:/var/lib/docker`) for cache + persistence and warn that the volume retains *everything* the box pulled. +- **CVE surface is doubled.** The image ships a host kernel-coupled `dockerd` plus `containerd` plus `runc` plus a + CLI; track Docker advisories alongside whatever language runtimes are inherited from `konard/box`. +- **Multi-arch build caveat.** Building this image for arm64 with `qemu` will be slow because Docker daemon and + containerd compile heavy; consider native arm64 runners or `--platform` matrix builds. + +A reasonable minimum README banner is one line at the top — for example: +"`konard/super-box` runs a Docker daemon inside the container. The default `--privileged` mode is functionally +equivalent to host root; use `--runtime=sysbox-runc` or the rootless tag for untrusted workloads, and never mount +the host's `/var/run/docker.sock` into this image." From 6b11924a331ef3bf8d792d488b12388d87c17143 Mon Sep 17 00:00:00 2001 From: konard Date: Tue, 28 Apr 2026 14:10:44 +0000 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20add=20dind-box=20family=20=E2=80=94?= =?UTF-8?q?=20Docker-in-Docker=20for=20every=20box=20variant=20(#80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a generic dind recipe at ubuntu/24.04/dind/ that takes any base box image as --build-arg BASE_IMAGE=... and produces a "-dind" sibling. Each running dind-box has its own inner dockerd, so docker ps -a inside lists only that box's children — the host-isolation goal in issue #80. Wires the recipe into the release workflow as a 14×2 (variant × arch) matrix so dind variants are published alongside every existing image (full, essentials, js, and every language box) on Docker Hub and ghcr.io with the standard latest/version/-amd64/-arm64 tag set and per-variant multi-arch manifests. README + case study updated with image tables and the security model (default --privileged, recommended Sysbox runtime, do-not-mount-host-socket warning, per-box docker ps scoping). --- .changeset/issue-80-dind-box.md | 18 + .github/workflows/release.yml | 436 ++++++++++++++++++++++- README.md | 63 ++++ docs/case-studies/issue-80/CASE-STUDY.md | 19 +- ubuntu/24.04/dind/Dockerfile | 58 +++ ubuntu/24.04/dind/dind-entrypoint.sh | 127 +++++++ ubuntu/24.04/dind/install.sh | 103 ++++++ 7 files changed, 822 insertions(+), 2 deletions(-) create mode 100644 .changeset/issue-80-dind-box.md create mode 100644 ubuntu/24.04/dind/Dockerfile create mode 100644 ubuntu/24.04/dind/dind-entrypoint.sh create mode 100644 ubuntu/24.04/dind/install.sh diff --git a/.changeset/issue-80-dind-box.md b/.changeset/issue-80-dind-box.md new file mode 100644 index 0000000..6227b12 --- /dev/null +++ b/.changeset/issue-80-dind-box.md @@ -0,0 +1,18 @@ +--- +bump: minor +--- + +Add `dind-box` family: Docker-in-Docker variant for every existing box image (issue #80). + +Each base image now has a sibling `-dind` that ships the Docker Engine, CLI, containerd, Buildx, and Compose v2 plus an entrypoint that starts the inner `dockerd` and drops to the `box` user: + +- `konard/box` → `konard/box-dind` +- `konard/box-essentials` → `konard/box-essentials-dind` +- `konard/box-js` → `konard/box-js-dind` +- `konard/box--dind` for every language box (python, go, rust, java, kotlin, ruby, php, perl, swift, lean, rocq) + +All variants are multi-arch (linux/amd64 + linux/arm64) on Docker Hub and ghcr.io. + +Recommended invocation: `docker run --runtime=sysbox-runc konard/box-dind` (Sysbox, no `--privileged` needed). Default fallback: `docker run --privileged konard/box-dind`. Each running dind-box has its own inner Docker daemon, so `docker ps -a` from inside the container only lists containers created by that container — the host-safety stretch goal in issue #80. + +See `docs/case-studies/issue-80/CASE-STUDY.md` for the full design and threat model. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d0d3853..8b68e16 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -237,6 +237,8 @@ jobs: rocq-changed: ${{ steps.language-changes.outputs.rocq }} cpp-changed: ${{ steps.language-changes.outputs.cpp }} assembly-changed: ${{ steps.language-changes.outputs.assembly }} + # dind-box change detection (issue #80) + dind-changed: ${{ steps.image-changes.outputs.dind }} steps: - uses: actions/checkout@v4 @@ -346,6 +348,13 @@ jobs: echo "common=false" >> $GITHUB_OUTPUT fi + # dind-box changes (issue #80) + if echo "$CHANGED_FILES" | grep -qE '^ubuntu/24\.04/dind/'; then + echo "dind=true" >> $GITHUB_OUTPUT + else + echo "dind=false" >> $GITHUB_OUTPUT + fi + - name: Detect per-language changes id: language-changes run: | @@ -533,6 +542,27 @@ jobs: echo "" echo "=== All full box tests passed ===" + - name: Build dind-box variant (amd64, on full) + run: | + set -e + echo "=== Building dind-box on full-box (issue #80) ===" + docker build -f ubuntu/24.04/dind/Dockerfile \ + --build-arg BASE_IMAGE=box-test \ + -t box-dind-test . + + - name: Test dind-box variant + run: | + set -e + echo "=== Testing dind-box (CLI presence only — no privileged mode in PR CI) ===" + # Verify Docker CLI, dockerd binary, buildx and compose plugins are present. + # We do NOT start dockerd here because GitHub-hosted runners disallow --privileged. + docker run --rm --entrypoint=/bin/bash box-dind-test -c 'docker --version' + docker run --rm --entrypoint=/bin/bash box-dind-test -c 'dockerd --version' + docker run --rm --entrypoint=/bin/bash box-dind-test -c 'docker buildx version' + docker run --rm --entrypoint=/bin/bash box-dind-test -c 'docker compose version' + docker run --rm --entrypoint=/bin/bash box-dind-test -c 'cat /etc/box/variant' + echo "=== dind-box smoke tests passed ===" + # === BUILD JS BOX (amd64) === # JS box is the base layer - built first, other images depend on it build-js-amd64: @@ -2074,10 +2104,374 @@ jobs: echo "Docker Hub multi-arch manifest created and pushed successfully for latest and ${VERSION}" + # === BUILD DIND-BOX VARIANTS (amd64) — issue #80 === + # Layers Docker Engine on top of every base box image so each variant has a + # "-dind" sibling. Runs after the source variant's manifest is published. + build-dind-amd64: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + needs: [detect-changes, js-manifest, essentials-manifest, languages-manifest, docker-manifest] + strategy: + fail-fast: false + matrix: + # variant.name -> tag suffix on box images, e.g. "js" => konard/box-js-dind + # variant.base -> the base image suffix (empty for full box "konard/box") + variant: + - { name: js, base_image_suffix: -js, needs_manifest: js } + - { name: essentials, base_image_suffix: -essentials, needs_manifest: essentials } + - { name: python, base_image_suffix: -python, needs_manifest: languages } + - { name: go, base_image_suffix: -go, needs_manifest: languages } + - { name: rust, base_image_suffix: -rust, needs_manifest: languages } + - { name: java, base_image_suffix: -java, needs_manifest: languages } + - { name: kotlin, base_image_suffix: -kotlin, needs_manifest: languages } + - { name: ruby, base_image_suffix: -ruby, needs_manifest: languages } + - { name: php, base_image_suffix: -php, needs_manifest: languages } + - { name: perl, base_image_suffix: -perl, needs_manifest: languages } + - { name: swift, base_image_suffix: -swift, needs_manifest: languages } + - { name: lean, base_image_suffix: -lean, needs_manifest: languages } + - { name: rocq, base_image_suffix: -rocq, needs_manifest: languages } + - { name: full, base_image_suffix: '', needs_manifest: docker } + if: | + always() && + needs.detect-changes.result == 'success' && + (needs.js-manifest.result == 'success' || needs.js-manifest.result == 'skipped') && + (needs.essentials-manifest.result == 'success' || needs.essentials-manifest.result == 'skipped') && + (needs.languages-manifest.result == 'success' || needs.languages-manifest.result == 'skipped') && + (needs.docker-manifest.result == 'success' || needs.docker-manifest.result == 'skipped') && + ( + (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-changes.outputs.should-build == 'true') || + (github.event_name == 'workflow_dispatch') + ) + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: main + + - name: Get latest version + id: version + run: | + git pull origin main || true + VERSION=$(cat VERSION | tr -d '[:space:]') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Compute image names + id: names + run: | + BASE_SUFFIX="${{ matrix.variant.base_image_suffix }}" + NAME="${{ matrix.variant.name }}" + # Source base image (no -dind): + echo "base_image=${{ env.DOCKERHUB_IMAGE_NAME }}${BASE_SUFFIX}:${{ steps.version.outputs.version }}-amd64" >> $GITHUB_OUTPUT + # Target dind image suffix: full -> "-dind", others -> "-dind" + if [ "$NAME" = "full" ]; then + echo "dind_suffix=-dind" >> $GITHUB_OUTPUT + else + echo "dind_suffix=${BASE_SUFFIX}-dind" >> $GITHUB_OUTPUT + fi + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.GHCR_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKERHUB_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push ${{ matrix.variant.name }} dind-box (amd64) + id: build-push + continue-on-error: true + uses: docker/build-push-action@v5 + with: + context: . + file: ubuntu/24.04/dind/Dockerfile + platforms: linux/amd64 + push: true + build-args: | + BASE_IMAGE=${{ steps.names.outputs.base_image }} + tags: | + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:latest-amd64 + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:${{ steps.version.outputs.version }}-amd64 + ${{ env.DOCKERHUB_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:latest-amd64 + ${{ env.DOCKERHUB_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:${{ steps.version.outputs.version }}-amd64 + provenance: false + cache-from: type=gha,scope=dind-${{ matrix.variant.name }}-amd64 + cache-to: type=gha,scope=dind-${{ matrix.variant.name }}-amd64,mode=max + + - name: Retry push ${{ matrix.variant.name }} dind-box (amd64) on failure + if: steps.build-push.outcome == 'failure' + run: | + echo "First push attempt failed, retrying with backoff..." + for attempt in 1 2 3; do + echo "==> Retry attempt $attempt/3..." + if docker buildx build \ + --file ubuntu/24.04/dind/Dockerfile \ + --platform linux/amd64 \ + --build-arg BASE_IMAGE=${{ steps.names.outputs.base_image }} \ + --tag ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:latest-amd64 \ + --tag ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:${{ steps.version.outputs.version }}-amd64 \ + --tag ${{ env.DOCKERHUB_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:latest-amd64 \ + --tag ${{ env.DOCKERHUB_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:${{ steps.version.outputs.version }}-amd64 \ + --provenance=false \ + --cache-from type=gha,scope=dind-${{ matrix.variant.name }}-amd64 \ + --push \ + .; then + echo "==> Push succeeded on retry attempt $attempt" + exit 0 + fi + if [ "$attempt" -lt 3 ]; then + delay=$((10 * attempt)) + echo "==> Retry failed, waiting ${delay}s before next attempt..." + sleep "$delay" + fi + done + echo "==> All retry attempts failed" + exit 1 + + # === BUILD DIND-BOX VARIANTS (arm64) — issue #80 === + build-dind-arm64: + runs-on: ubuntu-24.04-arm + timeout-minutes: 45 + needs: [detect-changes, js-manifest, essentials-manifest, languages-manifest, docker-manifest] + strategy: + fail-fast: false + matrix: + variant: + - { name: js, base_image_suffix: -js, needs_manifest: js } + - { name: essentials, base_image_suffix: -essentials, needs_manifest: essentials } + - { name: python, base_image_suffix: -python, needs_manifest: languages } + - { name: go, base_image_suffix: -go, needs_manifest: languages } + - { name: rust, base_image_suffix: -rust, needs_manifest: languages } + - { name: java, base_image_suffix: -java, needs_manifest: languages } + - { name: kotlin, base_image_suffix: -kotlin, needs_manifest: languages } + - { name: ruby, base_image_suffix: -ruby, needs_manifest: languages } + - { name: php, base_image_suffix: -php, needs_manifest: languages } + - { name: perl, base_image_suffix: -perl, needs_manifest: languages } + - { name: swift, base_image_suffix: -swift, needs_manifest: languages } + - { name: lean, base_image_suffix: -lean, needs_manifest: languages } + - { name: rocq, base_image_suffix: -rocq, needs_manifest: languages } + - { name: full, base_image_suffix: '', needs_manifest: docker } + if: | + always() && + needs.detect-changes.result == 'success' && + (needs.js-manifest.result == 'success' || needs.js-manifest.result == 'skipped') && + (needs.essentials-manifest.result == 'success' || needs.essentials-manifest.result == 'skipped') && + (needs.languages-manifest.result == 'success' || needs.languages-manifest.result == 'skipped') && + (needs.docker-manifest.result == 'success' || needs.docker-manifest.result == 'skipped') && + ( + (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.detect-changes.outputs.should-build == 'true') || + (github.event_name == 'workflow_dispatch') + ) + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: main + + - name: Get latest version + id: version + run: | + git pull origin main || true + VERSION=$(cat VERSION | tr -d '[:space:]') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Compute image names + id: names + run: | + BASE_SUFFIX="${{ matrix.variant.base_image_suffix }}" + NAME="${{ matrix.variant.name }}" + echo "base_image=${{ env.DOCKERHUB_IMAGE_NAME }}${BASE_SUFFIX}:${{ steps.version.outputs.version }}-arm64" >> $GITHUB_OUTPUT + if [ "$NAME" = "full" ]; then + echo "dind_suffix=-dind" >> $GITHUB_OUTPUT + else + echo "dind_suffix=${BASE_SUFFIX}-dind" >> $GITHUB_OUTPUT + fi + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.GHCR_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKERHUB_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push ${{ matrix.variant.name }} dind-box (arm64) + id: build-push + continue-on-error: true + uses: docker/build-push-action@v5 + with: + context: . + file: ubuntu/24.04/dind/Dockerfile + platforms: linux/arm64 + push: true + build-args: | + BASE_IMAGE=${{ steps.names.outputs.base_image }} + tags: | + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:latest-arm64 + ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:${{ steps.version.outputs.version }}-arm64 + ${{ env.DOCKERHUB_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:latest-arm64 + ${{ env.DOCKERHUB_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:${{ steps.version.outputs.version }}-arm64 + provenance: false + cache-from: type=gha,scope=dind-${{ matrix.variant.name }}-arm64 + cache-to: type=gha,scope=dind-${{ matrix.variant.name }}-arm64,mode=max + + - name: Retry push ${{ matrix.variant.name }} dind-box (arm64) on failure + if: steps.build-push.outcome == 'failure' + run: | + echo "First push attempt failed, retrying with backoff..." + for attempt in 1 2 3; do + echo "==> Retry attempt $attempt/3..." + if docker buildx build \ + --file ubuntu/24.04/dind/Dockerfile \ + --platform linux/arm64 \ + --build-arg BASE_IMAGE=${{ steps.names.outputs.base_image }} \ + --tag ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:latest-arm64 \ + --tag ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:${{ steps.version.outputs.version }}-arm64 \ + --tag ${{ env.DOCKERHUB_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:latest-arm64 \ + --tag ${{ env.DOCKERHUB_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:${{ steps.version.outputs.version }}-arm64 \ + --provenance=false \ + --cache-from type=gha,scope=dind-${{ matrix.variant.name }}-arm64 \ + --push \ + .; then + echo "==> Push succeeded on retry attempt $attempt" + exit 0 + fi + if [ "$attempt" -lt 3 ]; then + delay=$((10 * attempt)) + echo "==> Retry failed, waiting ${delay}s before next attempt..." + sleep "$delay" + fi + done + echo "==> All retry attempts failed" + exit 1 + + # === CREATE DIND-BOX MULTI-ARCH MANIFESTS — issue #80 === + dind-manifest: + runs-on: ubuntu-24.04 + needs: [detect-changes, build-dind-amd64, build-dind-arm64] + strategy: + fail-fast: false + matrix: + variant: + - { name: js, base_image_suffix: -js } + - { name: essentials, base_image_suffix: -essentials } + - { name: python, base_image_suffix: -python } + - { name: go, base_image_suffix: -go } + - { name: rust, base_image_suffix: -rust } + - { name: java, base_image_suffix: -java } + - { name: kotlin, base_image_suffix: -kotlin } + - { name: ruby, base_image_suffix: -ruby } + - { name: php, base_image_suffix: -php } + - { name: perl, base_image_suffix: -perl } + - { name: swift, base_image_suffix: -swift } + - { name: lean, base_image_suffix: -lean } + - { name: rocq, base_image_suffix: -rocq } + - { name: full, base_image_suffix: '' } + if: | + always() && + needs.detect-changes.result == 'success' && + needs.build-dind-amd64.result == 'success' && + needs.build-dind-arm64.result == 'success' + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: main + + - name: Get latest version + id: version + run: | + git pull origin main || true + VERSION=$(cat VERSION | tr -d '[:space:]') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.GHCR_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKERHUB_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Compute dind suffix + id: names + run: | + NAME="${{ matrix.variant.name }}" + BASE_SUFFIX="${{ matrix.variant.base_image_suffix }}" + if [ "$NAME" = "full" ]; then + echo "dind_suffix=-dind" >> $GITHUB_OUTPUT + else + echo "dind_suffix=${BASE_SUFFIX}-dind" >> $GITHUB_OUTPUT + fi + + - name: Create and push ${{ matrix.variant.name }} dind multi-arch manifests + run: | + VERSION="${{ steps.version.outputs.version }}" + DSUF="${{ steps.names.outputs.dind_suffix }}" + + # GHCR + docker manifest create ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}${DSUF}:latest \ + --amend ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}${DSUF}:latest-amd64 \ + --amend ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}${DSUF}:latest-arm64 + docker manifest push ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}${DSUF}:latest + + docker manifest create ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}${DSUF}:${VERSION} \ + --amend ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}${DSUF}:${VERSION}-amd64 \ + --amend ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}${DSUF}:${VERSION}-arm64 + docker manifest push ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}${DSUF}:${VERSION} + + # Docker Hub + docker manifest create ${{ env.DOCKERHUB_IMAGE_NAME }}${DSUF}:latest \ + --amend ${{ env.DOCKERHUB_IMAGE_NAME }}${DSUF}:latest-amd64 \ + --amend ${{ env.DOCKERHUB_IMAGE_NAME }}${DSUF}:latest-arm64 + docker manifest push ${{ env.DOCKERHUB_IMAGE_NAME }}${DSUF}:latest + + docker manifest create ${{ env.DOCKERHUB_IMAGE_NAME }}${DSUF}:${VERSION} \ + --amend ${{ env.DOCKERHUB_IMAGE_NAME }}${DSUF}:${VERSION}-amd64 \ + --amend ${{ env.DOCKERHUB_IMAGE_NAME }}${DSUF}:${VERSION}-arm64 + docker manifest push ${{ env.DOCKERHUB_IMAGE_NAME }}${DSUF}:${VERSION} + + echo "Pushed multi-arch manifest for ${{ env.DOCKERHUB_IMAGE_NAME }}${DSUF} (latest, ${VERSION})" + # === CREATE GITHUB RELEASE === create-release: runs-on: ubuntu-24.04 - needs: [detect-changes, docker-manifest, js-manifest, essentials-manifest, languages-manifest] + needs: [detect-changes, docker-manifest, js-manifest, essentials-manifest, languages-manifest, dind-manifest] if: | always() && needs.detect-changes.result == 'success' && @@ -2142,6 +2536,27 @@ jobs: | Lean | [\`${DOCKERHUB_IMAGE}-lean:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-lean/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-lean/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-lean/tags?name=${VERSION}-arm64) | | Rocq | [\`${DOCKERHUB_IMAGE}-rocq:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rocq/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rocq/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rocq/tags?name=${VERSION}-arm64) | + ### Docker Hub - dind-box (Docker-in-Docker variants, issue #80) + + Each variant runs an inner Docker daemon. Run with \`docker run --privileged\` (default) or \`docker run --runtime=sysbox-runc\` (recommended for shared hosts). \`docker ps -a\` inside the container only lists containers created by that container — see [docs/case-studies/issue-80](https://github.com/${REPO}/blob/v${VERSION}/docs/case-studies/issue-80/CASE-STUDY.md). + + | Image | Multi-arch | AMD64 | ARM64 | + |-------|------------|-------|-------| + | Full + dind | [\`${DOCKERHUB_IMAGE}-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-dind/tags?name=${VERSION}-arm64) | + | Essentials + dind | [\`${DOCKERHUB_IMAGE}-essentials-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-essentials-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-essentials-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-essentials-dind/tags?name=${VERSION}-arm64) | + | JS + dind | [\`${DOCKERHUB_IMAGE}-js-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-js-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-js-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-js-dind/tags?name=${VERSION}-arm64) | + | Python + dind | [\`${DOCKERHUB_IMAGE}-python-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-python-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-python-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-python-dind/tags?name=${VERSION}-arm64) | + | Go + dind | [\`${DOCKERHUB_IMAGE}-go-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-go-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-go-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-go-dind/tags?name=${VERSION}-arm64) | + | Rust + dind | [\`${DOCKERHUB_IMAGE}-rust-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rust-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rust-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rust-dind/tags?name=${VERSION}-arm64) | + | Java + dind | [\`${DOCKERHUB_IMAGE}-java-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-java-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-java-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-java-dind/tags?name=${VERSION}-arm64) | + | Kotlin + dind | [\`${DOCKERHUB_IMAGE}-kotlin-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-kotlin-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-kotlin-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-kotlin-dind/tags?name=${VERSION}-arm64) | + | Ruby + dind | [\`${DOCKERHUB_IMAGE}-ruby-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-ruby-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-ruby-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-ruby-dind/tags?name=${VERSION}-arm64) | + | PHP + dind | [\`${DOCKERHUB_IMAGE}-php-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-php-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-php-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-php-dind/tags?name=${VERSION}-arm64) | + | Perl + dind | [\`${DOCKERHUB_IMAGE}-perl-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-perl-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-perl-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-perl-dind/tags?name=${VERSION}-arm64) | + | Swift + dind | [\`${DOCKERHUB_IMAGE}-swift-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-swift-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-swift-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-swift-dind/tags?name=${VERSION}-arm64) | + | Lean + dind | [\`${DOCKERHUB_IMAGE}-lean-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-lean-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-lean-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-lean-dind/tags?name=${VERSION}-arm64) | + | Rocq + dind | [\`${DOCKERHUB_IMAGE}-rocq-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rocq-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rocq-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rocq-dind/tags?name=${VERSION}-arm64) | + ### GitHub Container Registry - Combo Boxes | Image | Multi-arch | AMD64 | ARM64 | @@ -2166,6 +2581,25 @@ jobs: | Lean | [\`${GHCR_IMAGE}-lean:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-lean?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-lean?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-lean?tag=${VERSION}-arm64) | | Rocq | [\`${GHCR_IMAGE}-rocq:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-rocq?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-rocq?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-rocq?tag=${VERSION}-arm64) | + ### GitHub Container Registry - dind-box (Docker-in-Docker variants, issue #80) + + | Image | Multi-arch | AMD64 | ARM64 | + |-------|------------|-------|-------| + | Full + dind | [\`${GHCR_IMAGE}-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-dind?tag=${VERSION}-arm64) | + | Essentials + dind | [\`${GHCR_IMAGE}-essentials-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-essentials-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-essentials-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-essentials-dind?tag=${VERSION}-arm64) | + | JS + dind | [\`${GHCR_IMAGE}-js-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-js-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-js-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-js-dind?tag=${VERSION}-arm64) | + | Python + dind | [\`${GHCR_IMAGE}-python-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-python-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-python-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-python-dind?tag=${VERSION}-arm64) | + | Go + dind | [\`${GHCR_IMAGE}-go-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-go-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-go-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-go-dind?tag=${VERSION}-arm64) | + | Rust + dind | [\`${GHCR_IMAGE}-rust-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-rust-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-rust-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-rust-dind?tag=${VERSION}-arm64) | + | Java + dind | [\`${GHCR_IMAGE}-java-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-java-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-java-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-java-dind?tag=${VERSION}-arm64) | + | Kotlin + dind | [\`${GHCR_IMAGE}-kotlin-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-kotlin-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-kotlin-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-kotlin-dind?tag=${VERSION}-arm64) | + | Ruby + dind | [\`${GHCR_IMAGE}-ruby-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-ruby-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-ruby-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-ruby-dind?tag=${VERSION}-arm64) | + | PHP + dind | [\`${GHCR_IMAGE}-php-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-php-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-php-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-php-dind?tag=${VERSION}-arm64) | + | Perl + dind | [\`${GHCR_IMAGE}-perl-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-perl-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-perl-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-perl-dind?tag=${VERSION}-arm64) | + | Swift + dind | [\`${GHCR_IMAGE}-swift-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-swift-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-swift-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-swift-dind?tag=${VERSION}-arm64) | + | Lean + dind | [\`${GHCR_IMAGE}-lean-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-lean-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-lean-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-lean-dind?tag=${VERSION}-arm64) | + | Rocq + dind | [\`${GHCR_IMAGE}-rocq-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-rocq-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-rocq-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-rocq-dind?tag=${VERSION}-arm64) | + ## Architecture \`\`\` diff --git a/README.md b/README.md index ae539cb..c87a8bd 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,15 @@ JS box (konard/box-js) ├─ box-lean (built in parallel) └─ box-rocq (built in parallel) └─ Full box (konard/box) ← merges all via COPY --from + +dind-box variants (issue #80, Docker-in-Docker): + Any of the boxes above also has a "dind" sibling that adds a working + Docker Engine on top of the source image: + + konard/box-js → konard/box-js-dind + konard/box-essentials → konard/box-essentials-dind + konard/box- → konard/box--dind + konard/box (full) → konard/box-dind ``` | Image | Description | Base Image | @@ -77,6 +86,8 @@ JS box (konard/box-js) | `konard/box-swift` | Swift 6.x | Built on essentials | | `konard/box-lean` | Lean (elan) | Built on essentials | | `konard/box-rocq` | Rocq/Coq (Opam) | Built on essentials | +| `konard/box-dind` | Full box + Docker-in-Docker (issue #80) | Layered on Full box | +| `konard/box--dind` | Any language box + Docker-in-Docker (issue #80) | Layered on each language box | ### Per-Language Install Scripts & Dockerfiles @@ -100,6 +111,7 @@ Each language has its own standalone `install.sh` and `Dockerfile` under `ubuntu | Rocq/Coq | `ubuntu/24.04/rocq/` | Opam, Rocq prover | | C/C++ | `ubuntu/24.04/cpp/` | CMake, Clang, LLVM, LLD | | Assembly | `ubuntu/24.04/assembly/` | NASM, FASM (x86_64) | +| Docker-in-Docker | `ubuntu/24.04/dind/` | Docker CE, Buildx, Compose, dockerd entrypoint (issue #80) | Each install script can be run standalone on Ubuntu 24.04: @@ -134,6 +146,27 @@ curl -fsSL https://raw.githubusercontent.com/link-foundation/box/main/ubuntu/24. | Lean | [`konard/box-lean:latest`](https://hub.docker.com/r/konard/box-lean/tags?name=latest) | [`latest-amd64`](https://hub.docker.com/r/konard/box-lean/tags?name=latest-amd64) | [`latest-arm64`](https://hub.docker.com/r/konard/box-lean/tags?name=latest-arm64) | | Rocq | [`konard/box-rocq:latest`](https://hub.docker.com/r/konard/box-rocq/tags?name=latest) | [`latest-amd64`](https://hub.docker.com/r/konard/box-rocq/tags?name=latest-amd64) | [`latest-arm64`](https://hub.docker.com/r/konard/box-rocq/tags?name=latest-arm64) | +### Docker Hub - dind-box (Docker-in-Docker variants, issue #80) + +Each row below has the same toolchain as its non-dind sibling **plus** a working Docker Engine (Docker CLI + dockerd + containerd + Buildx + Compose v2). The default is nested Docker-in-Docker — each container has its own daemon, so `docker ps -a` from inside the container only lists containers it created. See the [security model](#docker-in-docker-security-model) section below and [docs/case-studies/issue-80](docs/case-studies/issue-80/CASE-STUDY.md). + +| Image | Multi-arch | AMD64 | ARM64 | +|-------|------------|-------|-------| +| Full + dind | [`konard/box-dind:latest`](https://hub.docker.com/r/konard/box-dind/tags?name=latest) | [`latest-amd64`](https://hub.docker.com/r/konard/box-dind/tags?name=latest-amd64) | [`latest-arm64`](https://hub.docker.com/r/konard/box-dind/tags?name=latest-arm64) | +| Essentials + dind | [`konard/box-essentials-dind:latest`](https://hub.docker.com/r/konard/box-essentials-dind/tags?name=latest) | [`latest-amd64`](https://hub.docker.com/r/konard/box-essentials-dind/tags?name=latest-amd64) | [`latest-arm64`](https://hub.docker.com/r/konard/box-essentials-dind/tags?name=latest-arm64) | +| JS + dind | [`konard/box-js-dind:latest`](https://hub.docker.com/r/konard/box-js-dind/tags?name=latest) | [`latest-amd64`](https://hub.docker.com/r/konard/box-js-dind/tags?name=latest-amd64) | [`latest-arm64`](https://hub.docker.com/r/konard/box-js-dind/tags?name=latest-arm64) | +| Python + dind | [`konard/box-python-dind:latest`](https://hub.docker.com/r/konard/box-python-dind/tags?name=latest) | [`latest-amd64`](https://hub.docker.com/r/konard/box-python-dind/tags?name=latest-amd64) | [`latest-arm64`](https://hub.docker.com/r/konard/box-python-dind/tags?name=latest-arm64) | +| Go + dind | [`konard/box-go-dind:latest`](https://hub.docker.com/r/konard/box-go-dind/tags?name=latest) | [`latest-amd64`](https://hub.docker.com/r/konard/box-go-dind/tags?name=latest-amd64) | [`latest-arm64`](https://hub.docker.com/r/konard/box-go-dind/tags?name=latest-arm64) | +| Rust + dind | [`konard/box-rust-dind:latest`](https://hub.docker.com/r/konard/box-rust-dind/tags?name=latest) | [`latest-amd64`](https://hub.docker.com/r/konard/box-rust-dind/tags?name=latest-amd64) | [`latest-arm64`](https://hub.docker.com/r/konard/box-rust-dind/tags?name=latest-arm64) | +| Java + dind | [`konard/box-java-dind:latest`](https://hub.docker.com/r/konard/box-java-dind/tags?name=latest) | [`latest-amd64`](https://hub.docker.com/r/konard/box-java-dind/tags?name=latest-amd64) | [`latest-arm64`](https://hub.docker.com/r/konard/box-java-dind/tags?name=latest-arm64) | +| Kotlin + dind | [`konard/box-kotlin-dind:latest`](https://hub.docker.com/r/konard/box-kotlin-dind/tags?name=latest) | [`latest-amd64`](https://hub.docker.com/r/konard/box-kotlin-dind/tags?name=latest-amd64) | [`latest-arm64`](https://hub.docker.com/r/konard/box-kotlin-dind/tags?name=latest-arm64) | +| Ruby + dind | [`konard/box-ruby-dind:latest`](https://hub.docker.com/r/konard/box-ruby-dind/tags?name=latest) | [`latest-amd64`](https://hub.docker.com/r/konard/box-ruby-dind/tags?name=latest-amd64) | [`latest-arm64`](https://hub.docker.com/r/konard/box-ruby-dind/tags?name=latest-arm64) | +| PHP + dind | [`konard/box-php-dind:latest`](https://hub.docker.com/r/konard/box-php-dind/tags?name=latest) | [`latest-amd64`](https://hub.docker.com/r/konard/box-php-dind/tags?name=latest-amd64) | [`latest-arm64`](https://hub.docker.com/r/konard/box-php-dind/tags?name=latest-arm64) | +| Perl + dind | [`konard/box-perl-dind:latest`](https://hub.docker.com/r/konard/box-perl-dind/tags?name=latest) | [`latest-amd64`](https://hub.docker.com/r/konard/box-perl-dind/tags?name=latest-amd64) | [`latest-arm64`](https://hub.docker.com/r/konard/box-perl-dind/tags?name=latest-arm64) | +| Swift + dind | [`konard/box-swift-dind:latest`](https://hub.docker.com/r/konard/box-swift-dind/tags?name=latest) | [`latest-amd64`](https://hub.docker.com/r/konard/box-swift-dind/tags?name=latest-amd64) | [`latest-arm64`](https://hub.docker.com/r/konard/box-swift-dind/tags?name=latest-arm64) | +| Lean + dind | [`konard/box-lean-dind:latest`](https://hub.docker.com/r/konard/box-lean-dind/tags?name=latest) | [`latest-amd64`](https://hub.docker.com/r/konard/box-lean-dind/tags?name=latest-amd64) | [`latest-arm64`](https://hub.docker.com/r/konard/box-lean-dind/tags?name=latest-arm64) | +| Rocq + dind | [`konard/box-rocq-dind:latest`](https://hub.docker.com/r/konard/box-rocq-dind/tags?name=latest) | [`latest-amd64`](https://hub.docker.com/r/konard/box-rocq-dind/tags?name=latest-amd64) | [`latest-arm64`](https://hub.docker.com/r/konard/box-rocq-dind/tags?name=latest-arm64) | + ### GitHub Container Registry - Combo Boxes | Image | Multi-arch | AMD64 | ARM64 | @@ -158,6 +191,36 @@ curl -fsSL https://raw.githubusercontent.com/link-foundation/box/main/ubuntu/24. | Lean | [`ghcr.io/link-foundation/box-lean:latest`](https://github.com/link-foundation/box/pkgs/container/box-lean?tag=latest) | [`latest-amd64`](https://github.com/link-foundation/box/pkgs/container/box-lean?tag=latest-amd64) | [`latest-arm64`](https://github.com/link-foundation/box/pkgs/container/box-lean?tag=latest-arm64) | | Rocq | [`ghcr.io/link-foundation/box-rocq:latest`](https://github.com/link-foundation/box/pkgs/container/box-rocq?tag=latest) | [`latest-amd64`](https://github.com/link-foundation/box/pkgs/container/box-rocq?tag=latest-amd64) | [`latest-arm64`](https://github.com/link-foundation/box/pkgs/container/box-rocq?tag=latest-arm64) | +### GitHub Container Registry - dind-box (Docker-in-Docker variants, issue #80) + +| Image | Multi-arch | AMD64 | ARM64 | +|-------|------------|-------|-------| +| Full + dind | [`ghcr.io/link-foundation/box-dind:latest`](https://github.com/link-foundation/box/pkgs/container/box-dind?tag=latest) | [`latest-amd64`](https://github.com/link-foundation/box/pkgs/container/box-dind?tag=latest-amd64) | [`latest-arm64`](https://github.com/link-foundation/box/pkgs/container/box-dind?tag=latest-arm64) | +| Essentials + dind | [`ghcr.io/link-foundation/box-essentials-dind:latest`](https://github.com/link-foundation/box/pkgs/container/box-essentials-dind?tag=latest) | [`latest-amd64`](https://github.com/link-foundation/box/pkgs/container/box-essentials-dind?tag=latest-amd64) | [`latest-arm64`](https://github.com/link-foundation/box/pkgs/container/box-essentials-dind?tag=latest-arm64) | +| JS + dind | [`ghcr.io/link-foundation/box-js-dind:latest`](https://github.com/link-foundation/box/pkgs/container/box-js-dind?tag=latest) | [`latest-amd64`](https://github.com/link-foundation/box/pkgs/container/box-js-dind?tag=latest-amd64) | [`latest-arm64`](https://github.com/link-foundation/box/pkgs/container/box-js-dind?tag=latest-arm64) | +| Python + dind | [`ghcr.io/link-foundation/box-python-dind:latest`](https://github.com/link-foundation/box/pkgs/container/box-python-dind?tag=latest) | [`latest-amd64`](https://github.com/link-foundation/box/pkgs/container/box-python-dind?tag=latest-amd64) | [`latest-arm64`](https://github.com/link-foundation/box/pkgs/container/box-python-dind?tag=latest-arm64) | +| Go + dind | [`ghcr.io/link-foundation/box-go-dind:latest`](https://github.com/link-foundation/box/pkgs/container/box-go-dind?tag=latest) | [`latest-amd64`](https://github.com/link-foundation/box/pkgs/container/box-go-dind?tag=latest-amd64) | [`latest-arm64`](https://github.com/link-foundation/box/pkgs/container/box-go-dind?tag=latest-arm64) | +| Rust + dind | [`ghcr.io/link-foundation/box-rust-dind:latest`](https://github.com/link-foundation/box/pkgs/container/box-rust-dind?tag=latest) | [`latest-amd64`](https://github.com/link-foundation/box/pkgs/container/box-rust-dind?tag=latest-amd64) | [`latest-arm64`](https://github.com/link-foundation/box/pkgs/container/box-rust-dind?tag=latest-arm64) | +| Java + dind | [`ghcr.io/link-foundation/box-java-dind:latest`](https://github.com/link-foundation/box/pkgs/container/box-java-dind?tag=latest) | [`latest-amd64`](https://github.com/link-foundation/box/pkgs/container/box-java-dind?tag=latest-amd64) | [`latest-arm64`](https://github.com/link-foundation/box/pkgs/container/box-java-dind?tag=latest-arm64) | +| Kotlin + dind | [`ghcr.io/link-foundation/box-kotlin-dind:latest`](https://github.com/link-foundation/box/pkgs/container/box-kotlin-dind?tag=latest) | [`latest-amd64`](https://github.com/link-foundation/box/pkgs/container/box-kotlin-dind?tag=latest-amd64) | [`latest-arm64`](https://github.com/link-foundation/box/pkgs/container/box-kotlin-dind?tag=latest-arm64) | +| Ruby + dind | [`ghcr.io/link-foundation/box-ruby-dind:latest`](https://github.com/link-foundation/box/pkgs/container/box-ruby-dind?tag=latest) | [`latest-amd64`](https://github.com/link-foundation/box/pkgs/container/box-ruby-dind?tag=latest-amd64) | [`latest-arm64`](https://github.com/link-foundation/box/pkgs/container/box-ruby-dind?tag=latest-arm64) | +| PHP + dind | [`ghcr.io/link-foundation/box-php-dind:latest`](https://github.com/link-foundation/box/pkgs/container/box-php-dind?tag=latest) | [`latest-amd64`](https://github.com/link-foundation/box/pkgs/container/box-php-dind?tag=latest-amd64) | [`latest-arm64`](https://github.com/link-foundation/box/pkgs/container/box-php-dind?tag=latest-arm64) | +| Perl + dind | [`ghcr.io/link-foundation/box-perl-dind:latest`](https://github.com/link-foundation/box/pkgs/container/box-perl-dind?tag=latest) | [`latest-amd64`](https://github.com/link-foundation/box/pkgs/container/box-perl-dind?tag=latest-amd64) | [`latest-arm64`](https://github.com/link-foundation/box/pkgs/container/box-perl-dind?tag=latest-arm64) | +| Swift + dind | [`ghcr.io/link-foundation/box-swift-dind:latest`](https://github.com/link-foundation/box/pkgs/container/box-swift-dind?tag=latest) | [`latest-amd64`](https://github.com/link-foundation/box/pkgs/container/box-swift-dind?tag=latest-amd64) | [`latest-arm64`](https://github.com/link-foundation/box/pkgs/container/box-swift-dind?tag=latest-arm64) | +| Lean + dind | [`ghcr.io/link-foundation/box-lean-dind:latest`](https://github.com/link-foundation/box/pkgs/container/box-lean-dind?tag=latest) | [`latest-amd64`](https://github.com/link-foundation/box/pkgs/container/box-lean-dind?tag=latest-amd64) | [`latest-arm64`](https://github.com/link-foundation/box/pkgs/container/box-lean-dind?tag=latest-arm64) | +| Rocq + dind | [`ghcr.io/link-foundation/box-rocq-dind:latest`](https://github.com/link-foundation/box/pkgs/container/box-rocq-dind?tag=latest) | [`latest-amd64`](https://github.com/link-foundation/box/pkgs/container/box-rocq-dind?tag=latest-amd64) | [`latest-arm64`](https://github.com/link-foundation/box/pkgs/container/box-rocq-dind?tag=latest-arm64) | + +### Docker-in-Docker security model + +> ⚠️ **dind-box variants ship a Docker daemon and require elevated privileges to run.** +> +> - **Default invocation:** `docker run --privileged konard/box-dind` — the inner `dockerd` needs `CAP_SYS_ADMIN` and access to the kernel's overlay/namespace machinery. Each container has its own daemon, so `docker ps -a` from inside lists only that container's children. +> - **Recommended secure invocation:** [`docker run --runtime=sysbox-runc konard/box-dind`](https://github.com/nestybox/sysbox) — Sysbox is a drop-in OCI runtime that runs system containers without `--privileged` and without exposing host devices. +> - **Do NOT bind-mount `/var/run/docker.sock`.** That gives the container root on the host ([Quarkslab](https://blog.quarkslab.com/why-is-exposing-the-docker-socket-a-really-bad-idea.html), [OWASP](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html)) and breaks the per-box `docker ps` scoping property. +> - **Storage:** the inner daemon writes to `/var/lib/docker` inside the container by default. For persistence, mount a volume: `-v box-dind-data:/var/lib/docker`. + +See [docs/case-studies/issue-80/CASE-STUDY.md](docs/case-studies/issue-80/CASE-STUDY.md) for the full design and threat model. + ## Usage ### Quick Start diff --git a/docs/case-studies/issue-80/CASE-STUDY.md b/docs/case-studies/issue-80/CASE-STUDY.md index 586d6e6..76233de 100644 --- a/docs/case-studies/issue-80/CASE-STUDY.md +++ b/docs/case-studies/issue-80/CASE-STUDY.md @@ -1,4 +1,21 @@ -# Case Study: Issue #80 — `konard/super-box` (Docker‑in‑Box) +# Case Study: Issue #80 — `konard/box-dind` family (Docker‑in‑Box) + +## Implementation Update (2026‑04‑28) + +The case study below was written with the working name `konard/super-box`. After review, the project owner picked the final naming and scope: + +- **Final name:** `dind-box` (suffix `-dind`), not `super-box`. +- **Scope:** add a dind sibling for **every** existing image variant (`-js`, `-essentials`, every language box, and the full box). Image names follow the existing convention: + - `konard/box` → `konard/box-dind` + - `konard/box-essentials` → `konard/box-essentials-dind` + - `konard/box-js` → `konard/box-js-dind` + - `konard/box-` → `konard/box--dind` +- **Implementation:** a single generic recipe at [`ubuntu/24.04/dind/`](../../../ubuntu/24.04/dind/) (one `Dockerfile`, one `install.sh`, one `dind-entrypoint.sh`) that takes any base image as `--build-arg BASE_IMAGE=...` and produces the dind variant. The release workflow runs this recipe in a 14×2 (variant × arch) matrix and then assembles per‑variant multi‑arch manifests. +- **Architecture, security model, and host‑isolation guarantees are unchanged** from sections 3–6 below: the default tag is nested DinD on `--privileged`, Sysbox is the recommended secure runtime, DooD is rejected as a default, and `docker ps -a` is naturally scoped per container because each container owns its own `dockerd`. + +The rest of this document is preserved as written so the original analysis remains auditable; references to `super-box` should be read as `box-dind`. + +--- ## Executive Summary diff --git a/ubuntu/24.04/dind/Dockerfile b/ubuntu/24.04/dind/Dockerfile new file mode 100644 index 0000000..d9ae6e8 --- /dev/null +++ b/ubuntu/24.04/dind/Dockerfile @@ -0,0 +1,58 @@ +# dind-box Docker image (Issue #80) +# +# Adds a working Docker Engine (dockerd + CLI + containerd + Buildx + Compose) +# on top of any existing box image. Generic by design: pass any of the project's +# images as BASE_IMAGE and you get a "-dind" variant. +# +# docker build -f ubuntu/24.04/dind/Dockerfile \ +# --build-arg BASE_IMAGE=konard/box:latest \ +# -t konard/box-dind . +# +# docker build -f ubuntu/24.04/dind/Dockerfile \ +# --build-arg BASE_IMAGE=konard/box-python:latest \ +# -t konard/box-python-dind . +# +# Run (default): docker run --privileged konard/box-dind +# Run (Sysbox/safe): docker run --runtime=sysbox-runc konard/box-dind +# +# Each running dind-box has its own inner dockerd. `docker ps -a` from inside +# the container therefore only lists containers created by *this* box, which +# satisfies the host-isolation stretch goal in issue #80. +# +# See docs/case-studies/issue-80/CASE-STUDY.md for the full design rationale. + +ARG BASE_IMAGE=konard/box:latest +FROM ${BASE_IMAGE} + +USER root +ENV DEBIAN_FRONTEND=noninteractive + +# Marker that bakes which base image this dind variant was layered on. +# Used by tests and by the README size table. +ARG BASE_IMAGE +ENV BOX_DIND_BASE_IMAGE=${BASE_IMAGE} + +# Copy install layer +COPY ubuntu/24.04/common.sh /tmp/common.sh +COPY ubuntu/24.04/dind/install.sh /tmp/install.sh +COPY ubuntu/24.04/dind/dind-entrypoint.sh /tmp/dind-entrypoint.sh + +RUN chmod +x /tmp/install.sh /tmp/common.sh /tmp/dind-entrypoint.sh && \ + bash /tmp/install.sh && \ + rm -f /tmp/install.sh /tmp/common.sh /tmp/dind-entrypoint.sh + +# Sanity: docker, dockerd, buildx, compose must all be present. +RUN docker --version && \ + dockerd --version && \ + docker buildx version && \ + docker compose version + +# Keep WORKDIR consistent with the rest of the box images. +WORKDIR /home/box + +SHELL ["/bin/bash", "-c"] + +# IMPORTANT: dind-entrypoint.sh starts dockerd as root and then drops to box +# via runuser/su. Do not switch back to USER box here. +ENTRYPOINT ["/usr/local/bin/dind-entrypoint.sh"] +CMD ["/bin/bash"] diff --git a/ubuntu/24.04/dind/dind-entrypoint.sh b/ubuntu/24.04/dind/dind-entrypoint.sh new file mode 100644 index 0000000..d529f93 --- /dev/null +++ b/ubuntu/24.04/dind/dind-entrypoint.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +# Entrypoint for dind-box images. +# +# Responsibilities: +# 1. Start the inner Docker daemon (dockerd) in the background as root, +# with a storage driver suitable for "Docker inside a container". +# 2. Wait for dockerd to be ready on /var/run/docker.sock. +# 3. Hand off to the standard /usr/local/bin/entrypoint.sh as the box user +# so all language environments load exactly like in the regular box. +# +# This is the recommended pattern from docker:dind and cruizba/ubuntu-dind. +# See docs/case-studies/issue-80/CASE-STUDY.md for the full design rationale. +# +# Required runtime privileges (host side): +# - Default: docker run --privileged konard/-dind +# - Sysbox : docker run --runtime=sysbox-runc konard/-dind (no --privileged) +# +# Environment overrides: +# DIND_STORAGE_DRIVER Override storage driver (default: auto-detected: overlay2, fallback to vfs) +# DIND_DATA_ROOT Override --data-root for dockerd (default: /var/lib/docker) +# DIND_LOG_FILE Where to write dockerd logs (default: /var/log/dockerd.log) +# DIND_WAIT_SECONDS How long to wait for dockerd to come up (default: 30) +# DIND_SKIP_DAEMON If set to "1", do not start dockerd (use for DooD/Sysbox-only mode) + +set -eu + +DIND_STORAGE_DRIVER="${DIND_STORAGE_DRIVER:-}" +DIND_DATA_ROOT="${DIND_DATA_ROOT:-/var/lib/docker}" +DIND_LOG_FILE="${DIND_LOG_FILE:-/var/log/dockerd.log}" +DIND_WAIT_SECONDS="${DIND_WAIT_SECONDS:-30}" +DIND_SKIP_DAEMON="${DIND_SKIP_DAEMON:-0}" + +log() { echo "[dind-entrypoint] $*"; } +warn() { echo "[dind-entrypoint] WARN: $*" >&2; } + +start_dockerd() { + if pgrep -x dockerd >/dev/null 2>&1; then + log "dockerd already running (pid $(pgrep -x dockerd | head -n1))" + return 0 + fi + + mkdir -p "$DIND_DATA_ROOT" /var/log /var/run + + # Pick a storage driver. overlay2 is the modern default; if it fails (the host + # can't mount overlay-on-overlay without fuse-overlayfs), fall back to vfs. + if [ -z "$DIND_STORAGE_DRIVER" ]; then + if grep -q overlay /proc/filesystems 2>/dev/null; then + DIND_STORAGE_DRIVER="overlay2" + elif command -v fuse-overlayfs >/dev/null 2>&1; then + DIND_STORAGE_DRIVER="fuse-overlayfs" + else + DIND_STORAGE_DRIVER="vfs" + fi + fi + log "Starting dockerd (storage-driver=${DIND_STORAGE_DRIVER}, data-root=${DIND_DATA_ROOT})" + + # iptables module may not be available in the outer container; let dockerd handle it. + nohup dockerd \ + --host=unix:///var/run/docker.sock \ + --data-root="$DIND_DATA_ROOT" \ + --storage-driver="$DIND_STORAGE_DRIVER" \ + >>"$DIND_LOG_FILE" 2>&1 & + + # Wait until dockerd answers on /var/run/docker.sock. + i=0 + while [ "$i" -lt "$DIND_WAIT_SECONDS" ]; do + if docker info >/dev/null 2>&1; then + log "dockerd is ready after ${i}s" + return 0 + fi + i=$((i + 1)) + sleep 1 + done + + warn "dockerd did not become ready within ${DIND_WAIT_SECONDS}s" + warn "Last 40 lines of ${DIND_LOG_FILE}:" + tail -n 40 "$DIND_LOG_FILE" >&2 || true + warn "Continuing anyway; the user shell will still start, but 'docker' may fail" + return 0 +} + +if [ "$DIND_SKIP_DAEMON" != "1" ]; then + if [ "$(id -u)" -eq 0 ]; then + start_dockerd || true + else + warn "Not running as root; cannot start dockerd. Use --user root or set DIND_SKIP_DAEMON=1 to silence." + fi +fi + +# Ensure the docker socket is group-readable for the box user. +if [ -S /var/run/docker.sock ]; then + chgrp docker /var/run/docker.sock 2>/dev/null || true + chmod 660 /var/run/docker.sock 2>/dev/null || true +fi + +# Hand off to the box user via the existing entrypoint, which sources all the +# language environment managers. If no upstream entrypoint exists (e.g. base +# image is the bare js box), exec the command directly. +if [ "$#" -eq 0 ]; then + set -- /bin/bash +fi + +INNER_ENTRYPOINT="" +if [ -x /usr/local/bin/entrypoint.sh ]; then + INNER_ENTRYPOINT="/usr/local/bin/entrypoint.sh" +fi + +if [ "$(id -u)" -eq 0 ] && id box >/dev/null 2>&1; then + if [ -n "$INNER_ENTRYPOINT" ]; then + if command -v runuser >/dev/null 2>&1; then + exec runuser -u box -- "$INNER_ENTRYPOINT" "$@" + else + exec su - box -c "$INNER_ENTRYPOINT $(printf '%q ' "$@")" + fi + else + if command -v runuser >/dev/null 2>&1; then + exec runuser -u box -- "$@" + else + exec su - box -c "$(printf '%q ' "$@")" + fi + fi +fi + +if [ -n "$INNER_ENTRYPOINT" ]; then + exec "$INNER_ENTRYPOINT" "$@" +fi +exec "$@" diff --git a/ubuntu/24.04/dind/install.sh b/ubuntu/24.04/dind/install.sh new file mode 100644 index 0000000..1036533 --- /dev/null +++ b/ubuntu/24.04/dind/install.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Docker-in-Docker (dind-box) installation script +# Adds the Docker Engine, CLI, containerd, Buildx, and Compose plugins to any +# existing box image (js, essentials, full, or any language box). +# +# Usage: BASE_VARIANT= bash ubuntu/24.04/dind/install.sh +# +# This script is idempotent and runs as root during image build. +# It also adds the box user to the docker group so the inner dockerd is usable +# without sudo from the user shell. +# +# References: +# - https://docs.docker.com/engine/install/ubuntu/ +# - https://github.com/cruizba/ubuntu-dind +# - docs/case-studies/issue-80/CASE-STUDY.md + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ -f "$SCRIPT_DIR/../common.sh" ]; then + # shellcheck disable=SC1091 + source "$SCRIPT_DIR/../common.sh" +else + log_info() { echo "[*] $1"; } + log_success() { echo "[✓] $1"; } + log_warning() { echo "[!] $1"; } + log_error() { echo "[✗] $1"; } + log_step() { echo "==> $1"; } + command_exists() { command -v "$1" &>/dev/null; } + maybe_sudo() { if [ "$EUID" -eq 0 ]; then "$@"; elif command -v sudo &>/dev/null; then sudo "$@"; else "$@"; fi; } +fi + +log_step "Installing Docker-in-Docker (dind-box) layer" + +# --- Pre-flight --- +if [ "$EUID" -ne 0 ] && ! sudo -n true 2>/dev/null; then + log_error "This script requires sudo access." + exit 1 +fi + +ARCH="$(dpkg --print-architecture)" +log_info "Detected architecture: $ARCH" + +# --- Install Docker Engine, CLI, containerd, Buildx, Compose --- +log_step "Adding Docker apt repository" + +export DEBIAN_FRONTEND=noninteractive + +maybe_sudo apt-get update -y +maybe_sudo apt-get install -y \ + ca-certificates curl gnupg lsb-release iptables uidmap + +maybe_sudo install -m 0755 -d /etc/apt/keyrings +if [ ! -f /etc/apt/keyrings/docker.gpg ]; then + curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ + | maybe_sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg + maybe_sudo chmod a+r /etc/apt/keyrings/docker.gpg +fi + +UBUNTU_CODENAME="$(. /etc/os-release && echo "${UBUNTU_CODENAME:-${VERSION_CODENAME:-noble}}")" +echo "deb [arch=${ARCH} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu ${UBUNTU_CODENAME} stable" \ + | maybe_sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + +log_step "Installing docker-ce, docker-ce-cli, containerd.io, buildx, compose" +maybe_sudo apt-get update -y +maybe_sudo apt-get install -y \ + docker-ce \ + docker-ce-cli \ + containerd.io \ + docker-buildx-plugin \ + docker-compose-plugin \ + fuse-overlayfs + +log_success "Docker packages installed" + +# --- Ensure docker group exists and add box user --- +log_step "Configuring docker group for box user" +if ! getent group docker >/dev/null; then + maybe_sudo groupadd docker +fi +if id box &>/dev/null; then + maybe_sudo usermod -aG docker box + log_success "Added box user to docker group" +else + log_warning "box user not present yet; skipping group membership" +fi + +# --- Install dind entrypoint --- +log_step "Installing dind entrypoint" +maybe_sudo install -m 0755 "$SCRIPT_DIR/dind-entrypoint.sh" /usr/local/bin/dind-entrypoint.sh +log_success "dind entrypoint installed at /usr/local/bin/dind-entrypoint.sh" + +# --- Persist marker so users / tests can detect dind-box images --- +maybe_sudo mkdir -p /etc/box +echo "dind-box" | maybe_sudo tee /etc/box/variant >/dev/null + +# --- Cleanup --- +log_step "Cleaning up apt caches" +maybe_sudo apt-get clean +maybe_sudo rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +log_step "dind-box layer installation complete" +log_success "Run with: docker run --privileged konard/-dind" From 50534558329b3822e43ec6cdf1ca2f96ead8706f Mon Sep 17 00:00:00 2001 From: konard Date: Tue, 28 Apr 2026 14:14:31 +0000 Subject: [PATCH 4/8] fix(workflow): use block-style YAML for dind matrix entries Hyphen-prefixed values like '-js' inside YAML flow-style mappings caused GitHub Actions' workflow parser to reject the file (no jobs at all in the last run). Switching to explicit block-style with quoted strings makes the intent unambiguous and matches the style used elsewhere in this workflow. --- .github/workflows/release.yml | 128 ++++++++++++++++++++++------------ 1 file changed, 85 insertions(+), 43 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8b68e16..7309f60 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2115,22 +2115,36 @@ jobs: fail-fast: false matrix: # variant.name -> tag suffix on box images, e.g. "js" => konard/box-js-dind - # variant.base -> the base image suffix (empty for full box "konard/box") + # variant.base_image_suffix -> the base image suffix (empty for full box "konard/box") variant: - - { name: js, base_image_suffix: -js, needs_manifest: js } - - { name: essentials, base_image_suffix: -essentials, needs_manifest: essentials } - - { name: python, base_image_suffix: -python, needs_manifest: languages } - - { name: go, base_image_suffix: -go, needs_manifest: languages } - - { name: rust, base_image_suffix: -rust, needs_manifest: languages } - - { name: java, base_image_suffix: -java, needs_manifest: languages } - - { name: kotlin, base_image_suffix: -kotlin, needs_manifest: languages } - - { name: ruby, base_image_suffix: -ruby, needs_manifest: languages } - - { name: php, base_image_suffix: -php, needs_manifest: languages } - - { name: perl, base_image_suffix: -perl, needs_manifest: languages } - - { name: swift, base_image_suffix: -swift, needs_manifest: languages } - - { name: lean, base_image_suffix: -lean, needs_manifest: languages } - - { name: rocq, base_image_suffix: -rocq, needs_manifest: languages } - - { name: full, base_image_suffix: '', needs_manifest: docker } + - name: "js" + base_image_suffix: "-js" + - name: "essentials" + base_image_suffix: "-essentials" + - name: "python" + base_image_suffix: "-python" + - name: "go" + base_image_suffix: "-go" + - name: "rust" + base_image_suffix: "-rust" + - name: "java" + base_image_suffix: "-java" + - name: "kotlin" + base_image_suffix: "-kotlin" + - name: "ruby" + base_image_suffix: "-ruby" + - name: "php" + base_image_suffix: "-php" + - name: "perl" + base_image_suffix: "-perl" + - name: "swift" + base_image_suffix: "-swift" + - name: "lean" + base_image_suffix: "-lean" + - name: "rocq" + base_image_suffix: "-rocq" + - name: "full" + base_image_suffix: "" if: | always() && needs.detect-changes.result == 'success' && @@ -2249,20 +2263,34 @@ jobs: fail-fast: false matrix: variant: - - { name: js, base_image_suffix: -js, needs_manifest: js } - - { name: essentials, base_image_suffix: -essentials, needs_manifest: essentials } - - { name: python, base_image_suffix: -python, needs_manifest: languages } - - { name: go, base_image_suffix: -go, needs_manifest: languages } - - { name: rust, base_image_suffix: -rust, needs_manifest: languages } - - { name: java, base_image_suffix: -java, needs_manifest: languages } - - { name: kotlin, base_image_suffix: -kotlin, needs_manifest: languages } - - { name: ruby, base_image_suffix: -ruby, needs_manifest: languages } - - { name: php, base_image_suffix: -php, needs_manifest: languages } - - { name: perl, base_image_suffix: -perl, needs_manifest: languages } - - { name: swift, base_image_suffix: -swift, needs_manifest: languages } - - { name: lean, base_image_suffix: -lean, needs_manifest: languages } - - { name: rocq, base_image_suffix: -rocq, needs_manifest: languages } - - { name: full, base_image_suffix: '', needs_manifest: docker } + - name: "js" + base_image_suffix: "-js" + - name: "essentials" + base_image_suffix: "-essentials" + - name: "python" + base_image_suffix: "-python" + - name: "go" + base_image_suffix: "-go" + - name: "rust" + base_image_suffix: "-rust" + - name: "java" + base_image_suffix: "-java" + - name: "kotlin" + base_image_suffix: "-kotlin" + - name: "ruby" + base_image_suffix: "-ruby" + - name: "php" + base_image_suffix: "-php" + - name: "perl" + base_image_suffix: "-perl" + - name: "swift" + base_image_suffix: "-swift" + - name: "lean" + base_image_suffix: "-lean" + - name: "rocq" + base_image_suffix: "-rocq" + - name: "full" + base_image_suffix: "" if: | always() && needs.detect-changes.result == 'success' && @@ -2378,20 +2406,34 @@ jobs: fail-fast: false matrix: variant: - - { name: js, base_image_suffix: -js } - - { name: essentials, base_image_suffix: -essentials } - - { name: python, base_image_suffix: -python } - - { name: go, base_image_suffix: -go } - - { name: rust, base_image_suffix: -rust } - - { name: java, base_image_suffix: -java } - - { name: kotlin, base_image_suffix: -kotlin } - - { name: ruby, base_image_suffix: -ruby } - - { name: php, base_image_suffix: -php } - - { name: perl, base_image_suffix: -perl } - - { name: swift, base_image_suffix: -swift } - - { name: lean, base_image_suffix: -lean } - - { name: rocq, base_image_suffix: -rocq } - - { name: full, base_image_suffix: '' } + - name: "js" + base_image_suffix: "-js" + - name: "essentials" + base_image_suffix: "-essentials" + - name: "python" + base_image_suffix: "-python" + - name: "go" + base_image_suffix: "-go" + - name: "rust" + base_image_suffix: "-rust" + - name: "java" + base_image_suffix: "-java" + - name: "kotlin" + base_image_suffix: "-kotlin" + - name: "ruby" + base_image_suffix: "-ruby" + - name: "php" + base_image_suffix: "-php" + - name: "perl" + base_image_suffix: "-perl" + - name: "swift" + base_image_suffix: "-swift" + - name: "lean" + base_image_suffix: "-lean" + - name: "rocq" + base_image_suffix: "-rocq" + - name: "full" + base_image_suffix: "" if: | always() && needs.detect-changes.result == 'success' && From 519bc62f3ed1054e9e54907d47ca367b996ae841 Mon Sep 17 00:00:00 2001 From: konard Date: Tue, 28 Apr 2026 14:18:57 +0000 Subject: [PATCH 5/8] fix(workflow): use simple-string matrix for dind variants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub Actions accepts object-typed matrix entries, but actionlint flagged 'object should not be evaluated in template with ${{ }}' for steps that referenced ${{ matrix.variant }} without a sub-key. The previous push to main with object-typed matrices failed at workflow load (zero jobs, zero billable time, run name showed file path instead of workflow name) — this matches GitHub's behaviour when it can't materialize the matrix. Switch to a flat list of strings and derive base_image_suffix / dind_suffix inline in each step. Same matrix size (14 entries × 2 archs). --- .github/workflows/release.yml | 185 ++++++++++++++-------------------- 1 file changed, 73 insertions(+), 112 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7309f60..f6034e3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2114,37 +2114,23 @@ jobs: strategy: fail-fast: false matrix: - # variant.name -> tag suffix on box images, e.g. "js" => konard/box-js-dind - # variant.base_image_suffix -> the base image suffix (empty for full box "konard/box") + # variant -> base box flavour. Special value "full" maps to konard/box. + # Everything else maps to konard/box-. variant: - - name: "js" - base_image_suffix: "-js" - - name: "essentials" - base_image_suffix: "-essentials" - - name: "python" - base_image_suffix: "-python" - - name: "go" - base_image_suffix: "-go" - - name: "rust" - base_image_suffix: "-rust" - - name: "java" - base_image_suffix: "-java" - - name: "kotlin" - base_image_suffix: "-kotlin" - - name: "ruby" - base_image_suffix: "-ruby" - - name: "php" - base_image_suffix: "-php" - - name: "perl" - base_image_suffix: "-perl" - - name: "swift" - base_image_suffix: "-swift" - - name: "lean" - base_image_suffix: "-lean" - - name: "rocq" - base_image_suffix: "-rocq" - - name: "full" - base_image_suffix: "" + - "js" + - "essentials" + - "python" + - "go" + - "rust" + - "java" + - "kotlin" + - "ruby" + - "php" + - "perl" + - "swift" + - "lean" + - "rocq" + - "full" if: | always() && needs.detect-changes.result == 'success' && @@ -2176,16 +2162,18 @@ jobs: - name: Compute image names id: names run: | - BASE_SUFFIX="${{ matrix.variant.base_image_suffix }}" - NAME="${{ matrix.variant.name }}" - # Source base image (no -dind): - echo "base_image=${{ env.DOCKERHUB_IMAGE_NAME }}${BASE_SUFFIX}:${{ steps.version.outputs.version }}-amd64" >> $GITHUB_OUTPUT - # Target dind image suffix: full -> "-dind", others -> "-dind" + NAME="${{ matrix.variant }}" + # Source base image: full -> "konard/box"; others -> "konard/box-" + # Target dind image: full -> "konard/box-dind"; others -> "konard/box--dind" if [ "$NAME" = "full" ]; then - echo "dind_suffix=-dind" >> $GITHUB_OUTPUT + BASE_SUFFIX="" + DIND_SUFFIX="-dind" else - echo "dind_suffix=${BASE_SUFFIX}-dind" >> $GITHUB_OUTPUT + BASE_SUFFIX="-$NAME" + DIND_SUFFIX="-${NAME}-dind" fi + echo "base_image=${{ env.DOCKERHUB_IMAGE_NAME }}${BASE_SUFFIX}:${{ steps.version.outputs.version }}-amd64" >> $GITHUB_OUTPUT + echo "dind_suffix=${DIND_SUFFIX}" >> $GITHUB_OUTPUT - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -2204,7 +2192,7 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push ${{ matrix.variant.name }} dind-box (amd64) + - name: Build and push ${{ matrix.variant }} dind-box (amd64) id: build-push continue-on-error: true uses: docker/build-push-action@v5 @@ -2221,10 +2209,10 @@ jobs: ${{ env.DOCKERHUB_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:latest-amd64 ${{ env.DOCKERHUB_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:${{ steps.version.outputs.version }}-amd64 provenance: false - cache-from: type=gha,scope=dind-${{ matrix.variant.name }}-amd64 - cache-to: type=gha,scope=dind-${{ matrix.variant.name }}-amd64,mode=max + cache-from: type=gha,scope=dind-${{ matrix.variant }}-amd64 + cache-to: type=gha,scope=dind-${{ matrix.variant }}-amd64,mode=max - - name: Retry push ${{ matrix.variant.name }} dind-box (amd64) on failure + - name: Retry push ${{ matrix.variant }} dind-box (amd64) on failure if: steps.build-push.outcome == 'failure' run: | echo "First push attempt failed, retrying with backoff..." @@ -2239,7 +2227,7 @@ jobs: --tag ${{ env.DOCKERHUB_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:latest-amd64 \ --tag ${{ env.DOCKERHUB_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:${{ steps.version.outputs.version }}-amd64 \ --provenance=false \ - --cache-from type=gha,scope=dind-${{ matrix.variant.name }}-amd64 \ + --cache-from type=gha,scope=dind-${{ matrix.variant }}-amd64 \ --push \ .; then echo "==> Push succeeded on retry attempt $attempt" @@ -2263,34 +2251,20 @@ jobs: fail-fast: false matrix: variant: - - name: "js" - base_image_suffix: "-js" - - name: "essentials" - base_image_suffix: "-essentials" - - name: "python" - base_image_suffix: "-python" - - name: "go" - base_image_suffix: "-go" - - name: "rust" - base_image_suffix: "-rust" - - name: "java" - base_image_suffix: "-java" - - name: "kotlin" - base_image_suffix: "-kotlin" - - name: "ruby" - base_image_suffix: "-ruby" - - name: "php" - base_image_suffix: "-php" - - name: "perl" - base_image_suffix: "-perl" - - name: "swift" - base_image_suffix: "-swift" - - name: "lean" - base_image_suffix: "-lean" - - name: "rocq" - base_image_suffix: "-rocq" - - name: "full" - base_image_suffix: "" + - "js" + - "essentials" + - "python" + - "go" + - "rust" + - "java" + - "kotlin" + - "ruby" + - "php" + - "perl" + - "swift" + - "lean" + - "rocq" + - "full" if: | always() && needs.detect-changes.result == 'success' && @@ -2322,14 +2296,16 @@ jobs: - name: Compute image names id: names run: | - BASE_SUFFIX="${{ matrix.variant.base_image_suffix }}" - NAME="${{ matrix.variant.name }}" - echo "base_image=${{ env.DOCKERHUB_IMAGE_NAME }}${BASE_SUFFIX}:${{ steps.version.outputs.version }}-arm64" >> $GITHUB_OUTPUT + NAME="${{ matrix.variant }}" if [ "$NAME" = "full" ]; then - echo "dind_suffix=-dind" >> $GITHUB_OUTPUT + BASE_SUFFIX="" + DIND_SUFFIX="-dind" else - echo "dind_suffix=${BASE_SUFFIX}-dind" >> $GITHUB_OUTPUT + BASE_SUFFIX="-$NAME" + DIND_SUFFIX="-${NAME}-dind" fi + echo "base_image=${{ env.DOCKERHUB_IMAGE_NAME }}${BASE_SUFFIX}:${{ steps.version.outputs.version }}-arm64" >> $GITHUB_OUTPUT + echo "dind_suffix=${DIND_SUFFIX}" >> $GITHUB_OUTPUT - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -2348,7 +2324,7 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push ${{ matrix.variant.name }} dind-box (arm64) + - name: Build and push ${{ matrix.variant }} dind-box (arm64) id: build-push continue-on-error: true uses: docker/build-push-action@v5 @@ -2365,10 +2341,10 @@ jobs: ${{ env.DOCKERHUB_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:latest-arm64 ${{ env.DOCKERHUB_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:${{ steps.version.outputs.version }}-arm64 provenance: false - cache-from: type=gha,scope=dind-${{ matrix.variant.name }}-arm64 - cache-to: type=gha,scope=dind-${{ matrix.variant.name }}-arm64,mode=max + cache-from: type=gha,scope=dind-${{ matrix.variant }}-arm64 + cache-to: type=gha,scope=dind-${{ matrix.variant }}-arm64,mode=max - - name: Retry push ${{ matrix.variant.name }} dind-box (arm64) on failure + - name: Retry push ${{ matrix.variant }} dind-box (arm64) on failure if: steps.build-push.outcome == 'failure' run: | echo "First push attempt failed, retrying with backoff..." @@ -2383,7 +2359,7 @@ jobs: --tag ${{ env.DOCKERHUB_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:latest-arm64 \ --tag ${{ env.DOCKERHUB_IMAGE_NAME }}${{ steps.names.outputs.dind_suffix }}:${{ steps.version.outputs.version }}-arm64 \ --provenance=false \ - --cache-from type=gha,scope=dind-${{ matrix.variant.name }}-arm64 \ + --cache-from type=gha,scope=dind-${{ matrix.variant }}-arm64 \ --push \ .; then echo "==> Push succeeded on retry attempt $attempt" @@ -2406,34 +2382,20 @@ jobs: fail-fast: false matrix: variant: - - name: "js" - base_image_suffix: "-js" - - name: "essentials" - base_image_suffix: "-essentials" - - name: "python" - base_image_suffix: "-python" - - name: "go" - base_image_suffix: "-go" - - name: "rust" - base_image_suffix: "-rust" - - name: "java" - base_image_suffix: "-java" - - name: "kotlin" - base_image_suffix: "-kotlin" - - name: "ruby" - base_image_suffix: "-ruby" - - name: "php" - base_image_suffix: "-php" - - name: "perl" - base_image_suffix: "-perl" - - name: "swift" - base_image_suffix: "-swift" - - name: "lean" - base_image_suffix: "-lean" - - name: "rocq" - base_image_suffix: "-rocq" - - name: "full" - base_image_suffix: "" + - "js" + - "essentials" + - "python" + - "go" + - "rust" + - "java" + - "kotlin" + - "ruby" + - "php" + - "perl" + - "swift" + - "lean" + - "rocq" + - "full" if: | always() && needs.detect-changes.result == 'success' && @@ -2473,15 +2435,14 @@ jobs: - name: Compute dind suffix id: names run: | - NAME="${{ matrix.variant.name }}" - BASE_SUFFIX="${{ matrix.variant.base_image_suffix }}" + NAME="${{ matrix.variant }}" if [ "$NAME" = "full" ]; then echo "dind_suffix=-dind" >> $GITHUB_OUTPUT else - echo "dind_suffix=${BASE_SUFFIX}-dind" >> $GITHUB_OUTPUT + echo "dind_suffix=-${NAME}-dind" >> $GITHUB_OUTPUT fi - - name: Create and push ${{ matrix.variant.name }} dind multi-arch manifests + - name: Create and push ${{ matrix.variant }} dind multi-arch manifests run: | VERSION="${{ steps.version.outputs.version }}" DSUF="${{ steps.names.outputs.dind_suffix }}" From 5b8950180e7ce025e2476c1760327f3c529dcfdf Mon Sep 17 00:00:00 2001 From: konard Date: Tue, 28 Apr 2026 14:22:11 +0000 Subject: [PATCH 6/8] fix(workflow): inline-style matrix for dind variants (mirror language matrix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Push to feature branch keeps producing 0-second 'failure' runs that record no jobs, no billable time, and report the workflow file path as the run name. Local YAML and actionlint both pass. The pre-existing language matrix uses a flow-style inline list 'language: [a, b, c]' on one line — switch the dind matrix to the same form so it matches the proven pattern. --- .github/workflows/release.yml | 48 +++-------------------------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f6034e3..55279bb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2116,21 +2116,7 @@ jobs: matrix: # variant -> base box flavour. Special value "full" maps to konard/box. # Everything else maps to konard/box-. - variant: - - "js" - - "essentials" - - "python" - - "go" - - "rust" - - "java" - - "kotlin" - - "ruby" - - "php" - - "perl" - - "swift" - - "lean" - - "rocq" - - "full" + variant: [js, essentials, python, go, rust, java, kotlin, ruby, php, perl, swift, lean, rocq, full] if: | always() && needs.detect-changes.result == 'success' && @@ -2250,21 +2236,7 @@ jobs: strategy: fail-fast: false matrix: - variant: - - "js" - - "essentials" - - "python" - - "go" - - "rust" - - "java" - - "kotlin" - - "ruby" - - "php" - - "perl" - - "swift" - - "lean" - - "rocq" - - "full" + variant: [js, essentials, python, go, rust, java, kotlin, ruby, php, perl, swift, lean, rocq, full] if: | always() && needs.detect-changes.result == 'success' && @@ -2381,21 +2353,7 @@ jobs: strategy: fail-fast: false matrix: - variant: - - "js" - - "essentials" - - "python" - - "go" - - "rust" - - "java" - - "kotlin" - - "ruby" - - "php" - - "perl" - - "swift" - - "lean" - - "rocq" - - "full" + variant: [js, essentials, python, go, rust, java, kotlin, ruby, php, perl, swift, lean, rocq, full] if: | always() && needs.detect-changes.result == 'success' && From 351d9e8c40eaa964761602ae878966fa6f29a80a Mon Sep 17 00:00:00 2001 From: konard Date: Tue, 28 Apr 2026 14:24:23 +0000 Subject: [PATCH 7/8] fix(workflow): remove em-dashes from new dind-box sections Just in case GitHub's stricter parser dislikes em-dashes inside new run/comment blocks. Existing em-dashes in the architecture diagram heredoc are left alone since they were already passing on prior runs. --- .github/workflows/release.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 55279bb..aff83a5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -553,7 +553,7 @@ jobs: - name: Test dind-box variant run: | set -e - echo "=== Testing dind-box (CLI presence only — no privileged mode in PR CI) ===" + echo "=== Testing dind-box (CLI presence only - no privileged mode in PR CI) ===" # Verify Docker CLI, dockerd binary, buildx and compose plugins are present. # We do NOT start dockerd here because GitHub-hosted runners disallow --privileged. docker run --rm --entrypoint=/bin/bash box-dind-test -c 'docker --version' @@ -2104,7 +2104,7 @@ jobs: echo "Docker Hub multi-arch manifest created and pushed successfully for latest and ${VERSION}" - # === BUILD DIND-BOX VARIANTS (amd64) — issue #80 === + # === BUILD DIND-BOX VARIANTS (amd64) - issue #80 === # Layers Docker Engine on top of every base box image so each variant has a # "-dind" sibling. Runs after the source variant's manifest is published. build-dind-amd64: @@ -2228,7 +2228,7 @@ jobs: echo "==> All retry attempts failed" exit 1 - # === BUILD DIND-BOX VARIANTS (arm64) — issue #80 === + # === BUILD DIND-BOX VARIANTS (arm64) - issue #80 === build-dind-arm64: runs-on: ubuntu-24.04-arm timeout-minutes: 45 @@ -2346,7 +2346,7 @@ jobs: echo "==> All retry attempts failed" exit 1 - # === CREATE DIND-BOX MULTI-ARCH MANIFESTS — issue #80 === + # === CREATE DIND-BOX MULTI-ARCH MANIFESTS - issue #80 === dind-manifest: runs-on: ubuntu-24.04 needs: [detect-changes, build-dind-amd64, build-dind-arm64] @@ -2499,7 +2499,7 @@ jobs: ### Docker Hub - dind-box (Docker-in-Docker variants, issue #80) - Each variant runs an inner Docker daemon. Run with \`docker run --privileged\` (default) or \`docker run --runtime=sysbox-runc\` (recommended for shared hosts). \`docker ps -a\` inside the container only lists containers created by that container — see [docs/case-studies/issue-80](https://github.com/${REPO}/blob/v${VERSION}/docs/case-studies/issue-80/CASE-STUDY.md). + Each variant runs an inner Docker daemon. Run with \`docker run --privileged\` (default) or \`docker run --runtime=sysbox-runc\` (recommended for shared hosts). \`docker ps -a\` inside the container only lists containers created by that container - see [docs/case-studies/issue-80](https://github.com/${REPO}/blob/v${VERSION}/docs/case-studies/issue-80/CASE-STUDY.md). | Image | Multi-arch | AMD64 | ARM64 | |-------|------------|-------|-------| From a6c99bb95c068c01724a84fa7150d87a066392cb Mon Sep 17 00:00:00 2001 From: konard Date: Tue, 28 Apr 2026 14:28:39 +0000 Subject: [PATCH 8/8] fix(workflow): split release-notes step to stay under 21000-char limit GitHub Actions rejected the workflow with 'Exceeded max expression length 21000' on line 2466 (the create-release step's run block). Adding the dind-box tables to the existing heredoc pushed the step's expression over the per-step limit. Move the dind-box tables to a new 'Append dind-box tables to release notes' step that uses a bash for-loop to generate rows programmatically, keeping the GitHub-side expression interpolation small. Move 'gh release create/edit' to its own step so each step stays well under the limit. --- .github/workflows/release.yml | 82 +++++++++++++++++------------------ 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aff83a5..fb56704 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2497,27 +2497,6 @@ jobs: | Lean | [\`${DOCKERHUB_IMAGE}-lean:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-lean/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-lean/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-lean/tags?name=${VERSION}-arm64) | | Rocq | [\`${DOCKERHUB_IMAGE}-rocq:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rocq/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rocq/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rocq/tags?name=${VERSION}-arm64) | - ### Docker Hub - dind-box (Docker-in-Docker variants, issue #80) - - Each variant runs an inner Docker daemon. Run with \`docker run --privileged\` (default) or \`docker run --runtime=sysbox-runc\` (recommended for shared hosts). \`docker ps -a\` inside the container only lists containers created by that container - see [docs/case-studies/issue-80](https://github.com/${REPO}/blob/v${VERSION}/docs/case-studies/issue-80/CASE-STUDY.md). - - | Image | Multi-arch | AMD64 | ARM64 | - |-------|------------|-------|-------| - | Full + dind | [\`${DOCKERHUB_IMAGE}-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-dind/tags?name=${VERSION}-arm64) | - | Essentials + dind | [\`${DOCKERHUB_IMAGE}-essentials-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-essentials-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-essentials-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-essentials-dind/tags?name=${VERSION}-arm64) | - | JS + dind | [\`${DOCKERHUB_IMAGE}-js-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-js-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-js-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-js-dind/tags?name=${VERSION}-arm64) | - | Python + dind | [\`${DOCKERHUB_IMAGE}-python-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-python-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-python-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-python-dind/tags?name=${VERSION}-arm64) | - | Go + dind | [\`${DOCKERHUB_IMAGE}-go-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-go-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-go-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-go-dind/tags?name=${VERSION}-arm64) | - | Rust + dind | [\`${DOCKERHUB_IMAGE}-rust-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rust-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rust-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rust-dind/tags?name=${VERSION}-arm64) | - | Java + dind | [\`${DOCKERHUB_IMAGE}-java-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-java-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-java-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-java-dind/tags?name=${VERSION}-arm64) | - | Kotlin + dind | [\`${DOCKERHUB_IMAGE}-kotlin-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-kotlin-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-kotlin-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-kotlin-dind/tags?name=${VERSION}-arm64) | - | Ruby + dind | [\`${DOCKERHUB_IMAGE}-ruby-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-ruby-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-ruby-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-ruby-dind/tags?name=${VERSION}-arm64) | - | PHP + dind | [\`${DOCKERHUB_IMAGE}-php-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-php-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-php-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-php-dind/tags?name=${VERSION}-arm64) | - | Perl + dind | [\`${DOCKERHUB_IMAGE}-perl-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-perl-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-perl-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-perl-dind/tags?name=${VERSION}-arm64) | - | Swift + dind | [\`${DOCKERHUB_IMAGE}-swift-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-swift-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-swift-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-swift-dind/tags?name=${VERSION}-arm64) | - | Lean + dind | [\`${DOCKERHUB_IMAGE}-lean-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-lean-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-lean-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-lean-dind/tags?name=${VERSION}-arm64) | - | Rocq + dind | [\`${DOCKERHUB_IMAGE}-rocq-dind:${VERSION}\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rocq-dind/tags?name=${VERSION}) | [\`${VERSION}-amd64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rocq-dind/tags?name=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://hub.docker.com/r/${DOCKERHUB_IMAGE}-rocq-dind/tags?name=${VERSION}-arm64) | - ### GitHub Container Registry - Combo Boxes | Image | Multi-arch | AMD64 | ARM64 | @@ -2542,25 +2521,6 @@ jobs: | Lean | [\`${GHCR_IMAGE}-lean:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-lean?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-lean?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-lean?tag=${VERSION}-arm64) | | Rocq | [\`${GHCR_IMAGE}-rocq:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-rocq?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-rocq?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-rocq?tag=${VERSION}-arm64) | - ### GitHub Container Registry - dind-box (Docker-in-Docker variants, issue #80) - - | Image | Multi-arch | AMD64 | ARM64 | - |-------|------------|-------|-------| - | Full + dind | [\`${GHCR_IMAGE}-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-dind?tag=${VERSION}-arm64) | - | Essentials + dind | [\`${GHCR_IMAGE}-essentials-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-essentials-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-essentials-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-essentials-dind?tag=${VERSION}-arm64) | - | JS + dind | [\`${GHCR_IMAGE}-js-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-js-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-js-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-js-dind?tag=${VERSION}-arm64) | - | Python + dind | [\`${GHCR_IMAGE}-python-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-python-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-python-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-python-dind?tag=${VERSION}-arm64) | - | Go + dind | [\`${GHCR_IMAGE}-go-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-go-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-go-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-go-dind?tag=${VERSION}-arm64) | - | Rust + dind | [\`${GHCR_IMAGE}-rust-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-rust-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-rust-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-rust-dind?tag=${VERSION}-arm64) | - | Java + dind | [\`${GHCR_IMAGE}-java-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-java-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-java-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-java-dind?tag=${VERSION}-arm64) | - | Kotlin + dind | [\`${GHCR_IMAGE}-kotlin-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-kotlin-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-kotlin-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-kotlin-dind?tag=${VERSION}-arm64) | - | Ruby + dind | [\`${GHCR_IMAGE}-ruby-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-ruby-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-ruby-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-ruby-dind?tag=${VERSION}-arm64) | - | PHP + dind | [\`${GHCR_IMAGE}-php-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-php-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-php-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-php-dind?tag=${VERSION}-arm64) | - | Perl + dind | [\`${GHCR_IMAGE}-perl-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-perl-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-perl-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-perl-dind?tag=${VERSION}-arm64) | - | Swift + dind | [\`${GHCR_IMAGE}-swift-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-swift-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-swift-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-swift-dind?tag=${VERSION}-arm64) | - | Lean + dind | [\`${GHCR_IMAGE}-lean-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-lean-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-lean-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-lean-dind?tag=${VERSION}-arm64) | - | Rocq + dind | [\`${GHCR_IMAGE}-rocq-dind:${VERSION}\`](https://github.com/${REPO}/pkgs/container/box-rocq-dind?tag=${VERSION}) | [\`${VERSION}-amd64\`](https://github.com/${REPO}/pkgs/container/box-rocq-dind?tag=${VERSION}-amd64) | [\`${VERSION}-arm64\`](https://github.com/${REPO}/pkgs/container/box-rocq-dind?tag=${VERSION}-arm64) | - ## Architecture \`\`\` @@ -2601,7 +2561,47 @@ jobs: Released on ${DATE} ENDOFNOTES - # Check if release already exists + # Append dind-box tables in a separate step to stay under GitHub's + # 21000-char per-step expression limit (issue #80). + - name: Append dind-box tables to release notes + env: + GHCR_REG: ${{ env.GHCR_REGISTRY }} + GHCR_NAME: ${{ env.GHCR_IMAGE_NAME }} + DOCKERHUB_IMAGE: ${{ env.DOCKERHUB_IMAGE_NAME }} + run: | + VERSION="${{ steps.version.outputs.version }}" + REPO="${{ github.repository }}" + + { + printf '\n### Docker Hub - dind-box (Docker-in-Docker variants, issue #80)\n\n' + printf 'Each variant runs an inner Docker daemon. Run with `docker run --privileged` (default) or `docker run --runtime=sysbox-runc` (recommended for shared hosts). `docker ps -a` inside the container only lists containers created by that container - see [docs/case-studies/issue-80](https://github.com/%s/blob/v%s/docs/case-studies/issue-80/CASE-STUDY.md).\n\n' "$REPO" "$VERSION" + printf '| Image | Multi-arch | AMD64 | ARM64 |\n' + printf '|-------|------------|-------|-------|\n' + for variant in "Full|" "Essentials|-essentials" "JS|-js" "Python|-python" "Go|-go" "Rust|-rust" "Java|-java" "Kotlin|-kotlin" "Ruby|-ruby" "PHP|-php" "Perl|-perl" "Swift|-swift" "Lean|-lean" "Rocq|-rocq"; do + label="${variant%%|*}" + suffix="${variant#*|}" + dh="${DOCKERHUB_IMAGE}${suffix}-dind" + printf '| %s + dind | [`%s:%s`](https://hub.docker.com/r/%s/tags?name=%s) | [`%s-amd64`](https://hub.docker.com/r/%s/tags?name=%s-amd64) | [`%s-arm64`](https://hub.docker.com/r/%s/tags?name=%s-arm64) |\n' \ + "$label" "$dh" "$VERSION" "$dh" "$VERSION" "$VERSION" "$dh" "$VERSION" "$VERSION" "$dh" "$VERSION" + done + printf '\n### GitHub Container Registry - dind-box (Docker-in-Docker variants, issue #80)\n\n' + printf '| Image | Multi-arch | AMD64 | ARM64 |\n' + printf '|-------|------------|-------|-------|\n' + for variant in "Full|box" "Essentials|box-essentials" "JS|box-js" "Python|box-python" "Go|box-go" "Rust|box-rust" "Java|box-java" "Kotlin|box-kotlin" "Ruby|box-ruby" "PHP|box-php" "Perl|box-perl" "Swift|box-swift" "Lean|box-lean" "Rocq|box-rocq"; do + label="${variant%%|*}" + pkg="${variant#*|}-dind" + gh_image="${GHCR_REG}/${GHCR_NAME%/*}/${pkg}" + printf '| %s + dind | [`%s:%s`](https://github.com/%s/pkgs/container/%s?tag=%s) | [`%s-amd64`](https://github.com/%s/pkgs/container/%s?tag=%s-amd64) | [`%s-arm64`](https://github.com/%s/pkgs/container/%s?tag=%s-arm64) |\n' \ + "$label" "$gh_image" "$VERSION" "$REPO" "$pkg" "$VERSION" "$VERSION" "$REPO" "$pkg" "$VERSION" "$VERSION" "$REPO" "$pkg" "$VERSION" + done + } >> /tmp/release-notes.md + + - name: Publish GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION="${{ steps.version.outputs.version }}" + if gh release view "v${VERSION}" &>/dev/null; then echo "Release v${VERSION} already exists, updating..." gh release edit "v${VERSION}" --title "v${VERSION}" --notes-file /tmp/release-notes.md