devAlice
← Multi-OS

Dev Container — un entorno de desarrollo unificado para Mac / Windows / Linux

Usa VS Code Dev Containers para eliminar las diferencias entre sistemas operativos. Un entorno de desarrollo reproducible basado en Docker, definido por un único devcontainer.json que comparte todo el equipo.

«Funciona en mi máquina» casi siempre tiene su origen en diferencias entre sistemas operativos. Mac tiene brew y Apple Silicon, Windows tiene PowerShell y UTF-16, Linux tiene systemd. Aunque todos instalen la misma versión de Node y los mismos paquetes, siempre aparecen discrepancias sutiles.

Los Dev Containers (VS Code Dev Containers + el estándar devcontainer.json) encapsulan las diferencias de SO dentro de un contenedor Docker. Todo el equipo trabaja en el mismo entorno Linux; solo el IDE se ejecuta en el host. Esta guía configura un único dev container que funciona de forma idéntica en hosts Mac, Windows y Linux.

TL;DR

  1. Instala un runtime de Docker (Mac: OrbStack/Docker Desktop, Win: Docker Desktop/WSL Docker, Linux: docker)
  2. VS Code + la extensión Dev Containers (ms-vscode-remote.remote-containers)
  3. Coloca un .devcontainer/devcontainer.json en la raíz del proyecto
  4. Cmd/Ctrl+Shift+PDev Containers: Reopen in Container — el IDE se reinicia dentro del contenedor
  5. Un compañero de equipo: clona → ejecuta ese comando una vez → entorno idéntico

Requisitos previos

1. Por qué usar Dev Containers

Problemas que resuelve

  • «Funciona en mi máquina» — dentro del contenedor, el SO y la cadena de herramientas son idénticos
  • Incorporación rápida — los nuevos compañeros no leen un README de 30 líneas; hacen clic una vez
  • Conflictos de versión — Proyecto A en Node 18, B en Node 22, aislados sin mise/nvm en el host
  • Problemas de compilación específicos del SO — evita los problemas ARM de Apple Silicon, las rutas largas de Windows, etc.
  • Paridad con CI — usa la misma imagen base que tu flujo de trabajo de GitHub Actions y la brecha desaparece

Puntos débiles de Dev Containers

  • Desarrollo de apps con GUI — las compilaciones nativas de Mac o Windows no encajan en un contenedor
  • Cargas de trabajo con GPU — el paso de GPU es posible, pero ejecutar en el host resulta más estable
  • Máquinas de bajo rendimiento — Docker consume 1–2 GB adicionales de RAM (aceptable con 16 GB)

2. devcontainer.json mínimo

.devcontainer/devcontainer.json:

{
  "name": "My Project Dev",
  "image": "mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm",
  "features": {
    "ghcr.io/devcontainers/features/git:1": {},
    "ghcr.io/devcontainers/features/github-cli:1": {}
  },
  "postCreateCommand": "npm install",
  "customizations": {
    "vscode": {
      "extensions": [
        "dbaeumer.vscode-eslint",
        "esbenp.prettier-vscode"
      ]
    }
  },
  "forwardPorts": [3000, 5173],
  "remoteUser": "node"
}

Campos clave:

CampoFunción
imageImagen base de Docker (Node / Python / Go / Rust, etc.)
featuresHerramientas adicionales (git, gh, docker-in-docker, awscli — seleccionables desde un catálogo)
postCreateCommandSe ejecuta una vez tras crear el contenedor (instalar dependencias, etc.)
customizations.vscode.extensionsExtensiones de VS Code que se instalan automáticamente dentro del contenedor
forwardPortsPuertos expuestos al host
remoteUserUsuario dentro del contenedor (se recomienda no usar root por seguridad)

3. Primera ejecución

Paleta de comandos de VS Code (Cmd/Ctrl+Shift+P) → Dev Containers: Reopen in Container.

  • Primera vez: ~1–3 minutos (descarga de imagen + postCreateCommand)
  • La esquina inferior izquierda de VS Code muestra Dev Container: My Project Dev
# Terminal dentro del contenedor (Terminal de VS Code)
uname -a
# Linux 1234abcd 6.10.x #1 SMP Debian ...
 
which node
# /usr/local/share/nvm/versions/node/v22.x/bin/node

El mismo entorno Linux, independientemente del SO del host.


4. Elegir una imagen base

El campo image: en devcontainer.json. Catálogo oficial:

Lenguaje / stackImagen
Node.jsmcr.microsoft.com/devcontainers/javascript-node:1-{18,20,22}-bookworm
Pythonmcr.microsoft.com/devcontainers/python:1-{3.11,3.12,3.13}-bookworm
Gomcr.microsoft.com/devcontainers/go:1-{1.22,1.23}
Rustmcr.microsoft.com/devcontainers/rust:1-bookworm
Javamcr.microsoft.com/devcontainers/java:1-{17,21}-bookworm
.NETmcr.microsoft.com/devcontainers/dotnet:1-9.0-bookworm
Universal (políglota)mcr.microsoft.com/devcontainers/universal:2-linux

Recomendación: proyecto de un único lenguaje → imagen de ese lenguaje. Proyecto políglota → imagen base + features.

O escribe tu propio Dockerfile:

{
  "name": "My Custom Dev",
  "build": {
    "dockerfile": "Dockerfile",
    "context": ".."
  }
}

5. features — añade una herramienta con una línea

