---
id: 517bf76b20da9b1bac643f4bac1bfd6b
author: Lemon Mint
title: Lingkungan Docker Terisolasi yang Cepat untuk Rollout RL Agen AI dengan gvx
description: Cara mengoperasikan secara aman ratusan lingkungan Docker dan Kubernetes yang berjalan paralel, terisolasi secara ketat, dan tanpa hak istimewa (unprivileged) untuk SWE-bench serta rollout reinforcement learning pada coding agent, dengan pengaturan ulang volume ext4lite berbasis copy-on-write dalam waktu 100 milidetik.
language: id
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
---

## Hambatan Keamanan dan Infrastruktur dalam Rollout RL Coding Agent

Melatih dan mengevaluasi software engineering agent di berbagai benchmark dan framework reinforcement learning (RL) seperti **SWE-bench**, **SWE-ReX**, **R2E-Gym**, **SWE-Gym**, **Verl**, dan **OpenHands** membutuhkan eksekusi ribuan rollout konkuren terhadap **kode untrusted yang dihasilkan LLM**. Dalam tugas software engineering dunia nyata, agent tidak hanya menyunting file teks: agent menjalankan `docker build`, menyalakan database dan microservice melalui `docker compose`, mengeksekusi suite integration test, dan memeriksa log container.

Pada skala rollout (128 hingga 1.024 episode konkuren per cluster), infrastruktur container yang ada menghadapi empat hambatan keamanan dan performa yang bersifat sistemik:

1. **Kontensi Lock `dockerd` Host dan Kebocoran State**: Mengarahkan ratusan worker RL konkuren ke satu `/var/run/docker.sock` host memberikan setiap agent akses setara root ke daemon host, menyerialkan ekstraksi layer image, menghabiskan alokasi subnet `docker0`, memicu error `Another app is currently holding the xtables lock` pada `iptables` host, dan membocorkan container yatim lintas episode.
2. **Risiko Container Escape pada DinD Privileged**: Menjalankan container `docker:dind` standar memerlukan `--privileged` (`CAP_SYS_ADMIN`), yang mengekspos block device host mentah, cgroup release agent, dan kernel module kepada rollout RL yang untrusted.
3. **Batasan Virtualisasi Perangkat Keras (`/dev/kvm`)**: Runtime MicroVM (`Firecracker`, `QEMU`, `Kata Containers`) mengisolasi kernel internal dengan bersih, tetapi memerlukan `/dev/kvm`. Pod Kubernetes standar tanpa nested virtualization, instance cloud bare-metal tempat KVM dinonaktifkan, dan sandbox `gVisor` (`runsc`) yang ada tidak dapat menjalankan microVM KVM.
4. **Latensi Reset Episode (`env.reset()`) yang Lambat**: Memulai lingkungan `docker:dind` baru serta menarik atau membongkar base image untuk setiap episode RL memakan waktu 10 hingga 60 detik, sehingga worker training GPU menganggur selama menunggu reset lingkungan.

