«En mi máquina funciona» sigue siendo una de las frases más costosas de un equipo de
desarrollo web: versiones distintas de Node o PHP según el puesto, una extensión de editor
que falta, una variable de entorno olvidada. La especificación Development Containers,
impulsada en buena parte por Microsoft y ya implementada por varios proveedores de
herramientas, responde a este problema describiendo el entorno de desarrollo completo —
runtime, herramientas, extensiones, puertos — en un archivo versionado junto con el código.
El problema que resuelven los DevContainers
Un proyecto web suele depender de una lista precisa de requisitos: una versión de runtime
(Node, PHP, Python), herramientas de línea de comandos, a veces una base de datos local,
extensiones de editor compartidas para el linting o el formateo. Documentar esa lista en un
README funciona durante un tiempo, hasta que el documento se desvía de la
realidad. Las
herramientas de línea de comandos propias de cada proyecto
acaban variando de una máquina a otra sin que nadie lo note hasta que aparece un error difícil
de reproducir.
Un DevContainer traslada esa documentación de un texto que hay que leer a una configuración
que el editor ejecuta directamente: construye un contenedor, instala ahí las herramientas
previstas, abre la carpeta del proyecto y expone los puertos necesarios. La persona que se
incorpora al equipo ya no instala nada en su máquina anfitriona, aparte de Docker y su editor.
Anatomía de un devcontainer.json
La configuración vive en .devcontainer/devcontainer.json, en la raíz del
repositorio. Un ejemplo para un proyecto Node.js con PostgreSQL:
{
"name": "app-web-node",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
"features": {
"ghcr.io/devcontainers/features/node:1": { "version": "22" }
},
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
],
"settings": {
"editor.formatOnSave": true
}
}
},
"forwardPorts": [3000, 5432],
"postCreateCommand": "npm install"
}
Las «features» son piezas reutilizables que instalan un runtime o una herramienta (Node,
PHP, Docker-in-Docker, GitHub CLI) sin escribir un Dockerfile a mano. El bloque
customizations.vscode instala automáticamente las extensiones y los ajustes del
equipo, para que el linting y el formateo sean idénticos para todos desde el momento en que se
abre el proyecto.
Qué herramientas soportan la especificación
| Herramienta | Soporte | Nota |
|---|---|---|
| Visual Studio Code | Nativo (extensión Dev Containers) | Implementación de referencia original de la especificación |
| GitHub Codespaces | Nativo | Lee devcontainer.json directamente para aprovisionar la instancia en la nube |
| JetBrains (IntelliJ, PhpStorm, WebStorm) | Nativo desde 2024 | Mismos archivos, sin conversión |
CLI devcontainer | Nativo | Construye y ejecuta el contenedor fuera del editor, útil en CI |
| Docker Compose solo | Compatible como complemento | Un DevContainer puede apoyarse en un docker-compose.yml existente |
Poner en marcha un DevContainer en un proyecto existente
En un proyecto que ya funciona con Docker Compose, la migración se resume en tres pasos:
añadir una carpeta .devcontainer, apuntarla al archivo compose existente en lugar
de escribir uno nuevo, y declarar los puertos y el comando posterior a la creación. Para un
equipo que alterna entre varias ramas con
varios worktrees de Git en paralelo,
cada worktree puede abrir su propio DevContainer aislado, con sus propios puertos y su propio
estado de base de datos, sin interferir con las demás copias de trabajo.
Los secretos nunca deben escribirse en devcontainer.json, que se versiona junto
con el resto del repositorio. Se pasan al contenedor a través de las variables de entorno del
anfitrión (un campo remoteEnv o containerEnv que apunta a una
variable no versionada), siguiendo la misma lógica que
gestionar bien las variables de entorno en desarrollo web.
Un entorno de desarrollo documentado en un archivo de texto se desvía con el tiempo; uno
descrito en una configuración ejecutada no puede divergir del proyecto que lo define.
Límites y trampas que conviene conocer
En macOS y Windows, el rendimiento de E/S de disco de un volumen Docker montado desde el
anfitrión sigue siendo notablemente más lento que en un sistema de archivos Linux nativo. Para
un proyecto Node.js con una carpeta node_modules voluminosa, preferir un volumen
Docker nombrado frente a un montaje directo de la carpeta anfitriona reduce claramente el
impacto en los tiempos de build y de recarga en caliente.
Las imágenes base cargadas de herramientas preinstaladas (varios gigabytes) ralentizan la
primera apertura del proyecto y la actualización de la imagen en CI. Es mejor partir de una
imagen mínima y añadir solo las features realmente utilizadas. Por último, un DevContainer
estandariza el entorno de desarrollo, no el de producción: ejecutar las mismas imágenes en CI
mantiene la coherencia de las pruebas, pero el despliegue sigue siendo un tema aparte, con sus
propias restricciones de tamaño y seguridad de imagen.
| Enfoque | Alcance | Reproducibilidad |
|---|---|---|
| README con instrucciones manuales | Solo documentación | Baja: se desvía con el tiempo |
| Docker Compose solo | Servicios y dependencias | Buena para servicios, muda sobre el editor y sus extensiones |
| Vagrant | Máquina virtual completa | Buena, pero más pesada de aprovisionar que un contenedor |
| DevContainer | Runtime, herramientas, editor, puertos | Alta: un único archivo versionado pilota el editor |
Usar el mismo DevContainer en integración continua
La especificación también incluye una interfaz de línea de comandos, devcontainer,
que construye y ejecuta el mismo contenedor fuera de cualquier editor. Un pipeline de CI puede
así ejecutar las pruebas en el entorno exacto que usa el equipo en local, sin mantener una
segunda definición de imagen en paralelo:
npm install -g @devcontainers/cli
devcontainer up --workspace-folder .
devcontainer exec --workspace-folder . -- npm test
Este enfoque elimina toda una clase de regresiones causadas por la desviación entre el
entorno de desarrollo y el de CI: una dependencia del sistema instalada a mano en la máquina de
un desarrollador, y luego olvidada en la imagen que usa el pipeline, deja de ser un problema en
cuanto ambos entornos comparten la misma definición. La ganancia es clara en un proyecto PHP
que depende de extensiones nativas concretas, o en un proyecto Node.js que incluye módulos
compilados: la versión de la biblioteca de sistema subyacente ya no varía según la máquina que
ejecute el build.
La contrapartida es un tiempo de arranque del pipeline algo mayor que el de una imagen de CI
reducida al mínimo para ejecutar pruebas, ya que el contenedor también incluye las herramientas
y extensiones pensadas para el editor. Para los proyectos donde ese coste pesa en la duración
total del pipeline, una imagen de CI derivada de la misma base pero sin las capas específicas
del editor sigue siendo una opción razonable, siempre que se regenere automáticamente con cada
cambio del Dockerfile de origen en lugar de mantenerse a mano.
Lo que hay que recordar
La especificación Development Containers traslada la configuración del entorno de
desarrollo de una documentación que hay que mantener a un archivo que el editor ejecuta
realmente. Ahora cuenta con soporte nativo en VS Code, los IDE de JetBrains y GitHub
Codespaces, lo que la convierte en un estándar realista y no en una solución propietaria. Su
principal límite sigue siendo el rendimiento de E/S de disco en macOS y Windows, mejor resuelto
con volúmenes nombrados que con montajes directos de la carpeta anfitriona.
En los proyectos que retoma para clientes, el autor observa casi siempre el mismo
escenario: un README de instalación fechado hace varios meses, ya equivocado en al menos un
punto. Un DevContainer no lo resuelve todo, pero traslada la carga de la documentación a una
configuración que el editor comprueba cada vez que se abre el proyecto, lo que en la práctica
ahorra bastante ida y vuelta de depuración el primer día de un nuevo contrato —
Simon Janvier.
Para profundizar: la especificación completa en
containers.dev.
