Skip to content

Commit 93b011a

Browse files
dvdksnclaude
andcommitted
sandboxes: add release notes page sourced from GitHub releases
Add content/manuals/ai/sandboxes/release-notes.md as a single page listing the latest stable releases of Docker Sandboxes, with a link to the GitHub release history for older versions. The block between BEGIN/END GENERATED RELEASES markers is populated by hack/sbx-release-notes.py — a single-file uv-run script (PEP 723 inline deps, Jinja2 only) that hits the GitHub Releases API for docker/sbx-releases, filters to strict-semver stable tags with non-empty bodies, and emits H2-per-release sections for the latest N minor releases (default 3) and their patches. Re-runs are idempotent and only touch content between the markers. The script also invokes `prettier --write` so the output stays lint-clean. Extend the existing release-notes Vale skip rule with content/manuals/**/release-notes.md so the new file under ai/sandboxes/ picks up the same lint exclusions as engine/desktop/build release notes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
1 parent 7d9af2a commit 93b011a

3 files changed

Lines changed: 329 additions & 1 deletion

File tree

.vale.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Vale.Spelling = NO
1010
Vale.Terms = NO
1111

1212
# Skip release notes and old desktop changelog content entirely
13-
[{content/manuals/*/release-notes.md,content/manuals/*/release-notes/**,content/manuals/desktop/previous-versions/**}]
13+
[{content/manuals/*/release-notes.md,content/manuals/*/release-notes/**,content/manuals/**/release-notes.md,content/manuals/desktop/previous-versions/**}]
1414
Docker.Avoid = NO
1515
Docker.Capitalization = NO
1616
Docker.Exclamation = NO
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
---
2+
title: Docker Sandboxes release notes
3+
linkTitle: Release notes
4+
description: New features, bug fixes, and changes in Docker Sandboxes
5+
keywords: docker sandboxes, sbx, release notes, changelog
6+
toc_min: 1
7+
toc_max: 2
8+
tags:
9+
- Release notes
10+
---
11+
12+
This page lists changes in recent stable releases of Docker Sandboxes. For
13+
the full release history, including pre-releases and downloads, see the
14+
[Docker Sandboxes releases on GitHub](https://github.com/docker/sbx-releases/releases).
15+
16+
<!-- BEGIN GENERATED RELEASES -->
17+
18+
## 0.30.0
19+
20+
{{< release-date date="2026-05-19" >}}
21+
22+
[GitHub release](https://github.com/docker/sbx-releases/releases/tag/v0.30.0)
23+
24+
### Highlights
25+
26+
The CLI gets **non-interactive Docker Hub login** for scripted workflows, and sandboxes now have **a configurable grace period before auto-stopping** when the last session exits. Plus a wave of fixes covering Linux packaging, macOS worktree compatibility, Windows installer paths, network isolation, and recoverable sandbox state when host directories vanish.
27+
28+
### What's New
29+
30+
#### Governance & Policy
31+
32+
- Allow `sbx policy` setup before login
33+
34+
#### Kits & Agents
35+
36+
- Re-run `commands.startup` on every container start so init hooks are idempotent across restarts
37+
- Per-kit memory files for progressive disclosure
38+
- Enumerate installed kits in the AI memory file's Kits section
39+
40+
#### CLI & Auth
41+
42+
- Add non-interactive Docker Hub login for scripted workflows
43+
- Migrate `/reset` to `/daemon/reset`; state-dir wipe is now daemon-side
44+
- Print "Git repository detected" once when using `--branch`
45+
- Skip implicit run options when the user provides explicit args
46+
47+
#### Networking & Sandboxd
48+
49+
- Bind both loopback stacks by default when publishing ports
50+
- Allow raw TCP to `host.docker.internal` when localhost is allowed in policy
51+
- Add grace period before auto-stopping a sandbox when the last session exits
52+
53+
#### Bug Fixes
54+
55+
- Build sailor's `ffi` crate instead of `ffi-krun` for packaged Linux release artifacts
56+
- Keep sandboxes recoverable when workspace or worktree is deleted on the host
57+
- Add macOS `/private` path compatibility for worktrees
58+
- Probe canonical socket path for `sun_path` budget — fixes `krun_start_enter failed` on macOS with long usernames
59+
- Namespace gVisor socket dir and auth/secret stores by `--app-name` so concurrent daemons don't collide
60+
- Sanitize runtime ID when looking up gVisor network
61+
- Check database version before starting the daemon; surface an instructive error instead of crashing
62+
- Report Docker daemon startup time instead of the pre-start message in DinD
63+
- Harden `BuildFileCredential` to check more than just file existence
64+
- Open a sentinel connection in `cp` and `kit add` to prevent auto-stop race
65+
- Remove redundant `ContainerKill` before `ContainerRemove` in sandboxlib
66+
- Use a safe Windows `start` invocation for `OpenURL` in the TUI
67+
- Rename WiX install directory id to `INSTALLFOLDER`
68+
69+
#### Documentation
70+
71+
- Warn agents about worktree path traps with `--branch`
72+
- Improve consistency and wording in CLI help strings
73+
74+
## 0.29.0
75+
76+
{{< release-date date="2026-05-13" >}}
77+
78+
[GitHub release](https://github.com/docker/sbx-releases/releases/tag/v0.29.0)
79+
80+
### Highlights
81+
82+
This release brings **per-sandbox network policies**, giving callers fine-grained control over which domains each sandbox can reach, including an explicit `deniedDomains` list and allowance for binary TCP protocols like SSH. Sandboxes now carry **daemon-assigned UUIDs**, enabling reliable identification across restarts and telemetry. Several **agent improvements** land in this release: Gemini gets SSO browser relay, Codex auth is more robust, and the OpenAI OAuth flow now auto-opens the browser. A round of **bug fixes** improves daemon robustness on macOS (long-username `sun_path` overflow), gVisor isolation under `--app-name`, and database-version handling.
83+
84+
### What's New
85+
86+
#### Networking & Policy
87+
88+
- Support per-sandbox scoped network policies
89+
- Add `deniedDomains` to network kit policy
90+
- Allow binary TCP protocols (e.g. SSH) through domain allow rules
91+
- Pipe in policykit error handler for better diagnostics
92+
93+
#### Sandboxes
94+
95+
- Add daemon-assigned UUID to sandbox runtimes
96+
97+
#### Agents
98+
99+
- Enable SSO browser relay for Gemini
100+
- Auto-open browser during OpenAI OAuth flow
101+
- Skip auth.json placeholder for Codex when no host credentials
102+
- Expose Claude guidance to Codex sandboxes
103+
104+
#### CLI
105+
106+
- Require confirmation for `sbx rm <name>` to prevent accidental deletion
107+
- Unhide `kit` command in help output
108+
109+
#### Bug Fixes
110+
111+
- Namespace gVisor socket dir by `--app-name` so concurrent daemons don't share state
112+
- Probe canonical socket path for `sun_path` budget — fixes `krun_start_enter failed` for macOS users with long usernames
113+
- Check database version before starting the daemon and surface an instructive error instead of crashing
114+
- Route gVisor sockets to a persistent, sandboxd-owned location
115+
- Delete stranded tracker after failed auto-stop with no active sessions
116+
- Clean up DinD volume even when container inspect fails
117+
- Apply `SANDBOXES_STORAGE_ROOT` override to storage config
118+
- Report running binary (not first `sbx` on PATH) in `diagnose`
119+
- Explain how to configure OpenAI credentials in no-creds warning
120+
- Allow MCR layer-blob CDN in default-code-and-containers policy
121+
- Improve empty state of `sbx ls` with actionable guidance
122+
123+
## 0.28.2
124+
125+
{{< release-date date="2026-04-29" >}}
126+
127+
[GitHub release](https://github.com/docker/sbx-releases/releases/tag/v0.28.2)
128+
129+
### What's New
130+
131+
#### CLI
132+
133+
- Auto-open browser during login flow
134+
135+
#### Templates
136+
137+
- Install `ssh-add` and SSH client tools in the `main` template
138+
139+
#### Bug Fixes
140+
141+
- Prefer Codex OAuth over discovered API-key credentials
142+
- Propagate host TTY size when running `sbx exec -it`
143+
- Reveal trailing characters in masked secrets
144+
145+
## 0.28.1
146+
147+
{{< release-date date="2026-04-28" >}}
148+
149+
[GitHub release](https://github.com/docker/sbx-releases/releases/tag/v0.28.1)
150+
151+
### Highlights
152+
153+
A small release that wires **custom agent kits** through the CLI — discoverable in `--help` and invocable via `--kit` — and brings
154+
**in-process sandbox run/exec** with launch-mode and settings dialogs to the TUI. Two bug fixes round it out: private Docker Hub image pulls work again via `--template`, and the secrets-masking path is tightened.
155+
156+
### What's New
157+
158+
#### CLI
159+
160+
- Make custom agent kits invocable and surface `--kit` in help
161+
- TUI: in-process sandbox run/exec with launch mode dialog, settings dialog + misc fixes
162+
163+
#### Bug Fixes
164+
165+
- Enable private Docker Hub image pulls via `--template`
166+
- Tighten secrets masking and emphasize `set-custom` warning
167+
168+
<!-- END GENERATED RELEASES -->
169+
170+
## Earlier releases
171+
172+
For older versions, see the
173+
[Docker Sandboxes releases on GitHub](https://github.com/docker/sbx-releases/releases).

hack/sbx-release-notes.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
#!/usr/bin/env -S uv run --quiet --script
2+
# /// script
3+
# requires-python = ">=3.11"
4+
# dependencies = ["jinja2"]
5+
# ///
6+
"""
7+
Fetch recent stable releases from docker/sbx-releases and splice them into
8+
content/manuals/ai/sandboxes/release-notes.md between the BEGIN/END markers.
9+
10+
Usage (from repo root):
11+
12+
./hack/sbx-release-notes.py
13+
GITHUB_TOKEN=$(gh auth token) ./hack/sbx-release-notes.py
14+
./hack/sbx-release-notes.py --minor-releases 3
15+
"""
16+
17+
from __future__ import annotations
18+
19+
import argparse
20+
import json
21+
import os
22+
import re
23+
import shutil
24+
import subprocess
25+
import sys
26+
import urllib.request
27+
from collections import defaultdict
28+
from pathlib import Path
29+
30+
from jinja2 import Template
31+
32+
DEFAULT_REPO = "docker/sbx-releases"
33+
DEFAULT_FILE = Path("content/manuals/ai/sandboxes/release-notes.md")
34+
DEFAULT_MINOR_RELEASES = 3
35+
36+
BEGIN = "<!-- BEGIN GENERATED RELEASES -->"
37+
END = "<!-- END GENERATED RELEASES -->"
38+
39+
SEMVER = re.compile(r"^v(\d+)\.(\d+)\.(\d+)$")
40+
41+
TEMPLATE = Template(
42+
"""\
43+
{% for r in releases -%}
44+
## {{ r.version }}
45+
46+
{{ '{{<' }} release-date date="{{ r.date }}" {{ '>}}' }}
47+
48+
[GitHub release]({{ r.url }})
49+
50+
{{ r.body }}
51+
52+
{% endfor -%}
53+
"""
54+
)
55+
56+
57+
def fetch(repo: str) -> list[dict]:
58+
url = f"https://api.github.com/repos/{repo}/releases?per_page=100"
59+
req = urllib.request.Request(
60+
url,
61+
headers={
62+
"Accept": "application/vnd.github+json",
63+
"X-GitHub-Api-Version": "2022-11-28",
64+
},
65+
)
66+
if token := os.environ.get("GITHUB_TOKEN"):
67+
req.add_header("Authorization", f"Bearer {token}")
68+
with urllib.request.urlopen(req) as resp:
69+
return json.load(resp)
70+
71+
72+
def parse_stable(raw: list[dict]) -> list[dict]:
73+
out = []
74+
for r in raw:
75+
if r.get("prerelease") or r.get("draft"):
76+
continue
77+
m = SEMVER.match(r["tag_name"])
78+
if not m:
79+
continue
80+
body = (r.get("body") or "").strip()
81+
if not body:
82+
continue
83+
major, minor, patch = (int(x) for x in m.groups())
84+
out.append(
85+
{
86+
"major": major,
87+
"minor": minor,
88+
"patch": patch,
89+
"version": f"{major}.{minor}.{patch}",
90+
"date": r["published_at"][:10],
91+
"url": r["html_url"],
92+
"body": shift_headings(body),
93+
}
94+
)
95+
out.sort(key=lambda r: (r["major"], r["minor"], r["patch"]), reverse=True)
96+
return out
97+
98+
99+
def pick_minor_releases(releases: list[dict], n: int) -> list[dict]:
100+
by_minor: dict[tuple[int, int], list[dict]] = defaultdict(list)
101+
for r in releases:
102+
by_minor[(r["major"], r["minor"])].append(r)
103+
latest_keys = sorted(by_minor.keys(), reverse=True)[:n]
104+
keep = set(latest_keys)
105+
return [r for r in releases if (r["major"], r["minor"]) in keep]
106+
107+
108+
def shift_headings(body: str) -> str:
109+
"""Demote ATX headings by one level so body H2s become H3 under the
110+
version's H2. Skips fenced code blocks."""
111+
lines = body.splitlines()
112+
in_fence = False
113+
for i, line in enumerate(lines):
114+
stripped = line.lstrip(" \t")
115+
if stripped.startswith(("```", "~~~")):
116+
in_fence = not in_fence
117+
continue
118+
if in_fence:
119+
continue
120+
if stripped.startswith("#"):
121+
indent = line[: len(line) - len(stripped)]
122+
lines[i] = f"{indent}#{stripped}"
123+
return "\n".join(lines)
124+
125+
126+
def splice(path: Path, generated: str) -> None:
127+
src = path.read_text()
128+
try:
129+
before, rest = src.split(BEGIN, 1)
130+
_, after = rest.split(END, 1)
131+
except ValueError:
132+
sys.exit(f"markers {BEGIN!r} / {END!r} not found in {path}")
133+
path.write_text(f"{before}{BEGIN}\n\n{generated}{END}{after}")
134+
135+
136+
def main() -> None:
137+
p = argparse.ArgumentParser(description=__doc__)
138+
p.add_argument("--repo", default=DEFAULT_REPO)
139+
p.add_argument("--file", type=Path, default=DEFAULT_FILE)
140+
p.add_argument("--minor-releases", type=int, default=DEFAULT_MINOR_RELEASES)
141+
args = p.parse_args()
142+
143+
releases = pick_minor_releases(parse_stable(fetch(args.repo)), args.minor_releases)
144+
if not releases:
145+
sys.exit("no stable releases found")
146+
147+
generated = TEMPLATE.render(releases=releases)
148+
splice(args.file, generated)
149+
if shutil.which("npx"):
150+
subprocess.run(["npx", "--no-install", "prettier", "--write", str(args.file)], check=False)
151+
print(f"Wrote {len(releases)} releases (latest {args.minor_releases} minor releases) to {args.file}")
152+
153+
154+
if __name__ == "__main__":
155+
main()

0 commit comments

Comments
 (0)