Dev Containers para Entornos de Desarrollo Reproducibles

Los Dev Containers permiten definir entornos de desarrollo completos en un fichero de configuración, garantizando que todos los miembros del equipo trabajen con exactamente las mismas herramientas, versiones y dependencias independientemente de su sistema operativo. Con soporte nativo en VS Code, GitHub Codespaces y la CLI oficial, son la solución más efectiva para eliminar el clásico problema de "en mi máquina funciona".

Requisitos Previos

  • Docker instalado en el sistema anfitrión
  • VS Code con la extensión "Dev Containers" (ms-vscode-remote.remote-containers)
  • O la CLI oficial de Dev Containers
  • Git para clonar repositorios

Estructura de devcontainer.json

El fichero .devcontainer/devcontainer.json es el corazón de la configuración:

{
  "name": "Entorno de Desarrollo Node.js",

  // Imagen base del contenedor
  "image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bullseye",

  // Características adicionales a instalar
  "features": {
    "ghcr.io/devcontainers/features/git:1": {
      "version": "latest",
      "ppa": false
    },
    "ghcr.io/devcontainers/features/docker-in-docker:2": {
      "version": "latest",
      "dockerDashComposeVersion": "v2"
    },
    "ghcr.io/devcontainers/features/node:1": {
      "version": "20"
    }
  },

  // Extensiones de VS Code a instalar automáticamente
  "customizations": {
    "vscode": {
      "extensions": [
        "esbenp.prettier-vscode",
        "dbaeumer.vscode-eslint",
        "ms-vscode.vscode-typescript-next",
        "eamodio.gitlens",
        "ms-azuretools.vscode-docker"
      ],
      "settings": {
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "esbenp.prettier-vscode",
        "terminal.integrated.defaultProfile.linux": "bash"
      }
    }
  },

  // Puertos a exponer automáticamente
  "forwardPorts": [3000, 8080, 5432],

  // Etiquetar los puertos expuestos
  "portsAttributes": {
    "3000": {
      "label": "Aplicación web",
      "onAutoForward": "notify"
    },
    "5432": {
      "label": "PostgreSQL",
      "onAutoForward": "silent"
    }
  },

  // Comando que se ejecuta tras crear el contenedor
  "postCreateCommand": "npm install && npm run prepare",

  // Comando que se ejecuta al iniciar el contenedor
  "postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",

  // Variables de entorno para el contenedor
  "containerEnv": {
    "NODE_ENV": "development"
  },

  // Montar el agente SSH del anfitrión
  "mounts": [
    "source=${localEnv:SSH_AUTH_SOCK},target=/ssh-agent,type=bind,consistency=delegated"
  ],

  // Variables de entorno del agente SSH
  "remoteEnv": {
    "SSH_AUTH_SOCK": "/ssh-agent"
  }
}

Features: Herramientas Preconfiguradas

Los Features son componentes reutilizables que añaden herramientas al contenedor:

{
  "name": "Entorno Backend Python + Go",
  "image": "mcr.microsoft.com/devcontainers/base:ubuntu-22.04",

  "features": {
    // Python con versión específica
    "ghcr.io/devcontainers/features/python:1": {
      "version": "3.11",
      "installTools": true
    },

    // Go
    "ghcr.io/devcontainers/features/go:1": {
      "version": "1.22"
    },

    // kubectl y Helm para trabajo con Kubernetes
    "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {
      "version": "latest",
      "helm": "latest",
      "minikube": "none"
    },

    // Terraform para infraestructura como código
    "ghcr.io/devcontainers/features/terraform:1": {
      "version": "latest",
      "tflint": "latest"
    },

    // AWS CLI
    "ghcr.io/devcontainers/features/aws-cli:1": {},

    // GitHub CLI
    "ghcr.io/devcontainers/features/github-cli:1": {}
  }
}

Crear tu propio Feature personalizado:

