Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vale.ini
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Vale.Spelling = NO
Vale.Terms = NO

# Skip release notes and old desktop changelog content entirely
[{content/manuals/*/release-notes.md,content/manuals/*/release-notes/**,content/manuals/desktop/previous-versions/**}]
[{content/manuals/*/release-notes.md,content/manuals/*/release-notes/**,content/manuals/**/release-notes.md,content/manuals/desktop/previous-versions/**}]
Docker.Avoid = NO
Docker.Capitalization = NO
Docker.Exclamation = NO
Expand Down
173 changes: 173 additions & 0 deletions content/manuals/ai/sandboxes/release-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
---
title: Docker Sandboxes release notes
linkTitle: Release notes
description: New features, bug fixes, and changes in Docker Sandboxes
keywords: docker sandboxes, sbx, release notes, changelog
toc_min: 1
toc_max: 2
tags:
- Release notes
---

This page lists changes in recent stable releases of Docker Sandboxes. For
the full release history, including pre-releases and downloads, see the
[Docker Sandboxes releases on GitHub](https://github.com/docker/sbx-releases/releases).

<!-- BEGIN GENERATED RELEASES -->

## 0.30.0

{{< release-date date="2026-05-19" >}}

[GitHub release](https://github.com/docker/sbx-releases/releases/tag/v0.30.0)

### Highlights

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.

### What's New

#### Governance & Policy

- Allow `sbx policy` setup before login

#### Kits & Agents

- Re-run `commands.startup` on every container start so init hooks are idempotent across restarts
- Per-kit memory files for progressive disclosure
- Enumerate installed kits in the AI memory file's Kits section

#### CLI & Auth

- Add non-interactive Docker Hub login for scripted workflows
- Migrate `/reset` to `/daemon/reset`; state-dir wipe is now daemon-side
- Print "Git repository detected" once when using `--branch`
- Skip implicit run options when the user provides explicit args

#### Networking & Sandboxd

- Bind both loopback stacks by default when publishing ports
- Allow raw TCP to `host.docker.internal` when localhost is allowed in policy
- Add grace period before auto-stopping a sandbox when the last session exits

#### Bug Fixes

- Build sailor's `ffi` crate instead of `ffi-krun` for packaged Linux release artifacts
- Keep sandboxes recoverable when workspace or worktree is deleted on the host
- Add macOS `/private` path compatibility for worktrees
- Probe canonical socket path for `sun_path` budget — fixes `krun_start_enter failed` on macOS with long usernames
- Namespace gVisor socket dir and auth/secret stores by `--app-name` so concurrent daemons don't collide
- Sanitize runtime ID when looking up gVisor network
- Check database version before starting the daemon; surface an instructive error instead of crashing
- Report Docker daemon startup time instead of the pre-start message in DinD
- Harden `BuildFileCredential` to check more than just file existence
- Open a sentinel connection in `cp` and `kit add` to prevent auto-stop race
- Remove redundant `ContainerKill` before `ContainerRemove` in sandboxlib
- Use a safe Windows `start` invocation for `OpenURL` in the TUI
- Rename WiX install directory id to `INSTALLFOLDER`

#### Documentation

- Warn agents about worktree path traps with `--branch`
- Improve consistency and wording in CLI help strings

## 0.29.0

{{< release-date date="2026-05-13" >}}

[GitHub release](https://github.com/docker/sbx-releases/releases/tag/v0.29.0)

### Highlights

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.
Comment thread
dvdksn marked this conversation as resolved.

### What's New

#### Networking & Policy

- Support per-sandbox scoped network policies
- Add `deniedDomains` to network kit policy
- Allow binary TCP protocols (e.g. SSH) through domain allow rules
- Pipe in policykit error handler for better diagnostics

#### Sandboxes

- Add daemon-assigned UUID to sandbox runtimes

#### Agents

- Enable SSO browser relay for Gemini
- Auto-open browser during OpenAI OAuth flow
- Skip auth.json placeholder for Codex when no host credentials
- Expose Claude guidance to Codex sandboxes

#### CLI

- Require confirmation for `sbx rm <name>` to prevent accidental deletion
- Unhide `kit` command in help output

#### Bug Fixes

- Namespace gVisor socket dir by `--app-name` so concurrent daemons don't share state
- Probe canonical socket path for `sun_path` budget — fixes `krun_start_enter failed` for macOS users with long usernames
- Check database version before starting the daemon and surface an instructive error instead of crashing
- Route gVisor sockets to a persistent, sandboxd-owned location
- Delete stranded tracker after failed auto-stop with no active sessions
- Clean up DinD volume even when container inspect fails
- Apply `SANDBOXES_STORAGE_ROOT` override to storage config
- Report running binary (not first `sbx` on PATH) in `diagnose`
- Explain how to configure OpenAI credentials in no-creds warning
- Allow MCR layer-blob CDN in default-code-and-containers policy
- Improve empty state of `sbx ls` with actionable guidance

## 0.28.2

{{< release-date date="2026-04-29" >}}

[GitHub release](https://github.com/docker/sbx-releases/releases/tag/v0.28.2)

### What's New

#### CLI

- Auto-open browser during login flow

#### Templates

- Install `ssh-add` and SSH client tools in the `main` template

#### Bug Fixes

- Prefer Codex OAuth over discovered API-key credentials
- Propagate host TTY size when running `sbx exec -it`
- Reveal trailing characters in masked secrets

## 0.28.1

{{< release-date date="2026-04-28" >}}

[GitHub release](https://github.com/docker/sbx-releases/releases/tag/v0.28.1)

### Highlights

A small release that wires **custom agent kits** through the CLI — discoverable in `--help` and invocable via `--kit` — and brings
**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.

### What's New

#### CLI

- Make custom agent kits invocable and surface `--kit` in help
- TUI: in-process sandbox run/exec with launch mode dialog, settings dialog + misc fixes

#### Bug Fixes

- Enable private Docker Hub image pulls via `--template`
- Tighten secrets masking and emphasize `set-custom` warning

<!-- END GENERATED RELEASES -->

## Earlier releases

For older versions, see the
[Docker Sandboxes releases on GitHub](https://github.com/docker/sbx-releases/releases).
155 changes: 155 additions & 0 deletions hack/sbx-release-notes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#!/usr/bin/env -S uv run --quiet --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["jinja2"]
# ///
"""
Fetch recent stable releases from docker/sbx-releases and splice them into
content/manuals/ai/sandboxes/release-notes.md between the BEGIN/END markers.

Usage (from repo root):

./hack/sbx-release-notes.py
GITHUB_TOKEN=$(gh auth token) ./hack/sbx-release-notes.py
./hack/sbx-release-notes.py --minor-releases 3
"""

from __future__ import annotations

import argparse
import json
import os
import re
import shutil
import subprocess
import sys
import urllib.request
from collections import defaultdict
from pathlib import Path

from jinja2 import Template

DEFAULT_REPO = "docker/sbx-releases"
DEFAULT_FILE = Path("content/manuals/ai/sandboxes/release-notes.md")
DEFAULT_MINOR_RELEASES = 3

BEGIN = "<!-- BEGIN GENERATED RELEASES -->"
END = "<!-- END GENERATED RELEASES -->"

SEMVER = re.compile(r"^v(\d+)\.(\d+)\.(\d+)$")

TEMPLATE = Template(
"""\
{% for r in releases -%}
## {{ r.version }}

{{ '{{<' }} release-date date="{{ r.date }}" {{ '>}}' }}

[GitHub release]({{ r.url }})

{{ r.body }}

{% endfor -%}
"""
)


def fetch(repo: str) -> list[dict]:
url = f"https://api.github.com/repos/{repo}/releases?per_page=100"
req = urllib.request.Request(
url,
headers={
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
)
if token := os.environ.get("GITHUB_TOKEN"):
req.add_header("Authorization", f"Bearer {token}")
with urllib.request.urlopen(req) as resp:
return json.load(resp)


def parse_stable(raw: list[dict]) -> list[dict]:
out = []
for r in raw:
if r.get("prerelease") or r.get("draft"):
continue
m = SEMVER.match(r["tag_name"])
if not m:
continue
body = (r.get("body") or "").strip()
if not body:
continue
major, minor, patch = (int(x) for x in m.groups())
out.append(
{
"major": major,
"minor": minor,
"patch": patch,
"version": f"{major}.{minor}.{patch}",
"date": r["published_at"][:10],
"url": r["html_url"],
"body": shift_headings(body),
}
)
out.sort(key=lambda r: (r["major"], r["minor"], r["patch"]), reverse=True)
return out


def pick_minor_releases(releases: list[dict], n: int) -> list[dict]:
by_minor: dict[tuple[int, int], list[dict]] = defaultdict(list)
for r in releases:
by_minor[(r["major"], r["minor"])].append(r)
latest_keys = sorted(by_minor.keys(), reverse=True)[:n]
keep = set(latest_keys)
return [r for r in releases if (r["major"], r["minor"]) in keep]


def shift_headings(body: str) -> str:
"""Demote ATX headings by one level so body H2s become H3 under the
version's H2. Skips fenced code blocks."""
lines = body.splitlines()
in_fence = False
for i, line in enumerate(lines):
stripped = line.lstrip(" \t")
if stripped.startswith(("```", "~~~")):
in_fence = not in_fence
continue
if in_fence:
continue
if stripped.startswith("#"):
indent = line[: len(line) - len(stripped)]
lines[i] = f"{indent}#{stripped}"
return "\n".join(lines)


def splice(path: Path, generated: str) -> None:
src = path.read_text()
try:
before, rest = src.split(BEGIN, 1)
_, after = rest.split(END, 1)
except ValueError:
sys.exit(f"markers {BEGIN!r} / {END!r} not found in {path}")
path.write_text(f"{before}{BEGIN}\n\n{generated}{END}{after}")


def main() -> None:
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--repo", default=DEFAULT_REPO)
p.add_argument("--file", type=Path, default=DEFAULT_FILE)
p.add_argument("--minor-releases", type=int, default=DEFAULT_MINOR_RELEASES)
args = p.parse_args()

releases = pick_minor_releases(parse_stable(fetch(args.repo)), args.minor_releases)
if not releases:
sys.exit("no stable releases found")

generated = TEMPLATE.render(releases=releases)
splice(args.file, generated)
if shutil.which("npx"):
subprocess.run(["npx", "--no-install", "prettier", "--write", str(args.file)], check=False)
print(f"Wrote {len(releases)} releases (latest {args.minor_releases} minor releases) to {args.file}")


if __name__ == "__main__":
main()