Dev Sandbox autohospedado con Docker y Go: preview URLs sin Kubernetes

"El README de sandboxed describe el plano de control en Go, Docker, Traefik, SQLite, preview URLs, idle stop y límites de hardening en producción."
"La documentación de Docker sobre restricciones de recursos explica que los containers no tienen límites de CPU o memoria sin configuración explícita."
"La documentación de Docker Sandboxes usa microVMs, Docker daemon independiente, aislamiento de red y aislamiento de credenciales como modelo de seguridad más fuerte."
"El Docker provider de Traefik puede descubrir configuración de routing desde Docker labels y enrutar con Host rules a servicios en containers."
Abrir un entorno de preview por cada PR suele resolverse con Vercel o Netlify. Si importan más el costo, los datos dentro de una red privada o controlar la infraestructura, un host Docker único más un plano de control en Go puede cubrir la misma necesidad inicial. Cada sandbox tiene su propia preview URL, los recursos tienen límites y la frontera de seguridad queda explícita. Sin Kubernetes y sin varios nodos.
Tabla de decisión: cuándo usar cada opción
Cuando aparece la necesidad de “entornos de preview autohospedados”, mucha gente piensa primero en scripts con varios docker run, o salta directo a Kubernetes. Mejor empieza por esta tabla.
| Escenario | Opción recomendada | Motivo |
|---|---|---|
| Equipo interno con menos de 10 previews concurrentes y frontera de confianza dentro del equipo | Docker en un solo host + plano de control en Go | La densidad de recursos es manejable, la arquitectura es simple y no operas un cluster K8s |
| Equipo interno con más de 20 previews concurrentes o necesidad de alta disponibilidad multinodo | K8s + aislamiento por Namespace | Un host no alcanza; necesitas scheduling entre nodos y despliegues controlados |
| Usuarios externos o ejecución de código no fiable, incluidos agentes autónomos | microVMs como Docker Sandboxes o Firecracker | Docker socket equivale a mucho poder sobre el host; no debe mezclarse con workloads no fiables |
| Preview estática simple sin persistencia | Script shell + puerto aleatorio | Puede funcionar, pero la preview URL no es estable y no hay límites sólidos de recursos ni seguridad |
Usa tres criterios.
Tamaño del equipo: un host único funciona mejor para unas 10 previews internas concurrentes. Una estimación útil: 512 MB de RAM y 0,5 CPU por sandbox. Un host con 16 GB puede sostener unas 20 sandboxes antes de quedarse sin margen. Por encima de esa densidad, pasa a scheduling con K8s o reparte capacidad con microVMs.
Frontera de confianza: miembros internos y usuarios de confianza pueden correr dentro de Docker containers. Si desconocidos externos o agentes autónomos ejecutan código arbitrario, el diseño con Docker socket no es lo bastante seguro. Docker Sandboxes usa aislamiento por microVM; cada sandbox obtiene su propio Docker daemon, filesystem y red.
Camino de upgrade: empieza con Docker en un solo host para validar el loop de producto y la densidad de recursos. Si la concurrencia supera ese host o necesitas alta disponibilidad multinodo, pasa a Kubernetes. Si la frontera de confianza cambia de “equipo interno” a “usuarios externos”, pasa a microVMs.
El README de tastyeffectco/sandboxes deja clara la forma objetivo: AI app builders, agent platforms y coding playgrounds en Docker single-host. No es aislamiento de nivel microVM; es un plano de control en Go que crea containers en un host Docker único y expone preview URLs.
Desglose de arquitectura: componentes centrales del plano de control
Un entorno de preview con Docker single-host no es solo un docker run repetido. Necesita un plano de control que gestione ciclo de vida, registro de rutas y limpieza de recursos. La arquitectura de tastyeffectco/sandboxes se divide en seis módulos.
Plano de control en Go (sandboxd): corre dentro de un container y monta el Docker socket del host junto con un directorio de datos. Gestiona el ciclo de vida de sandbox containers mediante Docker CLI: crear, arrancar, detener y borrar. Todas las metadatos de sandbox viven en SQLite como source of truth.
Montaje de Docker socket: es la entrada del plano de control al Docker daemon. Al montar /var/run/docker.sock, sandboxd obtiene permisos para crear y gestionar containers. También es la frontera de privilegios de toda la arquitectura: con el Docker socket, el plano de control tiene permisos muy altos sobre el host.
Registro con Traefik labels: cuando arranca un sandbox container, el plano de control inyecta la configuración de routing de Traefik mediante Docker labels. Traefik actúa como reverse proxy, descubre rutas desde los labels y envía tráfico de *.preview.example.com al container correcto.
Almacenamiento de metadatos en SQLite: cada sandbox tiene un ID único y un directorio asociado. Las metadatos se guardan en SQLite. Los workspaces viven bajo SANDBOXED_DATA_DIR/workspaces/, con un subdirectorio por sandbox para código fuente, configuración y artefactos.
Idle reaper y pressure reaper: el idle reaper revisa cuánto tiempo lleva inactivo un sandbox container y lo detiene al superar el umbral para liberar RAM. El pressure reaper observa la presión de memoria del host y detiene algunas sandboxes antes de que el host llegue a OOM. Estos dos reapers son el núcleo de recuperación de recursos.
Wake path: después de que el idle reaper detiene un sandbox container, la primera visita a la preview URL debe despertarlo. Un catch-all de Traefik envía la request al plano de control; este arranca el container y devuelve una warming page hasta que la app esté lista.
La versión mínima útil es: container de plano de control en Go, montaje de Docker socket, Traefik, SQLite e idle reaper. El quick start local requiere Docker Engine y el plugin de Compose.
Implementación de preview URL
Lo importante de una preview URL es un dominio estable, no un puerto aleatorio. Cada sandbox recibe un host independiente {sandbox_id}.preview.example.com.
Configuración del Docker provider de Traefik
Traefik puede descubrir la configuración de routing desde labels de containers mediante el Docker provider. Configuración mínima:
# traefik.yml
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
exposedByDefault: false significa que Traefik solo descubrirá containers que lo indiquen explícitamente con labels.
Host rule y Docker labels
El plano de control inyecta labels al crear un sandbox container. Ejemplo:
labels:
- "traefik.enable=true"
- "traefik.http.routers.sandbox123.rule=Host(`sandbox123.preview.example.com`)"
- "traefik.http.routers.sandbox123.entrypoints=websecure"
- "traefik.http.services.sandbox123.loadbalancer.server.port=3000"
La Host rule envía las requests de sandbox123.preview.example.com a ese container. loadbalancer.server.port indica el puerto interno donde escucha la aplicación.
Ruta wake-on-request
Después de que el idle reaper detiene el sandbox container, la primera visita a la preview URL activa el wake flow:
- DNS resuelve hacia Traefik; necesitas wildcard DNS como
*.preview.example.com - Traefik conoce la regla de ruta de la sandbox, pero el container está detenido
- El catch-all de Traefik reenvía la request al wake handler del plano de control
- El plano de control arranca el sandbox container y devuelve una warming page
- Cuando el container está listo, Traefik envía las requests posteriores directamente al container
El catch-all debe tener prioridad inferior a todos los routers de sandbox:
labels:
- "traefik.http.routers.catch-all.rule=HostRegexp(`{subdomain:[a-z0-9-]+}.preview.example.com`)"
- "traefik.http.routers.catch-all.priority=1"
- "traefik.http.routers.catch-all.service=wake-service"
Toda request que no coincida con una ruta viva de sandbox cae en el catch-all, y el plano de control ejecuta la lógica de wake.
Frontera de seguridad: permisos del Docker socket
Montar el Docker socket da al plano de control poder de nivel host. Ese es el tradeoff de seguridad base de esta arquitectura: sirve para equipos internos y usuarios de confianza, no para ejecución de código no fiable.
Riesgos del Docker socket
La documentación oficial de Docker advierte que el daemon tiene superficie de ataque. Si la API de Docker se expone de forma insegura, un usuario remoto sin root puede obtener acceso root al host. Un container con /var/run/docker.sock montado puede usar Docker CLI para crear, modificar y borrar containers. También puede alcanzar filesystem y red del host mediante containers que cree.
Eso implica:
- El container del plano de control tiene permisos muy altos sobre el host
- No mezcles el plano de control con workloads no fiables en el mismo host
- El código dentro de sandbox containers puede afectar indirectamente al host si puede influir en el plano de control
Frontera para escenarios no fiables
Docker single-host + plano de control en Go encaja con:
- Entornos de preview para miembros de un equipo interno
- Coding playgrounds para usuarios de confianza
- Entornos internos de validación para AI app builders o agent platforms
No encaja con:
- Ejecución de código arbitrario de usuarios externos desconocidos
- Ejecución en producción de autonomous agents cuando el host no es de confianza
- Plataformas multi-tenant que exigen aislamiento fuerte
Si la frontera de confianza pasa de “equipo interno” a “usuarios externos”, migra a Docker Sandboxes oficiales o a microVMs tipo Firecracker. Docker Sandboxes incluye aislamiento por hypervisor, red independiente, Docker daemon independiente, filesystem independiente y aislamiento de credenciales. Cada sandbox es una microVM completa, no un container que comparte el Docker daemon del host.
Checklist de hardening para producción
Antes de producción, añade estas fronteras:
- Aislamiento de red: plano de control y sandbox containers corren en redes dedicadas, no en la red de negocio
- Autenticación de API: el quick start local puede venir sin autenticación; producción necesita token u otro mecanismo de auth
- Exposición mínima: expón preview URLs mediante Traefik, no el puerto de la API Docker
- TLS: las preview URLs deben usar certificado TLS wildcard, no HTTP en claro
- Logs y monitoring: registra requests de API del plano de control y eventos de ciclo de vida de containers, y genera alertas
Para fronteras más fuertes, compara gVisor, Firecracker y Kubernetes en un diseño de sandbox para AI agents.
Límites de recursos: memory / CPU / PIDs
Los containers Docker no tienen restricciones de recursos por defecto. Sin límites, un sandbox container puede consumir RAM y CPU del host y afectar a otras sandboxes y procesos del host. Un entorno de preview multi-tenant necesita límites duros por container.
Comportamiento por defecto de Docker
La documentación de Docker explica que los containers pueden usar recursos del host según lo permita el scheduler del kernel. Si no configuras --memory o --cpus, un container puede tomar recursos disponibles del host.
Límite duro de memoria
--memory define el máximo de memoria disponible para el container. Ejemplo:
docker run --memory="512m" --memory-swap="512m" sandbox-image
--memory-swap define el límite combinado de memoria + swap. Si --memory-swap es igual a --memory, el container no usa swap.
Si el container supera su límite de memoria, el OOM killer puede terminar procesos del container. Un OOM a nivel host también puede afectar a otros containers y procesos del host.
Límite de cuota de CPU
--cpus define cuánta capacidad de CPU puede usar un container. Ejemplo:
docker run --cpus="0.5" sandbox-image
El container puede usar como máximo 0,5 CPU. Con muchas sandboxes concurrentes, el límite de CPU evita que un container tome toda la capacidad de cómputo.
Límite de número de procesos
--pids-limit ayuda a evitar fork bombs. Ejemplo:
docker run --pids-limit=100 sandbox-image
El container puede crear como máximo 100 procesos. Después de eso, fork() falla.
Ejemplo de configuración con Compose
Con Docker Compose, configura límites bajo deploy.resources.limits:
services:
sandbox:
image: sandbox-image
deploy:
resources:
limits:
cpus: "0.5"
memory: 512M
pids: 100
deploy.resources en Compose aplica en modo Docker Swarm. En Docker de un solo host, pasa --memory, --cpus y --pids-limit manualmente, o ejecuta docker-compose --compatibility.
El plano de control debería inyectar estos parámetros al crear sandbox containers, no depender de que los usuarios los configuren a mano.
Operación: caché de imágenes y rate limits de Docker Hub
Cuando muchas sandboxes se crean y destruyen con frecuencia, los image pulls se vuelven un cuello de botella. Docker Hub tiene pull rate limits y abuse rate limits, con políticas que cambian según tipo de cuenta y plan. Producción no debería depender de tirar cada imagen de sandbox desde Docker Hub público cada vez.
Límites de Docker Hub
La documentación de Docker explica que usuarios anónimos, usuarios autenticados y cuentas de equipo tienen políticas distintas de pull rate. Al superar el límite, las requests de pull se rechazan. Los números concretos cambian con la política; revisa Docker Hub usage and limits en vez de fijarlos en el texto.
En entornos multi-sandbox, esto importa porque:
- Crear sandboxes con frecuencia puede tirar una imagen por cada inicio
- Reiniciar una sandbox tras idle cleanup puede volver a necesitar la imagen
- Varias sandboxes pueden tirar repetidamente la misma imagen
Precalentamiento de imágenes y estrategia de caché
Producción necesita varias medidas.
Precalentamiento de imágenes: tira imágenes comunes al host antes de arrancar el plano de control. Reduce la espera cuando inicia una sandbox.
Registry privado: sube imágenes comunes a un registry privado como Harbor, AWS ECR o Google Artifact Registry. El plano de control tira desde el registry privado en lugar de Docker Hub.
Login de Docker Hub: si debes tirar desde Docker Hub, usa una cuenta autenticada para obtener el allowance correspondiente. Docker recomienda iniciar sesión en producción en vez de depender de pulls anónimos.
Caché de imágenes: el Docker daemon ya cachea layers descargados. Si los sandbox containers se borran y recrean con frecuencia, evita que los jobs de limpieza borren layers útiles demasiado rápido.
Aceleración de registry interno
Si el host está en una red privada, configura mirrors o proxies de registry para timeouts de Docker pull. Trátalo como parte de la plataforma, no como un parche tras la primera caída.
Checklist de troubleshooting: la preview URL no abre
Cuando una preview URL no abre, revisa estos cinco puntos en orden.
1. ¿DNS apunta a Traefik?
Revisa el wildcard DNS. El registro A o CNAME de *.preview.example.com debe apuntar a la IP del host donde corre Traefik.
Usa dig o nslookup:
dig sandbox123.preview.example.com
La IP devuelta debe ser la IP del host de Traefik, no otra dirección.
2. ¿Traefik descubrió los labels del container?
Revisa la configuración del Docker provider de Traefik y los labels del container.
Usa el dashboard de Traefik o los logs:
docker logs traefik-container | grep "sandbox123"
Los logs de Traefik deberían mostrar que descubrió la ruta de sandbox123. Si no, revisa:
- Que exista el label
traefik.enable=true - Que
exposedByDefault: falseesté bien configurado - Que Traefik tenga montado correctamente el Docker socket
3. ¿El container está corriendo?
Revisa el estado del sandbox container.
Usa docker ps:
docker ps | grep sandbox123
Si el container está detenido, puede que lo haya parado el idle reaper o que el wake path no lo haya reiniciado. Al visitar la preview URL, el wake handler del plano de control debería arrancar el container y devolver una warming page. Si falla, revisa los logs del plano de control.
4. Dirección donde escucha la aplicación
Revisa la dirección y el puerto donde escucha la aplicación dentro del container.
Entra al container y revisa puertos:
docker exec sandbox123 netstat -tuln
La aplicación debería escuchar en 0.0.0.0:3000, no solo en 127.0.0.1:3000. La documentación de Docker indica que los puertos vinculados a 127.0.0.1 o ::1 solo son accesibles desde el Docker host; las requests externas no llegan.
Si la aplicación solo escucha en localhost, cambia su configuración o usa el modo de red adecuado.
5. Revisión de port binding
Compara el puerto de servicio de Traefik con el puerto real de la aplicación.
El label de Traefik puede decir:
- "traefik.http.services.sandbox123.loadbalancer.server.port=3000"
La aplicación dentro del container debe escuchar en el puerto 3000. Si Traefik apunta a 3000 pero la app escucha en 8080, las requests fallan.
Cuando los puertos no coinciden, corrige los labels de Traefik o la configuración de la app.
Conclusión
Docker en un solo host más un plano de control en Go puede sostener un entorno de preview autohospedado: cada sandbox tiene preview URL independiente, los recursos están acotados y el tradeoff de seguridad queda claro. El encaje es estrecho: equipos internos, usuarios de confianza y concurrencia pequeña. Cuando la frontera de confianza se abre a desconocidos externos o la concurrencia supera un host, pasa a microVMs o Kubernetes.
Los módulos centrales son directos: la tabla de decisión mantiene honesto el alcance; el plano de control, Traefik, SQLite y los reapers explican las piezas móviles; las preview URLs dependen de Host rules de Traefik y Docker labels; la seguridad trata el Docker socket como poder de nivel host; los límites de recursos son la base multi-tenant; la caché de imágenes reduce riesgo de Docker Hub; y la checklist de troubleshooting da un camino cuando fallan las preview URLs.
Siguientes pasos:
- Previews pequeñas para equipo interno: empieza con Docker single-host + plano de control en Go y mide densidad de recursos
- Usuarios externos o workloads de alto riesgo: pasa a Docker Sandboxes, Firecracker u otra frontera microVM
- Self-hosted CI Runner: lee la guía de GitHub Actions self-hosted runner para completar la infraestructura privada
- Aplicaciones dentro del entorno de preview: usa la guía de Next.js Docker self-hosting para ejecutar la app dentro de la sandbox
Crear un MVP de Dev Sandbox autohospedado
Pasar de un host Docker único a un entorno interno de preview con límites de recursos y control de acceso.
⏱️ Estimated time: 4 hr
- 1
Step 1: Elegir el modelo de aislamiento
Usa Docker en un solo host para equipos de confianza y previews pequeñas. Para usuarios desconocidos o código no fiable, usa microVMs, hosts separados o Kubernetes. - 2
Step 2: Preparar el plano de control
Ejecuta un servicio pequeño en Go que controle metadatos de sandbox, operaciones de ciclo de vida, reapers y wake-on-request. - 3
Step 3: Configurar la discovery de Traefik
Activa el Docker provider con `exposedByDefault: false` y añade labels solo a los containers sandbox que deben recibir tráfico. - 4
Step 4: Asignar preview URLs estables
Usa wildcard DNS como `*.preview.example.com` y enruta cada host `{sandbox_id}.preview.example.com` al puerto del container que sirve la aplicación. - 5
Step 5: Persistir los workspaces
Guarda cada sandbox bajo `SANDBOXED_DATA_DIR/workspaces/` o un directorio equivalente del host para que `docker stop` no borre archivos del usuario. - 6
Step 6: Añadir límites de recursos
Define límites de memoria, CPU y PIDs para cada sandbox. Un build descontrolado no debe dejar sin recursos al host. - 7
Step 7: Cerrar los puntos de entrada de producción
No expongas la API de Docker. Añade autenticación de API, TLS, control de acceso para preview links, separación de red y audit logs. - 8
Step 8: Planificar imágenes y registry
Precalienta imágenes comunes, inicia sesión en Docker Hub cuando haga falta y usa un registry privado o caché cuando crees sandboxes con frecuencia.
FAQ
¿En qué se diferencia un Dev Sandbox de Docker Compose?
¿Por qué no usar Kubernetes desde el principio?
¿El aislamiento de containers Docker sirve para ejecutar código arbitrario de desconocidos?
¿Una preview URL necesita HTTPS?
¿Se pierden los archivos después de un idle stop?
¿Los rate limits de Docker Hub pueden afectar esta arquitectura?
14 min de lectura · Publicado el: 5 jun 2026 · Actualizado el: 14 jul 2026
Guía práctica de Docker
Estás leyendo el primer artículo de esta serie. Continúa con el siguiente o abre el hub para ver toda la ruta.
Anterior
Estás al inicio de esta serie.
Siguiente
Este es el artículo más reciente de la serie por ahora.

Comentarios
Inicia sesión con GitHub para dejar un comentario