# Estructura de un Feature personalizado
mkdir -p .devcontainer/features/mi-herramienta/
tee .devcontainer/features/mi-herramienta/devcontainer-feature.json << 'EOF'
{
  "id": "mi-herramienta",
  "version": "1.0.0",
  "name": "Mi Herramienta Personalizada",
  "description": "Instala herramientas específicas del equipo",
  "options": {
    "version": {
      "type": "string",
      "default": "latest",
      "description": "Versión a instalar"
    }
  }
}
EOF

tee .devcontainer/features/mi-herramienta/install.sh << 'EOF'
#!/bin/bash
# Script de instalación del Feature

VERSION=${VERSION:-latest}

# Instalar dependencias del sistema
apt-get update && apt-get install -y jq curl

# Instalar la herramienta
curl -fsSL "https://ejemplo.com/install.sh" | bash -s -- "$VERSION"

echo "Mi herramienta instalada correctamente"
EOF

chmod +x .devcontainer/features/mi-herramienta/install.sh

Integración con Docker Compose

Para entornos con múltiples servicios (base de datos, Redis, etc.):

# .devcontainer/docker-compose.yml
version: "3.8"

services:
  app:
    build:
      context: ..
      dockerfile: .devcontainer/Dockerfile
    volumes:
      - ..:/workspace:cached
      - ~/.gitconfig:/root/.gitconfig:ro
    command: sleep infinity
    environment:
      DATABASE_URL: postgresql://postgres:postgres@db:5432/devdb
      REDIS_URL: redis://redis:6379

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: devdb
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    restart: unless-stopped

  mailhog:
    image: mailhog/mailhog
    # Captura emails en desarrollo
    ports:
      - "8025:8025"

volumes:
  postgres_data:
// .devcontainer/devcontainer.json con Docker Compose
{
  "name": "Entorno Full Stack",
  "dockerComposeFile": "docker-compose.yml",
  "service": "app",
  "workspaceFolder": "/workspace",

  "features": {
    "ghcr.io/devcontainers/features/node:1": {"version": "20"},
    "ghcr.io/devcontainers/features/git:1": {}
  },

  "customizations": {
    "vscode": {
      "extensions": ["ms-azuretools.vscode-docker"]
    }
  },

  "forwardPorts": [3000, 5432, 6379, 8025],

  "postCreateCommand": "npm install && npm run db:migrate",

  "shutdownAction": "stopCompose"
}
# .devcontainer/Dockerfile
FROM mcr.microsoft.com/devcontainers/javascript-node:1-20-bullseye

