diff --git a/_vale/config/vocabularies/Docker/accept.txt b/_vale/config/vocabularies/Docker/accept.txt index ff7fa6a72276..3c39a6c86a83 100644 --- a/_vale/config/vocabularies/Docker/accept.txt +++ b/_vale/config/vocabularies/Docker/accept.txt @@ -238,6 +238,7 @@ sysctls Sysdig systemd teleop +Temurin Testcontainers tmpfs Traefik diff --git a/content/guides/java-coding-agent-sandbox.md b/content/guides/java-coding-agent-sandbox.md new file mode 100644 index 000000000000..51c61b2ef35b --- /dev/null +++ b/content/guides/java-coding-agent-sandbox.md @@ -0,0 +1,471 @@ +--- +title: Run a coding agent safely on a Java project with Docker Sandboxes +linkTitle: Kit out your sandbox for Java +description: Use a Docker Sandbox and a reusable sbx kit to run a coding agent on a Spring Boot project, without giving it unrestricted access to your machine. +keywords: ai, sbx, docker sandboxes, java, spring boot, testcontainers, maven, sdkman, coding agent, kit +weight: 3 +summary: | + Build a reusable sbx kit so a coding agent can build and test a Spring Boot + project inside an isolated microVM, with a full Java toolchain and a minimal, + reviewable network allowlist. +params: + tags: [ai] + featured: true +--- + +Coding agents are most useful when they can build and test your project: +compile the code, run the tests, pull the containers an integration test needs. +On a Java project that means a JDK, Maven, and a Docker daemon, plus network +access to Maven Central and a few image registries. The straightforward way to +give an agent all of that is to run it directly on your machine with your +credentials and your full network, which is also the part worth being careful +about. + +The [Docker Sandboxes](../manuals/ai/sandboxes/_index.md) `sbx` CLI runs an +agent inside an isolated microVM with its own filesystem, its own Docker +daemon, and a network that denies everything by default. That isolation is +useful on its own, but the base sandbox ships only part of a Java toolchain — +it has a JDK, but no Maven — and the default-deny network blocks Maven Central. +You can install what's missing by hand in each sandbox, but that's a +per-sandbox chore that drifts over time. This guide closes the gap with a +[kit](../manuals/ai/sandboxes/customize/kits.md): a small, committed spec that +installs the toolchain and declares exactly which domains the project is +allowed to reach, so every teammate gets the same productive, isolated setup +from one command. + +In this guide, you'll learn how to: + +- Run a Java project inside a Docker Sandbox and see what's missing out of the + box. +- Write a kit that installs SDKMAN, a JDK, and Maven, and puts them on `PATH` + for both `sbx exec` and the agent's own shell. +- Declare the minimal network allowlist a Spring Boot and Maven workflow needs, + and nothing more. +- Run Testcontainers integration tests against the sandbox's built-in Docker + daemon. +- Commit the kit so your whole team gets the same sandbox with one command. + +## Assumptions + +This guide assumes you are comfortable with Maven and Spring Boot, and that you +have used a coding agent before (this guide uses Claude Code). You don't need +prior experience with Docker Sandboxes or kits. Knowing roughly what +[Testcontainers](https://testcontainers.com/) does will help, since the payoff +is a passing integration test, but it isn't required to follow the steps. + +## Prerequisites + +- The `sbx` CLI installed and authenticated. See + [Get started with Docker Sandboxes](../manuals/ai/sandboxes/get-started.md#install-and-sign-in) + to install it, then run `sbx login` once and follow the browser prompt. +- The sample project. This guide uses the official Testcontainers sample, + [`tc-guide-testing-spring-boot-kafka-listener`](https://github.com/testcontainers/tc-guide-testing-spring-boot-kafka-listener), + updated to Java 25 and Spring Boot 4. A copy with the finished kit is at + [shelajev/tc-guide-java-sbx-kits](https://github.com/shelajev/tc-guide-java-sbx-kits); + clone that to follow along, or apply the same steps to your own project. + +The sample is a Spring Boot service that listens for product price changes on a +Kafka topic and writes the new price to MySQL through Spring Data JPA. Its +integration test publishes an event with `KafkaTemplate` and uses Awaitility to +assert the row landed in the database, so it starts two containers, Kafka and +MySQL, plus the Testcontainers Ryuk resource reaper. Between them, these +exercise a complete, representative Java development setup — the JDK and Maven +toolchain, network access to Maven Central and image registries, and a working +Docker daemon for the containers the tests spin up — which is exactly what the +kit needs to provide. + +## Run your project in a sandbox + +Start with no kit, so you can see what a bare sandbox gives you. From the +project directory, launch an agent in a fresh sandbox. Give it a name so the +commands throughout the walkthrough are easy to copy: + +```console +$ cd tc-guide-java-sbx-kits +$ sbx run --name java-tmp claude +``` + +`sbx run` creates a sandbox, mounts the current directory into it, and starts +the `claude` agent attached to your terminal. The agent can read and edit your +files, and it has its own Docker daemon, and the base image comes with a useful +set of tools — but not everything a given project needs. Ask it to check the +toolchain, or run the check yourself through the agent's `!` shell (the `!` +prefix runs a command in the sandbox and prints the result): + +```text +!java -version +openjdk version "25.0.3" 2026-04-21 +OpenJDK Runtime Environment (build 25.0.3+9) +!mvn -v +bash: mvn: command not found +``` + +The sandbox ships a JDK, but no Maven. You could install Maven by hand, or ask +the agent to, but you'd repeat that in every new sandbox — and small +differences creep in over time. A kit installs it once, the same way for +everyone. There's a second gap too: `./mvnw test` would hit the default-deny +network the moment Maven reached out to Maven Central. The rest of this guide +fills both with a single kit. Remove this sandbox before continuing: + +```console +$ sbx rm -f java-tmp +``` + +## Build the toolchain kit + +A kit is a directory containing a `spec.yaml` file that declares capabilities a +sandbox can use — tools to install, network rules, credentials, environment +variables, files, and agent context. This kit uses two of them: install +commands that run when the sandbox is built, and a network allowlist that +applies while it runs. It's a `mixin` kit, which layers extra capabilities on +top of an existing agent. + +A kit can be loaded from a local path (a directory or ZIP file), a Git +repository, or an OCI registry. This guide uses a local kit, kept in a +`.sbx/kits//` directory in the project as a convention — the location is +up to you. Checking it into your repository is optional, but committing it is a +good way to share one setup across the team, so everyone's sandbox is +configured the same way. + +Create `.sbx/kits/java-toolchain/spec.yaml`. The install section has three +steps, shown here in full and explained below. The network block comes later, +once you know which domains the project needs: + +```yaml +schemaVersion: "1" +kind: mixin +name: java-toolchain +displayName: Java toolchain (SDKMAN + JDK + Maven) +description: Installs SDKMAN, Temurin JDK 25, and Maven, and puts java and mvn on PATH for sbx exec and agent shell calls. + +commands: + install: + - command: | + set -eu + apt-get update + apt-get install -y zip unzip + user: "root" + description: Install SDKMAN prerequisites + + - command: | + set -eu + exec bash <<'BASH' + set -eo pipefail + + export SDKMAN_DIR="$HOME/.sdkman" + + if [ ! -s "$SDKMAN_DIR/bin/sdkman-init.sh" ]; then + curl -fsSL "https://get.sdkman.io?rcupdate=false" | bash + fi + + mkdir -p "$SDKMAN_DIR/etc" + { + echo "sdkman_auto_answer=true" + echo "sdkman_selfupdate_feature=false" + } >> "$SDKMAN_DIR/etc/config" + + # shellcheck disable=SC1091 + . "$SDKMAN_DIR/bin/sdkman-init.sh" + + sdk install java 25.0.3-tem + sdk install maven 3.9.16 + + java -version + mvn -v + BASH + user: "1000" + description: Install SDKMAN, Temurin JDK 25, and Maven + + - command: | + set -eu + + agent_home="$(getent passwd 1000 | cut -d: -f6)" + if [ -z "$agent_home" ]; then + agent_home="/home/agent" + fi + + touch /etc/sandbox-persistent.sh + if ! grep -q 'java-toolchain-kit-path' /etc/sandbox-persistent.sh; then + cat >> /etc/sandbox-persistent.sh <25`, and `3.9.16` is Maven. The + base image already ships a JDK, but installing a pinned one in the kit means + the version won't drift when the image updates, and every teammate's sandbox + has the same toolchain. + +The closing `java -version` and `mvn -v` make the build fail loudly if either +tool didn't install, so a broken kit never reaches a green checkmark. + +### Put the toolchain on PATH + +Installing the tools isn't enough. A sandbox runs commands two ways: the agent's +own `!` shell, and `sbx exec` from your host. Both start fresh shells that don't +read SDKMAN's rc-file hooks, so without help neither finds `java`. The sandbox +sources `/etc/sandbox-persistent.sh` before every command in both paths, so +that's where the `PATH` entries go, and that's what the third step writes. This +is the standard pattern for version managers like SDKMAN and nvm; see +[Customize the shell environment](../manuals/ai/sandboxes/customize/kit-examples.md#customize-the-shell-environment) +for the same technique applied to nvm. + +The third step prepends SDKMAN's `current/bin` directories for the JDK and Maven +to `PATH`, so the kit's pinned toolchain takes precedence over the JDK the base +image ships. The `grep` guard with the `java-toolchain-kit-path` marker +keeps the block from being added twice, and the `case` statements keep each +directory from being added to `PATH` more than once — that file is sourced +before every single command, and a growing `PATH` adds up fast. + +> [!WARNING] +> Add only the `PATH` exports, never SDKMAN's bash-completion script. Because +> `/etc/sandbox-persistent.sh` is sourced before every command and not only at +> shell startup, a completion script (which expects `COMP_WORDS` and related +> variables to exist) breaks every subsequent command, and the shell goes +> silent. Sourcing `sdkman-init.sh` here would also give you the `sdk` command +> in every shell, at the cost of running SDKMAN's init on every command. Point +> `PATH` at the binaries directly instead: `java` and `mvn` are what the agent +> needs for normal work, and if you want `sdk` later you can run +> `source "$SDKMAN_DIR/bin/sdkman-init.sh"` by hand. + +## Open the network for a Spring Boot and Maven workflow + +By default, a sandbox denies every outbound network request that isn't on an +allowlist. The kit's install commands run when the sandbox is created, and they +need the network — SDKMAN's API, the JDK download broker, and Maven Central. +With nothing allowed, those requests are blocked, the install fails, and +`sbx run` never finishes building the sandbox. So the kit has to declare its +allowlist before you can use it. + +For this project the list is short and predictable, following the tools as they +reach out: SDKMAN and its JDK broker while the kit installs, Maven Central when +the build resolves dependencies, and Docker Hub when Testcontainers pulls +images. Add a `network` block to `spec.yaml`: + +```yaml +network: + allowedDomains: + # SDKMAN + JDK/Maven distribution + - "get.sdkman.io:443" + - "api.sdkman.io:443" + - "broker.sdkman.io:443" + - "github.com:443" + - "release-assets.githubusercontent.com:443" + + # Maven Central + - "repo.maven.apache.org:443" + + # Testcontainers images from Docker Hub: cp-kafka, mysql, and Ryuk + - "auth.docker.io:443" + - "registry-1.docker.io:443" + - "production.cloudfront.docker.com:443" +``` + +This is the whole list for this project: SDKMAN and its JDK broker, GitHub and +its release-asset host (where SDKMAN fetches the Temurin build), Maven Central, +and the three Docker Hub endpoints an image pull touches. It's short enough to +review in a code review, which is the point. Anything not on it stays blocked, +and you can see exactly what the agent is allowed to reach. + +> [!NOTE] +> Your host might already have broader policy rules (for GitHub or Docker Hub, +> say) from other work. A domain those rules already cover won't show up as a +> block. The kit still declares it explicitly so the allowlist is complete on a +> teammate's machine that doesn't have those host rules. + +## Run the Testcontainers integration tests + +The kit is now complete: install commands and the network allowlist. Validate +the finished spec before you run it: + +```console +$ sbx kit validate .sbx/kits/java-toolchain +Kit "java-toolchain" is valid. +``` + +Create the sandbox with the finished kit and start the agent. This time the +install commands can reach the network, so the sandbox builds cleanly: + +```console +$ sbx run --name java-tmp claude --kit .sbx/kits/java-toolchain +``` + +The toolchain is on `PATH` for both entry points. Verify from your host with +`sbx exec`: + +```console +$ sbx exec java-tmp bash -lc 'java -version && mvn -v' +openjdk version "25.0.3" 2026-xx-xx +OpenJDK Runtime Environment Temurin-25.0.3+x (build 25.0.3+x) +OpenJDK 64-Bit Server VM Temurin-25.0.3+x (build 25.0.3+x, mixed mode, sharing) +Apache Maven 3.9.16 +``` + +and from inside the agent with `!java -version`. Both find the toolchain, which +is the proof the `PATH` step worked across both shells. + +Run the integration test. Testcontainers talks to the sandbox's built-in Docker +daemon, so there's nothing extra to configure: + +```text +!./mvnw test +``` + +On the first run, Maven downloads dependencies from Maven Central, and +Testcontainers pulls `confluentinc/cp-kafka:7.6.1`, `mysql:8.0.32`, and the Ryuk +reaper image from Docker Hub, all over the domains the kit allows. The +containers start and the test runs: + +```text +[INFO] Creating container for image: testcontainers/ryuk:... +[INFO] Container testcontainers/ryuk:... started +[INFO] Creating container for image: confluentinc/cp-kafka:7.6.1 +[INFO] Container confluentinc/cp-kafka:7.6.1 started +[INFO] Creating container for image: mysql:8.0.32 +[INFO] Container mysql:8.0.32 started +[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 +[INFO] BUILD SUCCESS +``` + +The agent built and tested a real Spring Boot service, with live Kafka and MySQL +containers, entirely inside the sandbox. + +To see the other half — that the sandbox is still closed — try a domain the kit +doesn't allow: + +```text +!curl -sS https://example.com +Blocked by network policy: domain example.com + detail: no matching allow rule — blocked by default deny policy +``` + +The agent has exactly what the project needs and nothing more. When you're done +exploring, remove the throwaway sandbox: + +```console +$ sbx rm -f java-tmp +``` + +## Make it reproducible for the team + +The kit lives in the repository, so committing it turns this from a one-off +setup into something the whole team shares. A teammate clones the repository, +launches the sandbox with the kit, and gets the same JDK, the same Maven, and +the same network allowlist, in their own isolated microVM. The setup is reviewed +like any other code, because it is code. + +### Give the agent context about the kit + +The agent starts fresh in each sandbox and doesn't automatically know what the +kit installed. Add an `agentContext` block to `spec.yaml`; the sandbox appends +it to the agent's memory file at creation, so the agent starts already knowing +the toolchain is there and how to use it: + +```yaml +agentContext: | + This sandbox has a Java toolchain installed and on PATH: Temurin JDK 25 and + Maven, managed by SDKMAN. Build and test the project with `./mvnw`. + Testcontainers uses the sandbox's built-in Docker daemon, so integration + tests run with `./mvnw test`. +``` + +This is the place for kit-specific notes the agent should act on, and it travels +with the kit — so you don't have to repeat it in the project's `CLAUDE.md`. + +### Document how to start + +The one thing a person needs is the launch command, so put it in the project's +`README` where a teammate will look for it: + +````md +## Run this project in a Docker Sandbox + +Start a coding agent in a sandbox with the committed kit: + +```console +$ sbx run claude --kit .sbx/kits/java-toolchain +``` +```` + +A teammate clones the repository, runs that one command, and gets the same JDK, +the same Maven, and the same network allowlist, in their own isolated microVM. + +> [!TIP] +> To steer teammates away from running a coding agent directly on their host, +> add an instruction to the project's agent instructions (for example, +> `CLAUDE.md`) that tells the agent to stop and relaunch in a sandbox: +> +> ```md +> If you are not running inside a Docker Sandbox, stop and tell the user to +> relaunch you with `sbx run claude --kit .sbx/kits/java-toolchain`. +> ``` +> +> It's opinionated, but it turns "use the sandbox" from a suggestion into +> something the agent helps enforce. + +## Conclusion + +You have a coding agent that is productive on a Java project, with a full JDK +and Maven toolchain and a working Docker daemon for Testcontainers, running in +an isolated microVM whose network only reaches the handful of domains the +project actually uses. The kit makes that combination reproducible: it's +committed to the repository, readable in a pull request, and applied with one +command, so the safe and productive setup is the default for everyone on the +team rather than something each person reconstructs by hand. + +This kit is deliberately composite. It bundles SDKMAN, the JDK, and Maven +together because they're always needed as a unit for this project. If your team +has kits for other stacks, it might make sense to factor them into smaller, +composable kits instead. + +The finished sample, including the kit, is at +[shelajev/tc-guide-java-sbx-kits](https://github.com/shelajev/tc-guide-java-sbx-kits). + +## Further reading + +- [Docker Sandboxes overview](../manuals/ai/sandboxes/_index.md) for what the + isolation gives you and how `sbx` works. +- [Customize sandboxes with kits](../manuals/ai/sandboxes/customize/kits.md) for + what kits can do and how to build one. +- [Kit spec reference](../manuals/ai/sandboxes/customize/kit-reference.md) for + every field in `spec.yaml`, including `network`, `agentContext`, and command + users. +- [Testing Spring Boot Kafka listeners with Testcontainers](https://testcontainers.com/guides/testing-spring-boot-kafka-listener-using-testcontainers/), + the original guide behind the sample app. +- [sbx-moderne-kit](https://github.com/shelajev/sbx-moderne-kit) for a more + involved kit that installs a different toolchain using the same + PATH-persistence pattern. diff --git a/content/guides/opencode-model-runner.md b/content/guides/opencode-model-runner.md index ca667f17ea54..b625b44aed97 100644 --- a/content/guides/opencode-model-runner.md +++ b/content/guides/opencode-model-runner.md @@ -8,7 +8,6 @@ keywords: ai, opencode, docker model runner, local models, coding assistant weight: 3 params: tags: [ai] - featured: true time: 10 minutes ---