---
id: 517bf76b20da9b1bac643f4bac1bfd6b
author: Lemon Mint
title: Raske isolerte Docker-miljøer for AI-agent RL-rollouts med gvx
description: Hvordan kjøre hundrevis av parallelle, sterkt isolerte, uprivilegerte Docker- og Kubernetes-miljøer på en sikker måte for SWE-bench og rollouts i forsterkningslæring for kodeagenter, med copy-on-write-tilbakestillinger av ext4lite-volumer på 100 millisekunder.
language: "no"
date: 2026-11-06T12:02:55.95126412Z
path: /blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04
go_package: z.gfire.dev/gvx
go_repourl: https://github.com/gfire-sigs/gvx.git
category: ai
---

## Flaskehalsen i sikkerhet og infrastruktur for RL-rollouts med kodeagenter

Trening og evaluering av programvareingeniør-agenter på tvers av benchmarks og rammeverk for reinforcement learning (RL), som **SWE-bench**, **SWE-ReX**, **R2E-Gym**, **SWE-Gym**, **Verl** og **OpenHands**, krever kjøring av tusenvis av samtidige rollouts av **utrygg, LLM-generert kode**. I reelle oppgaver innen programvareutvikling redigerer en agent ikke bare tekstfiler: Den kjører `docker build`, starter databaser og mikrotjenester via `docker compose`, kjører suiter med integrasjonstester og inspiserer containerlogger.

Ved rollout-skala (128 til 1 024 samtidige episoder per klynge) møter eksisterende containerinfrastruktur fire systemiske flaskehalser knyttet til sikkerhet og ytelse:

1. **Låskonflikt og tilstandslekkasje i vertens `dockerd`**: Når hundrevis av samtidige RL-arbeidere peker mot en enkelt vertsbasert `/var/run/docker.sock`, gir det hver agent root-tilsvarende tilgang til vertsdemonen, serialiserer uttrekking av image-lag, tømmer adresserommet for `docker0`-subnett, utløser feilmeldingen `Another app is currently holding the xtables lock` i vertens `iptables` og lekker foreldreløse containere på tvers av episoder.
2. **Risiko for containerrømming via privilegert DinD**: Kjøring av standard `docker:dind`-containere krever `--privileged` (`CAP_SYS_ADMIN`), noe som eksponerer rå blokkenheter på verten, cgroup release agents og kjernemoduler for utrygge RL-rollouts.
3. **Begrensninger ved maskinvarevirtualisering (`/dev/kvm`)**: MicroVM-kjøretider (`Firecracker`, `QEMU`, `Kata Containers`) isolerer interne kjerner på en ren måte, men de krever `/dev/kvm`. Standard Kubernetes-pods uten nøstet virtualisering, bare-metal-skyinstanser der KVM er deaktivert, og eksisterende `gVisor` (`runsc`)-sandkasser kan ikke starte KVM-microVM-er.
4. **Høy latens ved tilbakestilling av episoder (`env.reset()`)**: Oppstart av et nytt `docker:dind`-miljø og henting eller utpakking av basisbilder for hver RL-episode tar 10 til 60 sekunder, og GPU-treningsarbeidere står inaktive mens de venter på tilbakestilling av miljøene.