# Instalar herramientas adicionales del sistema
RUN apt-get update && apt-get install -y \
    postgresql-client \
    redis-tools \
    && rm -rf /var/lib/apt/lists/*

# Instalar herramientas globales de Node
RUN npm install -g \
    typescript \
    ts-node \
    @prisma/cli \
    knex

Uso con VS Code

# Abrir el repositorio en VS Code
code /ruta/al/proyecto

# VS Code detectará el .devcontainer/ automáticamente y mostrará
# una notificación para "Reabrir en contenedor"

# También puedes usar el Command Palette:
# Ctrl+Shift+P → "Dev Containers: Reopen in Container"

# Para ver los logs de construcción del contenedor:
# Ctrl+Shift+P → "Dev Containers: Show Container Log"

# Para reconstruir el contenedor (tras cambios en devcontainer.json):
# Ctrl+Shift+P → "Dev Containers: Rebuild Container"

Atajos útiles dentro del Dev Container:

# Dentro del contenedor, ejecutar tareas
# El terminal de VS Code ya está dentro del contenedor

# Verificar que estás dentro del contenedor
cat /etc/hostname  # Mostrará el ID del contenedor

# Ver los servicios disponibles (si usas Docker Compose)
docker ps

# Conectar a la base de datos
psql $DATABASE_URL

# Ejecutar migraciones
npm run db:migrate

Dev Container CLI

Para usar Dev Containers sin VS Code:

# Instalar la CLI
npm install -g @devcontainers/cli

# Verificar la instalación
devcontainer --version

# Construir e iniciar el contenedor
cd /ruta/al/proyecto
devcontainer up --workspace-folder .

# Ejecutar un comando dentro del contenedor
devcontainer exec --workspace-folder . npm test

# Ejecutar una shell interactiva
devcontainer exec --workspace-folder . bash

# Parar y eliminar el contenedor
devcontainer down --workspace-folder .

# Construir sin iniciar (útil para CI/CD)
devcontainer build --workspace-folder . \
  --image-name mi-devcontainer:latest

Integración en pipelines CI/CD:

# .github/workflows/ci.yml - Pruebas dentro del Dev Container
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Instalar Dev Container CLI
        run: npm install -g @devcontainers/cli

      - name: Ejecutar pruebas en el Dev Container
        run: |
          devcontainer up --workspace-folder .
          devcontainer exec --workspace-folder . npm test

Estandarización del Equipo

Buenas prácticas para usar Dev Containers en equipo:

// Configuración orientada al equipo
{
  "name": "Proyecto MiEmpresa",

  // Versionar la imagen para reproducibilidad exacta
  "image": "mcr.microsoft.com/devcontainers/javascript-node:1-20.12-bullseye",

  // Documentar las variables de entorno requeridas
  "containerEnv": {
    "NODE_ENV": "development",
    "TZ": "Europe/Madrid"
  },

  // Tarea de bienvenida al abrir el proyecto
  "postCreateCommand": {
    "instalar": "npm install",
    "preparar": "npm run prepare",
    "mensaje": "echo '\\n✓ Entorno listo. Ejecuta: npm run dev\\n'"
  },

  // Extensiones obligatorias para el proyecto
  "customizations": {
    "vscode": {
      "extensions": [
        // Linting y formato - obligatorias
        "esbenp.prettier-vscode",
        "dbaeumer.vscode-eslint",
        // Colaboración
        "ms-vsliveshare.vsliveshare"
      ]
    }
  }
}
# Añadir guía de inicio rápido en el README
# Asegúrate de documentar los requisitos previos del equipo:

# 1. Instalar Docker Desktop (Windows/Mac) o Docker Engine (Linux)
# 2. Instalar VS Code + extensión "Dev Containers"
# 3. Clonar el repositorio
# 4. Abrir en VS Code → aceptar "Reabrir en contenedor"
# 5. Esperar a que se construya el contenedor (primera vez: ~5 minutos)

Solución de Problemas

El contenedor no se construye:

# Ver los logs detallados de construcción
# En VS Code: Ctrl+Shift+P → "Dev Containers: Show Container Log"

# Desde la CLI
devcontainer up --workspace-folder . --log-level debug 2>&1 | tail -50

Cambios en devcontainer.json no se aplican:

# Forzar reconstrucción completa del contenedor
# VS Code: Ctrl+Shift+P → "Dev Containers: Rebuild Container Without Cache"

# CLI:
devcontainer up --workspace-folder . --remove-existing-container

Permisos de ficheros incorrectos:

# El usuario dentro del contenedor suele ser 'vscode' con UID 1000
# Si los ficheros montados tienen permisos incorrectos:
sudo chown -R 1000:1000 /ruta/al/proyecto

# O ajustar en devcontainer.json:
# "remoteUser": "root"  (solo para depuración)

Conclusión

Los Dev Containers eliminan la fricción de configurar entornos de desarrollo al codificar toda la infraestructura necesaria en un fichero de configuración versionado. La integración con Docker Compose permite replicar entornos de producción completos localmente, mientras que la compatibilidad con GitHub Codespaces facilita el trabajo en la nube cuando es necesario. Adoptar Dev Containers es especialmente valioso en equipos grandes donde la consistencia del entorno impacta directamente en la productividad.