**[gvx](https://github.com/gfire-sigs/gvx)** (CLI `rungv` dan Go SDK `z.gfire.dev/gvx/pkg/gvx`) menyediakan **runtime rollout yang aman dengan pertahanan berlapis (defense-in-depth)**, yang mengatasi keempat hambatan tersebut. Setiap lingkungan rollout berjalan sebagai **satu proses user-space tanpa privilese yang mengunci dirinya sendiri** (diisolasi oleh kernel `Sentry` gVisor milik Google, `pkg/hostlockdown`, dan `netstack` dalam memori), dengan seluruh state `/var/lib/docker`-nya berada di dalam **satu file `.ext4` copy-on-write tunggal (`ext4lite`)**.

> **Keamanan Zero-Privilege untuk Rollout Agent yang Untrusted**: Setiap episode `gvx` mengunci proses OS host miliknya sendiri sebelum menjalankan kode guest (`PR_SET_NO_NEW_PRIVS` + BPF `seccomp` pada Linux, Seatbelt `deny default` pada macOS, Restricted Token + Job Object pada Windows). Bahkan jika agent RL menjalankan `docker run --privileged` atau perintah destruktif `rm -rf /` di dalam episodenya, operasi tersebut tetap terbatas secara ketat pada kernel user-space `Sentry` milik episode itu dan file volume `.ext4` yang bersifat sementara.

---

## Arsitektur Rollout: Instance Unprivileged Paralel + Snapshot Reflink

Dalam `gvx`, setiap worker rollout adalah proses OS unprivileged biasa dengan kernel gVisor `Sentry`-nya sendiri, `netstack` user-space-nya sendiri (`10.0.2.15/24`), dan volume blok `ext4lite`-nya sendiri (`ep-N.ext4`), sementara berbagi cache image OCI `erofs` yang bersifat read-only dan content-addressable melalui page cache host.

![Arsitektur Rollout RL Agent Paralel dengan gvx](/assets/images/gvx/gvx-rl-rollout-snapshot.png)

### Mengapa Volume `ext4lite` Berkas Tunggal Mengubah Ekonomi Reset RL

Karena filesystem `ext4lite` milik `gvx` menyimpan seluruh volume `ext4` ber-journal JBD2 (termasuk direktori layer `overlayfs` Docker, extended attribute `trusted.overlay.*`, whiteout device node, dan metadata image) di dalam satu file reguler host, pengelolaan siklus hidup episode menjadi satu operasi file:

1. **`env.reset()` 100 Milidetik melalui `cp --reflink=always`**:
   - Anda membangun file `golden-docker.ext4` sekali saja yang berisi base image yang sudah di-pull sebelumnya (`python:3.11`, `postgres:16`, `redis:7`, atau base image SWE-bench spesifik tugas).
   - Pada awal setiap episode RL, `cp --reflink=always golden-docker.ext4 ep-0042.ext4` (pada XFS, Btrfs, ZFS, atau APFS macOS) membuat clone Copy-on-Write yang independen dalam waktu kurang dari 100 milidetik tanpa menyalin blok disk yang mendasarinya.
   - Hanya blok 4 KiB yang di-dirty selama episode spesifik tersebut yang menggunakan ruang disk tambahan.
2. **Teardown Tanpa Residu (`env.close()`)**:
   - Menghentikan proses `rungv` (`sb.Close()` di Go) dan melepas tautan `ep-0042.ext4` membebaskan 100% proses, socket jaringan, mount, dan penyimpanan episode dalam sekitar 10 milidetik. Tidak ada yang pernah di-mount pada tabel mount kernel host atau didaftarkan di cgroup host.

---

## Alur Kerja Langkah demi Langkah: Snapshot `.ext4` Golden dan Reset Episode Cepat

Anda dapat memasang `rungv` pada setiap node worker rollout, baik dengan mengunduh binary prebuilt yang diattest SLSA dari [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`) atau dengan menjalankan `CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest`.

### Langkah 1: Buat Volume `docker:dind` Golden Sekali Saja

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

# 1. Mulai instance seed docker:dind yang didukung 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. Isi awal base image dan build cache di dalam 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. Hentikan instance seed dengan baik-baik agar ext4lite melakukan flush dan kompaksi image
kill -INT $SEED_PID
wait $SEED_PID
```

### Langkah 2: Jalankan Episode RL Paralel dalam 100 Milidetik

Setiap worker rollout paralel meng-clone `golden-docker.ext4` melalui reflink dan mem-boot instance `docker:dind` yang terisolasi dengan semua image sudah tersedia di `/var/lib/docker`:

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

# Clone CoW instan (~100 ms pada XFS/Btrfs/APFS)
cp --reflink=always \
  /srv/rl-snapshots/golden-docker.ext4 \
  "/srv/rl-episodes/${EPISODE_ID}.ext4"

# Jalankan episode unprivileged yang terisolasi dengan RAM 4 GiB dan 2 CPU
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

# Di dalam worker rollout, image langsung tersedia tanpa pull dari jaringan
docker images

# Teardown episode setelah kalkulasi reward (~10 ms)
kill -KILL $EP_PID
rm -f "/srv/rl-episodes/${EPISODE_ID}.ext4" "${SOCK}"
```

---

## Pool Rollout Terprogram Menggunakan Go SDK `z.gfire.dev/gvx/pkg/gvx`

Jika server environment RL atau harness evaluasi Anda ditulis dalam Go (atau memanggil daemon rollout Go melalui gRPC), Anda dapat mengelola episode container paralel secara langsung melalui `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. Clone reflink volume golden ext4lite
	cpCmd := exec.CommandContext(ctx, "cp", "--reflink=always", goldenExt4, epExt4)
	if err := cpCmd.Run(); err != nil {
		return fmt.Errorf("clone golden volume: %w", err)
	}

	// 2. Terapkan timeout wall-clock 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, // batas 4 GiB
			CPUMillis:   2000,    // 2,0 CPU
		},
		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)
	}
}
```

---

## Perbandingan Runtime Sandbox RL Agent

| Metrik / Persyaratan                                      | Shared Host `dockerd`             | Privileged `docker:dind` | Firecracker / KVM MicroVM      | Standard `gVisor` (`runsc`)       | `gvx` (`rungv` / `pkg/gvx`)                 |
| :-------------------------------------------------------- | :-------------------------------- | :----------------------- | :----------------------------- | :-------------------------------- | :------------------------------------------ |
| **Membutuhkan `root` host atau `--privileged`**           | Ya (`docker.sock` = root)         | Ya (`--privileged`)      | Membutuhkan `/dev/kvm` + setup `TAP` | Ya (atau `userns` + `mount`) | **Tidak (satu proses unprivileged)**        |
| **Berjalan di dalam Pod K8s unprivileged / `runsc` yang ada** | Tidak                  | Tidak                    | Tidak (tanpa `/dev/kvm`)       | Gagal (`EBUSY` pada nested seccomp) | **Ya (fallback loop futex otomatis)**     |
| **Mendukung `docker build` & `docker compose` di dalam**  | Berbagi daemon host               | Ya                       | Ya                             | Gagal tanpa tuning DinD host      | **Ya (`overlayfs` di atas `ext4lite`)**     |
| **Isolasi jaringan per episode**                          | `docker0` / lock `xtables` bersama | Namespace terpisah      | NIC VM terpisah                | Netns terpisah                    | **Diisolasi dalam `netstack` di dalam proses** |
| **Latensi reset episode hangat (`env.reset()`)**          | 5 detik hingga 30 detik (prune container/volume) | 10 detik hingga 60 detik (daemon dingin) | ~150 ms (snapshot VM) | 5 detik hingga 20 detik | **~100 ms (`cp --reflink=always` `.ext4`)** |
| **Pengembangan lokal lintas platform (Windows / macOS / Linux)** | Memerlukan VM Linux        | Memerlukan VM Linux      | Hanya Linux KVM                | Hanya Linux                       | **Native di Windows, macOS, dan Linux**     |

---

## Bacaan Lanjutan dalam Seri Ini

1. **[gvx: Menjalankan Container Linux, Docker, dan Kubernetes dalam Satu Proses Unprivileged](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)**: Walkthrough arsitektur lengkap `gvx` di Windows (`whpx`, `systrap`), macOS (`hvf`), dan Linux (`systrap`).
2. **[Menjalankan Docker, docker build, dan docker compose Tanpa Root atau User Namespace](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)**: Panduan bootstrap cepat 3 langkah dan troubleshooting ketika `sudo`, `unshare(CLONE_NEWUSER)`, `mount(2)`, dan `/dev/fuse` diblokir.
3. **[Menjalankan Docker-in-Docker di Dalam gVisor dan Sandbox Bersarang dengan gvx](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)**: Penjelasan mendalam mengapa `docker.io` dan `runsc` bersarang gagal di dalam `gVisor` (`runsc`) dan bagaimana `gvx` beralih ke loop futex `_RUN_SYSCALL_LOOP` saat terjadi `EBUSY`.
4. **[Lingkungan Docker Terisolasi yang Cepat untuk Rollout RL Agent AI dengan gvx](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)** (postingan ini)