**[gvx](https://github.com/gfire-sigs/gvx)** (`rungv`-CLI og `z.gfire.dev/gvx/pkg/gvx` Go-SDK) tilbyr en **sikker rollout-kjøretid med forsvar i dybden** som løser alle fire flaskehalsene. Hvert rollout-miljø kjører som **én enkelt, uprivilegert og selvlåst prosess i brukerrommet** (isolert av Googles gVisor `Sentry`-kjerne, `pkg/hostlockdown` og et `netstack` i minnet), og hele tilstanden i `/var/lib/docker` ligger i **én enkelt copy-on-write `.ext4`-fil (`ext4lite`)**.

> **Null-privilegert sikkerhet for utrygge agentutrullinger**: Hver `gvx`-episode låser sin egen vertsprosess i operativsystemet før gjestekode kjøres (`PR_SET_NO_NEW_PRIVS` + BPF `seccomp` på Linux, Seatbelt `deny default` på macOS, Restricted Token + Job Object på Windows). Selv om en RL-agent kjører `docker run --privileged` eller destruktive `rm -rf /`-kommandoer inne i sin episode, forblir disse operasjonene strengt begrenset til episodens `Sentry`-kjerne i brukerrommet og til dens midlertidige `.ext4`-volumfil.

---

## Rollout-arkitektur: Parallelle uprivilegerte instanser + Reflink-øyeblikksbilder

I `gvx` er hver rollout-arbeider en vanlig uprivilegert OS-prosess med sin egen gVisor `Sentry`-kjerne, sitt eget `netstack` i brukerrommet (`10.0.2.15/24`) og sitt eget `ext4lite`-blokkvolum (`ep-N.ext4`). Samtidig deler den en skrivebeskyttet, innholdsadressert `erofs`-OCI-bildecache på tvers av vertens sidecache.

![Parallell arkitektur for RL-rollouts med agenter og gvx](/assets/images/gvx/gvx-rl-rollout-snapshot.png)

### Hvorfor enkeltfilsvolumer med `ext4lite` endrer økonomien ved RL-tilbakestilling

Fordi `ext4lite`-filsystemet i `gvx` lagrer et helt JBD2-journalført `ext4`-volum (inkludert Dockers `overlayfs`-lagkataloger, utvidede attributter `trusted.overlay.*`, whiteout-enhetsnoder og bildemetadata) i én vanlig fil på verten, blir livssyklushåndtering av episoder en enkelt filoperasjon:

1. **100 millisekunders `env.reset()` via `cp --reflink=always`**:
   - Du bygger én gang en fil `golden-docker.ext4` som inneholder forhåndshentede basisbilder (`python:3.11`, `postgres:16`, `redis:7` eller oppgavespesifikke basisbilder for `SWE-bench`).
   - Ved starten av hver RL-episode oppretter `cp --reflink=always golden-docker.ext4 ep-0042.ext4` (på XFS, Btrfs, ZFS eller macOS APFS) en uavhengig Copy-on-Write-klone på under 100 millisekunder uten å kopiere underliggende diskblokker.
   - Bare de 4 KiB-blokkene som er endret under den spesifikke episoden, bruker ekstra diskplass.
2. **Nedrivning uten rester (`env.close()`)**:
   - Når `rungv`-prosessen avsluttes (`sb.Close()` i Go) og `ep-0042.ext4` fjernes, frigjøres 100 % av episodens prosesser, nettverkssokler, monteringer og lagring på ca. 10 millisekunder. Ingenting blir noen gang montert i vertskjernens monteringstabell eller registrert i vertens cgroups.

---

## Trinn-for-trinn-arbeidsflyt: Golden `.ext4`-øyeblikksbilde og rask tilbakestilling av episoder

Du kan installere `rungv` på hver rollout-arbeiderknute enten ved å laste ned den SLSA-attesterte, ferdigbygde binærfilen fra [GitHub Releases](https://github.com/gfire-sigs/gvx/releases) (`gh attestation verify rungv-v0.2.0-linux-amd64.tar.gz --repo gfire-sigs/gvx --signer-workflow gfire-sigs/gvx/.github/workflows/release.yml --deny-self-hosted-runners`) eller ved å kjøre `CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest`.

### Steg 1: Opprett det golden `docker:dind`-volumet én gang

```sh
mkdir -p /srv/rl-snapshots /srv/rl-episodes

# 1. Start en seed-instans av docker:dind basert på golden-docker.ext4
rungv run \
  -p unix:/srv/rl-snapshots/seed.sock:2375 \
  --mount type=volume,src=/srv/rl-snapshots/golden-docker.ext4,dst=/var/lib/docker \
  -e DOCKER_TLS_CERTDIR= \
  docker:dind --tls=false &
SEED_PID=$!

# 2. Forhåndsfyll basisbilder og byggecache inne i golden-docker.ext4
export DOCKER_HOST="unix:///srv/rl-snapshots/seed.sock"
until docker info >/dev/null 2>&1; do sleep 0.5; done

docker pull python:3.11-slim
docker pull redis:7-alpine
docker pull postgres:16-alpine

# 3. Stopp seed-instansen på en kontrollert måte, slik at ext4lite tømmer buffere og komprimerer bildet
kill -INT $SEED_PID
wait $SEED_PID
```

### Steg 2: Start parallelle RL-episoder på 100 millisekunder

Hver parallell rollout-arbeider kloner `golden-docker.ext4` via reflink og starter en isolert `docker:dind`-instans med alle bilder allerede til stede i `/var/lib/docker`:

```sh
EPISODE_ID="ep-0042"
SOCK="/srv/rl-episodes/${EPISODE_ID}.sock"

# Umiddelbar Copy-on-Write-klone (~100 ms på XFS/Btrfs/APFS)
cp --reflink=always \
  /srv/rl-snapshots/golden-docker.ext4 \
  "/srv/rl-episodes/${EPISODE_ID}.ext4"

# Start isolert uprivilegert episode med 4 GiB RAM og 2 CPU-er
rungv run \
  -m 4g --cpus 2 \
  -p "unix:${SOCK}:2375" \
  --mount "type=volume,src=/srv/rl-episodes/${EPISODE_ID}.ext4,dst=/var/lib/docker" \
  -e DOCKER_TLS_CERTDIR= \
  docker:dind --tls=false &
EP_PID=$!

export DOCKER_HOST="unix://${SOCK}"
until docker info >/dev/null 2>&1; do sleep 0.2; done

# Inne i rollout-arbeideren er bildene umiddelbart tilgjengelige uten nedlasting over nettverket
docker images

# Nedrivning av episoden etter beregning av belønning (~10 ms)
kill -KILL $EP_PID
rm -f "/srv/rl-episodes/${EPISODE_ID}.ext4" "${SOCK}"
```

---

## Programmatisk rollout-pool med Go-SDK-en `z.gfire.dev/gvx/pkg/gvx`

Hvis RL-miljøserveren eller evalueringsrammeverket ditt er skrevet i Go (eller kaller en Go-rollout-demon over gRPC), kan du styre parallelle containerepisoder direkte gjennom `z.gfire.dev/gvx/pkg/gvx`:

```go
package main

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"z.gfire.dev/gvx/pkg/gvx"
)

func runEpisode(ctx context.Context, goldenExt4, scratchDir string, epID int) error {
	epExt4 := filepath.Join(scratchDir, fmt.Sprintf("ep-%04d.ext4", epID))
	defer os.Remove(epExt4)

	// 1. Lag en reflink-klone av det golden ext4lite-volumet
	cpCmd := exec.CommandContext(ctx, "cp", "--reflink=always", goldenExt4, epExt4)
	if err := cpCmd.Run(); err != nil {
		return fmt.Errorf("clone golden volume: %w", err)
	}

	// 2. Sett en tidsgrense i sanntid per episode
	epCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
	defer cancel()

	var stdout, stderr bytes.Buffer
	sb, err := gvx.New(epCtx, gvx.Config{
		Platform:               gvx.PlatformAuto,
		NetworkMode:            gvx.NetModeNAT,
		NetRaw:                 true,
		AllowPacketSocketWrite: true,
		AllowSUID:              true,
		OCIImage:               "docker:dind",
		Entrypoint:             []string{"sh", "-c"},
		EntrypointSet:          true,
		Cmd: []string{
			"dockerd-entrypoint.sh --tls=false >/var/log/dockerd.log 2>&1 & " +
				"until docker info >/dev/null 2>&1; do sleep 0.2; done && " +
				"docker run --rm python:3.11-slim python -c 'print(6 * 7)'",
		},
		Envv: []string{"DOCKER_TLS_CERTDIR="},
		Mounts: []gvx.Mount{
			{
				Kind:      gvx.MountVolume,
				ImagePath: epExt4,
				GuestPath: "/var/lib/docker",
			},
		},
		Resources: gvx.Resources{
			MemoryBytes: 4 << 30, // Maksimalt 4 GiB
			CPUMillis:   2000,    // 2,0 CPU-er
		},
		Stdout: &stdout,
		Stderr: &stderr,
	})
	if err != nil {
		return fmt.Errorf("init episode %d: %w", epID, err)
	}
	defer sb.Close()

	exitCode, err := sb.Run(epCtx)
	if err != nil {
		return fmt.Errorf("run episode %d: %w", epID, err)
	}

	log.Printf("episode %d finished: exit=%d stdout=%q", epID, exitCode, stdout.String())
	return nil
}

func main() {
	ctx := context.Background()
	if err := runEpisode(ctx, "/srv/rl-snapshots/golden-docker.ext4", "/srv/rl-episodes", 1); err != nil {
		log.Fatal(err)
	}
}
```

---

## Sammenligning av kjøretider for sandkasser i agent-RL

| Metrikk / krav                                            | Delt vert-`dockerd`               | Privilegert `docker:dind`  | Firecracker / KVM MicroVM            | Standard `gVisor` (`runsc`)          | `gvx` (`rungv` / `pkg/gvx`)                   |
| :-------------------------------------------------------- | :-------------------------------- | :------------------------- | :----------------------------------- | :----------------------------------- | :-------------------------------------------- |
| **Krever vert-`root` eller `--privileged`**               | Ja (`docker.sock` = root)         | Ja (`--privileged`)        | Krever `/dev/kvm` + `TAP`-oppsett    | Ja (eller `userns` + `mount`)        | **Nei (én enkelt uprivilegert prosess)**      |
| **Fungerer i uprivilegerte K8s-pods / eksisterende `runsc`** | Nei                            | Nei                        | Nei (ingen `/dev/kvm`)               | Feiler (`EBUSY` ved nøstet seccomp)  | **Ja (automatisk reserveløsning med futex-løkke)** |
| **Støtter indre `docker build` og `docker compose`**      | Deler vertsdemonen                | Ja                         | Ja                                   | Feiler uten justering av vert-DinD   | **Ja (`overlayfs` på `ext4lite`)**            |
| **Nettverksisolering per episode**                        | Delt `docker0` / `xtables`-lås    | Eget navnerom              | Eget VM-nettverkskort                | Eget netns                           | **Isolert `netstack` i prosessen**            |
| **Latens ved varm tilbakestilling av episode (`env.reset()`)** | 5 s til 30 s (beskjæring av container/volum) | 10 s til 60 s (kald demon) | ~150 ms (VM-øyeblikksbilde)          | 5 s til 20 s                         | **~100 ms (`cp --reflink=always` `.ext4`)**   |
| **Lokal utvikling på tvers av plattformer (Windows / macOS / Linux)** | Krever Linux-VM            | Krever Linux-VM            | Kun Linux KVM                        | Kun Linux                            | **Innebygd støtte på Windows, macOS og Linux** |

---

## Videre lesning i denne serien

1. **[gvx: Kjøring av Linux-containere, Docker og Kubernetes i én uprivilegert prosess](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)**: Fullstendig gjennomgang av arkitekturen i `gvx` på Windows (`whpx`, `systrap`), macOS (`hvf`) og Linux (`systrap`).
2. **[Kjøring av Docker, docker build og docker compose uten root eller brukernavnerom](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)**: Rask veiledning i tre trinn for oppstart og feilsøking når `sudo`, `unshare(CLONE_NEWUSER)`, `mount(2)` og `/dev/fuse` er blokkert.
3. **[Kjøring av Docker-in-Docker inne i gVisor og nøstede sandkasser med gvx](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)**: Grundig gjennomgang av hvorfor `docker.io` og nøstet `runsc` feiler inne i `gVisor` (`runsc`), og hvordan `gvx` faller tilbake til futex-løkken `_RUN_SYSCALL_LOOP` ved `EBUSY`.
4. **[Raske isolerte Docker-miljøer for RL-rollouts med AI-agenter og gvx](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)** (dette innlegget)
