--- title: "Cómo usar Dev Containers en Ubuntu 24.04 con Docker" description: "Crea entornos de desarrollo reproducibles con Dev Containers en Ubuntu 24.04: devcontainer.json, features, Compose con PostgreSQL, la CLI oficial y VS Code." category: "Herramientas de Desarrollo" language: es source: https://cubepath.com/es/docs/herramientas-de-desarrollo/devcontainers-para-desarrollo-reproducible updated: 2026-09-25 --- # Cómo usar Dev Containers en Ubuntu 24.04 con Docker Dev Containers es una especificación abierta para describir un entorno de desarrollo completo (imagen base, herramientas, servicios, extensiones del editor) en un fichero `.devcontainer/devcontainer.json` dentro del repositorio. Cualquier persona que clone el proyecto obtiene el mismo entorno en un contenedor, con las mismas versiones, sin instalar nada más que Docker. En este tutorial prepararás un servidor Ubuntu 24.04 como máquina de desarrollo, crearás un Dev Container para un proyecto Node.js, le añadirás una base de datos PostgreSQL con Docker Compose y lo usarás tanto desde la CLI oficial como desde VS Code por SSH. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor o equipo con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 2 vCPU y 4 GB de RAM (la primera construcción de imágenes consume bastante). - Un usuario no root con privilegios `sudo`. - Docker Engine y el plugin Docker Compose instalados desde el repositorio oficial de Docker, con tu usuario en el grupo `docker`. - Opcional, para el paso 6: VS Code en tu equipo local y acceso SSH al servidor. Comprueba que Docker funciona sin `sudo`: ```bash docker run --rm hello-world ``` ## Paso 1: Instalar Node.js y la CLI de Dev Containers La CLI de referencia de la especificación, `@devcontainers/cli`, es la misma que usa VS Code por debajo y se distribuye como paquete npm. Ubuntu 24.04 trae Node.js 18, que ya está fuera de soporte, así que instala Node.js 22 LTS desde el repositorio de NodeSource: ```bash sudo apt update sudo apt install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list sudo apt update sudo apt install -y nodejs ``` Instala la CLI de forma global: ```bash sudo npm install -g @devcontainers/cli ``` Comprueba ambas versiones: ```bash node --version devcontainer --version ``` ```output v22.x.x 0.x.x ``` ## Paso 2: Crear un Dev Container básico Crea un proyecto de ejemplo con su directorio `.devcontainer`: ```bash mkdir -p ~/demo/.devcontainer cd ~/demo ``` Crea el fichero de configuración: ```bash nano ~/demo/.devcontainer/devcontainer.json ``` ```json { "name": "demo", "image": "mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm", "forwardPorts": [3000], "postCreateCommand": "node --version && npm --version", "containerEnv": { "NODE_ENV": "development" }, "customizations": { "vscode": { "extensions": [ "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } } } ``` Lo que define cada propiedad: - `image`: la imagen base. Las imágenes de `mcr.microsoft.com/devcontainers/` ya incluyen un usuario no root (`node` en este caso) con `sudo`, Git y utilidades comunes. La etiqueta `1-22-bookworm` fija la versión mayor de la imagen, Node.js 22 y Debian 12. - `forwardPorts`: puertos del contenedor que el editor reenvía a tu equipo. - `postCreateCommand`: se ejecuta una sola vez, al crear el contenedor. Es el sitio para `npm install` o migraciones. - `containerEnv`: variables de entorno del contenedor. - `customizations.vscode.extensions`: extensiones que VS Code instala dentro del contenedor. La CLI las ignora. Levanta el contenedor: ```bash devcontainer up --workspace-folder . ``` La primera vez descarga la imagen. Al terminar, la última línea es un JSON con el resultado: ```output {"outcome":"success","containerId":"5d1f0c...","remoteUser":"node","remoteWorkspaceFolder":"/workspaces/demo"} ``` El proyecto está montado en `/workspaces/demo`. Ejecuta comandos dentro del contenedor con `devcontainer exec`: ```bash devcontainer exec --workspace-folder . node --version devcontainer exec --workspace-folder . whoami ``` ```output v22.x.x node ``` Para abrir una shell interactiva, usa `devcontainer exec --workspace-folder . bash`. ## Paso 3: Añadir herramientas con features Las features son paquetes reutilizables que instalan una herramienta en cualquier imagen base, sin escribir un Dockerfile. Se publican como artefactos OCI; las oficiales están en `ghcr.io/devcontainers/features/`. Añade GitHub CLI y acceso al Docker del anfitrión editando `devcontainer.json`: ```bash nano ~/demo/.devcontainer/devcontainer.json ``` ```json { "name": "demo", "image": "mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm", "features": { "ghcr.io/devcontainers/features/github-cli:1": {}, "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {} }, "forwardPorts": [3000], "postCreateCommand": "node --version && npm --version", "containerEnv": { "NODE_ENV": "development" }, "customizations": { "vscode": { "extensions": [ "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } } } ``` `docker-outside-of-docker` monta el socket de Docker del anfitrión, de modo que los comandos `docker` dentro del contenedor gestionan los contenedores del servidor. Es más ligero que `docker-in-docker`, pero da al contenedor control total sobre Docker del anfitrión: úsalo solo en máquinas de desarrollo. Los cambios en `devcontainer.json` no se aplican a un contenedor existente. Recréalo: ```bash devcontainer up --workspace-folder . --remove-existing-container ``` Verifica que las herramientas están disponibles: ```bash devcontainer exec --workspace-folder . gh --version devcontainer exec --workspace-folder . docker ps ``` El segundo comando debe listar los contenedores del servidor, incluido el propio Dev Container. ## Paso 4: Añadir PostgreSQL con Docker Compose Casi cualquier aplicación real necesita servicios auxiliares. Cuando `devcontainer.json` apunta a un fichero Compose, la CLI levanta todos los servicios y te conecta al que indiques. Primero, crea un Dockerfile para el contenedor de trabajo con el cliente de PostgreSQL: ```bash nano ~/demo/.devcontainer/Dockerfile ``` ```dockerfile FROM mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm RUN apt-get update \ && apt-get install -y --no-install-recommends postgresql-client \ && rm -rf /var/lib/apt/lists/* ``` Crea el fichero Compose: ```bash nano ~/demo/.devcontainer/compose.yaml ``` ```yaml services: app: build: context: . dockerfile: Dockerfile volumes: - ..:/workspaces/demo:cached command: sleep infinity environment: DATABASE_URL: postgresql://app:your_dev_password@db:5432/app depends_on: - db db: image: postgres:17 restart: unless-stopped environment: POSTGRES_USER: app POSTGRES_PASSWORD: your_dev_password POSTGRES_DB: app volumes: - pgdata:/var/lib/postgresql/data volumes: pgdata: ``` Sustituye `your_dev_password` por una contraseña propia en los dos sitios. Aspectos importantes: - `..:/workspaces/demo` monta la raíz del proyecto (el directorio padre de `.devcontainer`) en el contenedor. - `command: sleep infinity` mantiene vivo el contenedor `app`; sin un proceso en primer plano se detendría al arrancar. - El servicio `db` no publica puertos en el servidor: `app` lo alcanza por su nombre en la red interna de Compose. - El volumen `pgdata` conserva los datos aunque recrees el entorno. Sustituye `devcontainer.json` para que use Compose en lugar de una imagen: ```bash nano ~/demo/.devcontainer/devcontainer.json ``` ```json { "name": "demo", "dockerComposeFile": "compose.yaml", "service": "app", "workspaceFolder": "/workspaces/demo", "shutdownAction": "stopCompose", "remoteUser": "node", "features": { "ghcr.io/devcontainers/features/github-cli:1": {} }, "forwardPorts": [3000], "postCreateCommand": "psql --version", "customizations": { "vscode": { "extensions": [ "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] } } } ``` `service` indica en qué contenedor se trabaja y `shutdownAction: stopCompose` hace que VS Code pare todos los servicios al cerrar la ventana. La feature `docker-outside-of-docker` se ha quitado porque este entorno no la necesita; puedes mantenerla si trabajas con contenedores desde dentro. Elimina el contenedor del paso anterior, que usaba otra configuración, y levanta el entorno nuevo: ```bash docker rm -f $(docker ps -aq --filter "label=devcontainer.local_folder=$HOME/demo") devcontainer up --workspace-folder . ``` Comprueba la conexión desde el contenedor de trabajo. Las comillas simples evitan que tu shell expanda `$DATABASE_URL` en el anfitrión, donde no existe: ```bash devcontainer exec --workspace-folder . bash -c 'psql "$DATABASE_URL" -c "select version();"' ``` ```output version ---------------------------------------------------------------------------- PostgreSQL 17.x (Debian 17.x-1.pgdg120+1) on x86_64-pc-linux-gnu, ... (1 row) ``` Si la base de datos aún se está inicializando la primera vez, espera unos segundos y repite el comando. ## Paso 5: Parar y limpiar el entorno La CLI no tiene un comando para detener el entorno; se hace con Docker. Para un entorno basado en Compose, localiza el proyecto que ha creado la CLI: ```bash docker compose ls ``` ```output NAME STATUS CONFIG FILES demo_devcontainer running(2) /home/your_user/demo/.devcontainer/compose.yaml ``` Páralo o elimínalo con ese nombre: ```bash docker compose -p demo_devcontainer stop docker compose -p demo_devcontainer down ``` `down` borra los contenedores pero conserva el volumen `pgdata`; añade `-v` solo si quieres empezar con una base de datos vacía. Un nuevo `devcontainer up` recrea todo en segundos porque las imágenes ya están en caché. ## Paso 6: Usar el Dev Container desde VS Code por SSH Con el servidor preparado, puedes programar desde tu equipo con VS Code mientras todo se ejecuta en el servidor: 1. En VS Code, instala las extensiones **Remote - SSH** (`ms-vscode-remote.remote-ssh`) y **Dev Containers** (`ms-vscode-remote.remote-containers`). 2. Abre la paleta de comandos (`Ctrl+Shift+P`) y ejecuta **Remote-SSH: Connect to Host...**, con `your_user@your_server_ip`. 3. Abre la carpeta `~/demo` en el servidor. 4. Ejecuta **Dev Containers: Reopen in Container**. VS Code usa el Docker del servidor para construir y arrancar el mismo entorno que has creado con la CLI, instala las extensiones de `customizations.vscode` dentro del contenedor y reenvía el puerto 3000 a tu equipo. La terminal integrada ya se abre dentro del contenedor, como usuario `node`, en `/workspaces/demo`. Después de editar `devcontainer.json`, `compose.yaml` o el Dockerfile, aplica los cambios con **Dev Containers: Rebuild Container**. ## Paso 7: Compartir el entorno con el equipo El directorio `.devcontainer` debe versionarse junto al código. Unas pautas para que el entorno sea reproducible de verdad: - Fija versiones: usa etiquetas concretas de imagen (`1-22-bookworm`, `postgres:17`) y de features (`:1`), nunca `latest`. - Pon los pasos de preparación (`npm ci`, migraciones) en `postCreateCommand`, para que un entorno nuevo quede listo sin instrucciones manuales. - No guardes secretos en `devcontainer.json` ni en `compose.yaml`; las contraseñas de este ejemplo solo sirven para la base de datos local. Si la construcción tarda mucho, puedes construir la imagen una vez y publicarla en tu registro. Inicia sesión antes con `docker login`: ```bash devcontainer build --workspace-folder . --image-name registry.your_domain/team/demo-dev:2026.09 --push ``` Por último, puedes ejecutar los tests en CI dentro del mismo entorno que usan los desarrolladores. En GitHub Actions basta con instalar la CLI en el runner: ```yaml name: CI on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm install -g @devcontainers/cli - run: devcontainer up --workspace-folder . - run: devcontainer exec --workspace-folder . npm test ``` ## Solución de problemas - **`permission denied while trying to connect to the Docker daemon socket`**: tu usuario no está en el grupo `docker`. Ejecuta `sudo usermod -aG docker "$USER"` y vuelve a iniciar sesión. - **La construcción falla y no se ve por qué**: repite con `devcontainer up --workspace-folder . --log-level trace` para ver cada comando de Docker. En VS Code, usa **Dev Containers: Show Container Log**. - **Los cambios de `devcontainer.json` no aparecen**: el contenedor existente se reutiliza. Recréalo con `--remove-existing-container` o **Rebuild Container**. - **Ficheros creados en el contenedor pertenecen a otro usuario en el servidor**: en Linux, la CLI ajusta el UID del usuario remoto (`node`) al de tu usuario del anfitrión al crear el contenedor. Si trabajas con varios usuarios o has cambiado `remoteUser`, recrea el contenedor; no uses `chmod 777` ni `remoteUser: root` como solución. - **`psql: could not translate host name "db"`**: el contenedor no está en la red de Compose, normalmente porque `devcontainer.json` todavía usa `image` en lugar de `dockerComposeFile`, o porque el contenedor antiguo sigue en marcha. ## Conclusión Has instalado la CLI de Dev Containers en Ubuntu 24.04, has definido un entorno Node.js con features y una base de datos PostgreSQL mediante Docker Compose, y lo has usado desde la línea de comandos y desde VS Code por SSH. Todo el entorno queda descrito en `.devcontainer`, así que cualquier persona del equipo, o tu pipeline de CI, puede reproducirlo con un solo comando. Como siguientes pasos puedes: - Crear una feature propia para las herramientas internas de tu equipo, siguiendo la plantilla del repositorio `devcontainers/feature-starter`. - Añadir Redis u otros servicios al mismo `compose.yaml`. - Publicar la imagen preconstruida en un registro privado y referenciarla en `devcontainer.json` para acelerar el primer arranque.