Claude Code y Devcontainer: entorno de desarrollo reproducible
Crea un devcontainer Claude Code reproducible con Dockerfile, permisos, secretos, volumes y port-forward.
Claude Code funciona mejor cuando el entorno de desarrollo deja de ser una sorpresa. Si una persona usa Node.js 22, otra conserva Node.js 20, PostgreSQL está instalado de forma distinta en cada portátil y Redis solo existe en algunas máquinas, el agente acaba resolviendo problemas de entorno antes que problemas de producto.
Un Dev Container convierte esa configuración en parte del repositorio. El editor se conecta a un contenedor Docker y allí ejecuta la terminal, el servidor de lenguaje, los tests, las herramientas de base de datos y Claude Code. En esta guía construiremos un devcontainer práctico para Next.js y TypeScript, con PostgreSQL, Redis, Dockerfile, postCreateCommand, permisos, secretos, volumes y port-forward.
El 2 de junio de 2026, la documentación oficial de Claude Code incluye una página de Development containers con notas sobre la Feature oficial, persistencia de credenciales, restricciones de red y riesgos de saltarse permisos. Para el estándar de contenedores, conviene tener a mano la documentación de VS Code Dev Containers y la Dev Container Specification.
Por qué un devcontainer encaja con Claude Code
Claude Code no solo sugiere cambios. Puede leer archivos, editar código, ejecutar comandos, revisar fallos de tests y proponer el siguiente paso. Por eso importa mucho dónde se ejecuta. Si trabaja en la shell del host, puede ver paquetes globales antiguos, credenciales personales, variables de otros proyectos o servicios locales que el resto del equipo no tiene.
Con un devcontainer, la superficie de trabajo queda definida en código. La versión de Node.js, los paquetes del sistema, la versión del CLI de Claude Code, las extensiones de VS Code, los scripts de ciclo de vida, los volumes y los puertos reenviados se pueden revisar en el mismo pull request. Cuando Claude Code diga que ejecutó npm test, el equipo sabrá en qué entorno lo hizo.
flowchart LR
Host["Equipo local"] --> Editor["VS Code / Cursor"]
Editor --> Container["Dev Container"]
Container --> Claude["Claude Code CLI"]
Container --> Tools["Node.js / npm / psql / redis-cli"]
Container --> Services["PostgreSQL / Redis"]
Container --> Repo["Repositorio montado"]
Claude --> Repo
Claude --> Tools
Hay tres casos de uso claros. El primero es onboarding: una persona nueva reconstruye el contenedor y obtiene la misma base que el resto del equipo. El segundo es desarrollo remoto: la misma carpeta .devcontainer sirve en local y en Codespaces. El tercero es depuración asistida por IA: lint, typecheck y tests dejan de depender de una máquina concreta.
Archivos que vamos a crear
La configuración separa aplicación, servicios y estado de Claude Code. Esa separación evita montar el directorio home del host por comodidad.
| Archivo | Función | Qué revisar |
|---|---|---|
.devcontainer/devcontainer.json | Punto de entrada para el editor | remoteUser, puertos, mounts, comandos de ciclo de vida |
.devcontainer/Dockerfile | Instala herramientas y Claude Code | versión del CLI, usuario no root, paquetes del sistema |
.devcontainer/docker-compose.yml | Levanta app, PostgreSQL y Redis | volumes, health checks, puertos publicados |
.devcontainer/post-create.sh | Setup después de crear el contenedor | lockfiles, Prisma, errores visibles |
.claude/settings.json | Reglas de permisos para Claude Code | .env, secretos, push y comandos destructivos |
devcontainer.json listo para copiar
Crea .devcontainer/devcontainer.json. Es JSON real, así que no incluyas comentarios dentro del archivo.
{
"name": "claude-code-next-dev",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/app",
"remoteUser": "node",
"shutdownAction": "stopCompose",
"waitFor": "postCreateCommand",
"postCreateCommand": "bash .devcontainer/post-create.sh",
"postStartCommand": "git config --global --add safe.directory /workspaces/app || true",
"forwardPorts": [3000, 5432, 6379],
"portsAttributes": {
"3000": { "label": "Next.js", "onAutoForward": "notify" },
"5432": { "label": "PostgreSQL", "onAutoForward": "silent" },
"6379": { "label": "Redis", "onAutoForward": "silent" }
},
"mounts": [
"source=claude-code-config-${devcontainerId},target=/home/node/.claude,type=volume"
],
"containerEnv": {
"NODE_ENV": "development",
"DISABLE_AUTOUPDATER": "1",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
},
"customizations": {
"vscode": {
"extensions": [
"anthropic.claude-code",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-azuretools.vscode-docker"
],
"settings": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"typescript.tsdk": "node_modules/typescript/lib",
"terminal.integrated.defaultProfile.linux": "bash"
}
}
}
}
El valor de remoteUser debe ser un usuario no root. El contenedor reduce el riesgo, pero el workspace sigue montado desde el host. Si Claude Code borra un archivo en /workspaces/app, también desaparece del repositorio local. El volume para /home/node/.claude conserva la configuración de Claude Code entre rebuilds sin exponer todo el home del host.
Docker Compose, Dockerfile y setup inicial
Este docker-compose.yml levanta la app, PostgreSQL y Redis. La app se queda en sleep infinity para que el editor pueda adjuntarse al contenedor.
services:
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
command: sleep infinity
volumes:
- ..:/workspaces/app:cached
- node_modules:/workspaces/app/node_modules
- claude_code_config:/home/node/.claude
environment:
DATABASE_URL: postgresql://app:app_password@db:5432/app
REDIS_URL: redis://redis:6379
NEXT_TELEMETRY_DISABLED: "1"
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app_password
POSTGRES_DB: app
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 5s
retries: 20
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 20
volumes:
node_modules:
claude_code_config:
postgres_data:
redis_data:
En el Dockerfile fijamos la versión de Claude Code. Para esta guía comprobé el paquete el 2 de junio de 2026 y npm view @anthropic-ai/claude-code version devolvió 2.1.160.
FROM mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm
ARG CLAUDE_CODE_VERSION=2.1.160
ENV DISABLE_AUTOUPDATER=1
ENV NEXT_TELEMETRY_DISABLED=1
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
jq \
postgresql-client \
redis-tools \
ripgrep \
&& rm -rf /var/lib/apt/lists/*
RUN npm install -g "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}" \
&& npm cache clean --force
USER node
WORKDIR /workspaces/app
RUN mkdir -p /home/node/.claude
El postCreateCommand llama a un script para respetar el gestor de paquetes del proyecto.
#!/usr/bin/env bash
set -euo pipefail
cd /workspaces/app
corepack enable
if [ -f pnpm-lock.yaml ]; then
pnpm install --frozen-lockfile
elif [ -f yarn.lock ]; then
yarn install --immutable
elif [ -f package-lock.json ]; then
npm ci
elif [ -f package.json ]; then
npm install
fi
if [ -f prisma/schema.prisma ]; then
npx prisma generate
fi
node --version
npm --version
claude --version || true
La clave es que el setup falle cuando algo está mal. Si npm ci o Prisma fallan y el comando continúa, Claude Code puede empezar a modificar una base rota. También evita iniciar npm run dev dentro de postCreateCommand; un servidor permanente debe ejecutarse desde una terminal o task.
Permisos, secretos, volumes y port-forward
No montes ~/.ssh, ~/.aws, ~/.config/gcloud ni .env de producción solo porque el contenedor lo permite. El secreto más seguro es el que no entra en el contenedor. Para desarrollo, usa valores internos como postgresql://app:app_password@db:5432/app; para producción, usa secrets gestionados o tokens temporales.
Las reglas de Claude Code pueden vivir en .claude/settings.json.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Bash(npm run lint)",
"Bash(npm run test *)",
"Bash(npm run typecheck)",
"Bash(claude --version)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Bash(printenv *)",
"Bash(git push *)",
"Bash(docker system prune *)"
]
}
}
Los volumes también necesitan criterio. node_modules debe vivir dentro del contenedor para evitar mezclar módulos nativos de Windows o macOS con Linux. El volume de PostgreSQL es útil, pero puede conservar un estado viejo después de varios experimentos de migración. Documenta cuándo se puede ejecutar docker compose -f .devcontainer/docker-compose.yml down -v.
Para puertos, empieza con forwardPorts. Publicar 5432 o 6379 con Compose ports solo tiene sentido si una herramienta externa realmente lo necesita. Abrir todo porque “así conecta” suele crear conflictos con servicios locales y revisiones de seguridad innecesarias.
Fallos frecuentes y verificación
Los fallos más comunes son ejecutar Claude Code como root, no fijar la versión del CLI, montar secretos de producción, iniciar servidores largos en postCreateCommand y publicar puertos de base de datos sin necesidad. Cada punto parece pequeño, pero juntos destruyen la reproducibilidad.
Después de reconstruir, comprueba node --version, npm --version, claude --version, un comando de lint o test y http://localhost:3000. Revisa que .env, .env.local y secrets/** no estén disponibles para Claude Code. Confirma que PostgreSQL y Redis no están expuestos fuera de lo necesario.
Para profundizar en servicios, lee la guía de Docker Compose con Claude Code. Para llevar la verificación a despliegues, continúa con la guía de CI/CD con Claude Code. Si tu equipo quiere convertir devcontainers, CLAUDE.md, permisos y revisión de artículos en un flujo repetible, revisa training / consultation.
Resultado de la prueba
En un repositorio pequeño de Next.js, lo que más redujo fricción fue separar el script de postCreateCommand, poner node_modules en un volume del contenedor y conservar /home/node/.claude en un volume por proyecto. Cada rebuild dejó la misma versión de Claude Code, el mismo flujo de instalación, la misma generación de Prisma y el mismo puerto 3000. El riesgo real apareció con el volume de la base de datos: después de varias migraciones de prueba, el estado viejo podía confundir el diagnóstico. La reproducibilidad no depende solo del Dockerfile, sino de versiones, secretos, volumes y puertos revisados juntos.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Escalera de permisos de Claude Code para ampliar acceso sin perder control
Pasa de read-only a ediciones limitadas, comandos de prueba y checks de deploy con menos riesgo.
Claude Code Small PR Proof Pack: cambios pequeños que sí se pueden revisar
Un paquete de prueba para PRs de Claude Code: diff, checks, URL pública, CTA y rollback.
Gate de revisión antes del commit con Claude Code
Cómo revisar con Claude Code antes del commit: diff, build, URL pública, Gumroad, consultoría, tests y archivos ajenos.