# Incidente: Docker-in-Docker roto en el runner — por qué Semgrep corre nativo (2026-08) | | | |---|---| | **Ventana del incidente** | 2026-08-15, primer intento de correr Semgrep como container aparte en `build.yaml` | | **Servicio afectado** | Gitea Actions runner (`gitea-runner`, imagen `act_runner`) | | **Impacto** | Cualquier step que intentara `docker run -v "$(pwd):/algo"` desde dentro de un job fallaba con `read-only file system` o rutas inexistentes | ## Docker-in-Docker vs. sibling containers, para quien recién arranca en CI/CD Cuando un job de CI necesita usar Docker (por ejemplo, para correr una herramienta empaquetada como imagen), hay dos formas distintas de dárselo, y confundirlas rompe cosas de maneras difíciles de diagnosticar: - **Docker-in-Docker (DinD):** el contenedor del job tiene su **propio** daemon Docker corriendo adentro, completamente aislado del daemon del host. Cuando el job hace `docker run`, ese contenedor nuevo nace *dentro* del contenedor del job, como una muñeca rusa. El filesystem que ve ese daemon interno es el del contenedor del job, no el del host real. - **Sibling containers (contenedores hermanos):** el contenedor del job **no** tiene su propio daemon — en cambio, monta el socket del daemon Docker del **host** (`/var/run/docker.sock`) y habla directamente con él. Cuando el job hace `docker run`, ese contenedor nuevo nace como **hermano** del contenedor del job (mismo nivel, mismo host, mismo daemon), no adentro suyo. La imagen `act_runner` que usa este runner de Gitea Actions usa el segundo modelo: **sibling containers**, montando el socket del daemon del host (ver `create_gitea_runner_container()` en `scripts/deploy-lab.sh`, que monta `-v /var/run/docker.sock:/var/run/docker.sock`). ## Por qué eso rompe un `docker run -v` ingenuo Con sibling containers, cuando un step dentro de un job pide ```bash docker run -v "${{ github.workspace }}:/src" alguna-imagen ``` ese comando lo ejecuta el **daemon del host**, no el contenedor del job. `${{ github.workspace }}` es una ruta que existe *dentro* del contenedor del job (por ejemplo `/workspace/devops/apps-registry`) — pero el daemon del host, al crear el contenedor hermano, intenta montar esa misma ruta **desde el filesystem del host real**, donde probablemente no existe (o existe otra cosa completamente distinta ahí). ## Síntoma Al intentar correr Semgrep como container aparte (`docker run` desde dentro del step), el error observado en el run real fue: ```text mkdir /workspace: read-only file system ``` No un "no existe la ruta" limpio — un intento de crear el directorio que falla porque, en el contexto real del daemon del host, esa ruta cae en un punto de montaje de solo lectura (o simplemente no es un lugar donde el daemon del host puede/debe escribir). ## Causa raíz Sibling containers, no Docker-in-Docker: un `docker run -v "${{ github.workspace }}:/src"` ejecutado desde dentro de un job intenta montar, en el daemon del **host**, una ruta que solo tiene sentido **dentro** del contenedor del job. El daemon del host no ve esa ruta como el mismo directorio — la ve como una ruta arbitraria de su propio filesystem. ## Fix aplicado Se evita el problema de raíz: **Semgrep corre nativo**, instalado directo en el contenedor del job (mismo criterio ya usado para Gitleaks, Trivy, Syft y Cosign — todos se instalan como binario/paquete dentro del job, ninguno corre como container aparte): ```bash python3 -m venv /tmp/semgrep-venv /tmp/semgrep-venv/bin/pip install --quiet "semgrep==1.173.0" ``` ### Por qué un venv y no `pip3 install` directo La imagen del runner ya trae paquetes de Python instalados por `apt` (por ejemplo `PyJWT`) sin metadata compatible con `pip`. Un `pip3 install --break-system-packages` sobre esa base falla al intentar reemplazarlos (`RECORD file not found`) porque `pip` no encuentra el registro de archivos que necesita para saber qué está reemplazando. Un venv aislado evita tocar los paquetes del sistema por completo — Semgrep y sus dependencias quedan en su propio directorio, sin interferir con nada que `apt` ya haya instalado. ## Validación Validado localmente contra `docker.gitea.com/runner-images:ubuntu-latest` (la misma imagen que usa el runner real) que `python3`/`pip3` están disponibles, y confirmado en una corrida real posterior al fix que Semgrep corre y reporta hallazgos sin ningún error de Docker de por medio.