El bloque features usa módulos del catálogo oficial (https://containers.dev/features). Con una sola línea, la herramienta queda instalada.

Opciones habituales:

"features": {
  "ghcr.io/devcontainers/features/git:1": {},
  "ghcr.io/devcontainers/features/github-cli:1": {},
  "ghcr.io/devcontainers/features/docker-in-docker:2": {},   // ejecuta docker dentro del contenedor
  "ghcr.io/devcontainers/features/aws-cli:1": {},
  "ghcr.io/devcontainers/features/terraform:1": {},
  "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {}
}

6. Docker-in-Docker — contenedores dentro del contenedor

Cuando necesitas ejecutar docker run dentro del dev container (pruebas con docker-compose, verificación de compilaciones):

"features": {
  "ghcr.io/devcontainers/features/docker-in-docker:2": {
    "version": "latest",
    "dockerDashComposeVersion": "v2"
  }
}

O comparte el socket de Docker del host (más ligero, pero con menor aislamiento de seguridad):

"mounts": [
  "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
]

7. Montajes de archivos host ↔ contenedor

La carpeta de trabajo se monta automáticamente en /workspaces/{proyecto}.

Montajes adicionales:

"mounts": [
  "source=${localEnv:HOME}/.aws,target=/home/node/.aws,type=bind,readonly",
  "source=${localEnv:HOME}/.gitconfig,target=/home/node/.gitconfig,type=bind"
]

Para el rendimiento de montajes en macOS, consulta docker-setup §5.2 (se recomienda VirtioFS).


8. Variables de entorno y secretos

Variables de entorno estáticas

"containerEnv": {
  "NODE_ENV": "development",
  "LOG_LEVEL": "debug"
}

Secretos (nunca en el repositorio)

Monta un archivo .env o pásalo mediante localEnv:

"containerEnv": {
  "GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}",
  "OPENAI_API_KEY": "${localEnv:OPENAI_API_KEY}"
}

Las variables de entorno del shell del host se inyectan en el contenedor. Asegúrate de que .env esté en .gitignore.


9. Multi-contenedor (integración con docker-compose)

Para dependencias como base de datos o Redis:

.devcontainer/docker-compose.yml:

services:
  app:
    image: mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm
    volumes:
      - ..:/workspaces/myapp
    command: sleep infinity
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: dev
    volumes:
      - db-data:/var/lib/postgresql/data
volumes:
  db-data:

.devcontainer/devcontainer.json:

{
  "dockerComposeFile": "docker-compose.yml",
  "service": "app",
  "workspaceFolder": "/workspaces/myapp"
}

VS Code se conecta al contenedor app; db arranca automáticamente.


10. Mismo entorno que CI — devcontainer-cli

Reutiliza devcontainer.json en CI:

npm install -g @devcontainers/cli
 
# Compila el contenedor y ejecuta un comando
devcontainer up --workspace-folder .
devcontainer exec --workspace-folder . npm test

GitHub Actions:

# .github/workflows/test.yml
- name: Test in dev container
  uses: devcontainers/ci@v0.3
  with:
    runCmd: npm test

CI en verde = mismo resultado que en local.


11. Verificación

# Host
docker --version
 
# Dentro del contenedor (Terminal de VS Code)
uname -a                # Linux ...
which node              # /usr/local/share/...
echo $NODE_ENV          # development (containerEnv aplicado)
node --version          # la versión de Node del contenedor
git --version           # instalada mediante features

Las cinco respuestas correctas dentro del contenedor = configuración completada.


12. Resolución de problemas

«Failed to connect» / el contenedor no arranca

  • ¿Está el runtime de Docker activo? (barra de menú de Docker Desktop, orb status, colima status)
  • docker ps para detectar colisiones de puertos o nombres
  • VS Code → Paleta de comandos → «Dev Containers: Rebuild Without Cache»

Las compilaciones tardan demasiado

  • La primera compilación descarga la imagen base (varios GB) — es lo esperado
  • Las siguientes usan la caché. postCreateCommand no se cachea (se ejecuta en cada compilación)
  • Distingue onCreateCommand (solo la primera vez) de postCreateCommand (en cada nueva compilación)

Permiso denegado en archivos

  • El UID del contenedor y el del host no coinciden (habitual en hosts Linux)
  • Establece remoteUser junto con updateRemoteUserUID:
    "remoteUser": "vscode",
    "updateRemoteUserUID": true

forwardPorts no funciona

  • ¿La app dentro del contenedor escucha en 0.0.0.0 o :: (no en 127.0.0.1)?
  • Ejemplo: Next.js necesita next dev -H 0.0.0.0

Git config del host no se detecta

  • El montaje automático de .gitconfig es opcional; añádelo explícitamente en mounts
  • O bien usa el git instalado mediante features, que copia el gitconfig del host

El IDE es lento con node_modules muy grandes

  • Excluye node_modules del watcher de VS Code (files.watcherExclude)
  • O coloca node_modules en un volumen propio del contenedor, en lugar de un montaje del host:
    "mounts": [
      "source=${localWorkspaceFolderBasename}-node-modules,target=/workspaces/${localWorkspaceFolderBasename}/node_modules,type=volume"
    ]

13. Siguientes pasos


Referencias

Historial de cambios

  • 2026-05-16: Primer borrador. Fundamentos de devcontainer.json · selección de imagen base · features · integración con docker-compose · integración con CI · seis casos de resolución de problemas.