Ejecuta herramientas CLI de IA en Docker o Podman de forma segura (Guía 2026)
Ejecuta herramientas CLI de IA en Docker o Podman de forma segura: contenedores aislados para Claude Code, Amp, Gemini CLI y OpenCode, con configuraciones persistentes y cero riesgo para el host.
Actualizado Publicado 85 min de lectura

Los agentes de programación con IA pueden escribir archivos, ejecutar comandos de shell y leer todo lo que tu cuenta de usuario puede alcanzar. Esta guía te muestra cómo ejecutar herramientas CLI de IA en Docker o Podman dentro de un entorno seguro y aislado, de modo que un rm -rf alucinado o una instalación de dependencias fallida no puedan tocar tu máquina. Construirás una única imagen de contenedor que aloja Claude Code, Amp, Factory.ai, Gemini CLI, OpenCode y GitHub Copilot CLI, cada una con sus propias configuraciones, claves API y montajes de proyectos.
¿Por qué poner las herramientas CLI de IA en contenedores?
Instalas una CLI, te pide un comando de shell, quiere ejecutar una compilación, decide hacer npm install -g de algo. Cada uno de esos pasos ocurre con los permisos de tu usuario en tu máquina. Un contenedor convierte eso en un sandbox desechable: el agente obtiene un shell y tú puedes borrarlo todo con docker compose down cuando termines.
La versión honesta del argumento: un contenedor es aislamiento, no una máquina virtual. Comparte tu kernel. Si montas tus claves SSH o el socket de Docker dentro, le has entregado tu host al agente de todos modos. Todo en esta guía está construido en torno a no hacer eso.
Si quieres la misma idea pero con el agente viviendo en un servidor en lugar de tu portátil, nuestra guía de despliegue de NanoClaw cubre un agente Claude aislado en contenedor en un VPS.
¿Por qué ejecutar herramientas CLI de IA en Docker o Podman? (aislamiento y sandbox)
Lo único que realmente te rompe
No montes $HOME ni $HOME/.ssh en el contenedor del agente. Tampoco ~/.aws, ~/.config/gcloud o /var/run/docker.sock. Un agente con tu clave privada SSH y el socket de Docker es un agente con root en tu host y capacidad de llegar a todos los servidores a los que tú puedes llegar. Monta solo la carpeta del proyecto.
Ejecutar agentes de programación con IA en sandbox mediante contenedores te da seis cosas:
- Aislamiento: los comandos de shell del agente se ejecutan contra el sistema de archivos del contenedor, no contra tus dotfiles
- Reproducibilidad: tú y un compañero ejecutáis versiones de herramientas idénticas byte a byte
- Seguridad: prueba un agente experimental sin que reescriba tu
~/.zshrcglobal - Múltiples configuraciones: contenedores separados para “clave API del trabajo” y “clave API personal”
- Reversión limpia:
docker compose downy el desastre desaparece - Fijado de versiones: fija Node y Python por proyecto en lugar de pelearte con nvm y pyenv
Lo que los contenedores no te dan:
- Aislamiento de kernel. Una fuga del contenedor es un compromiso del host. Si necesitas una frontera de seguridad real, usa una VM (es la misma razón por la que ejecutamos código no confiable en VMs, no en contenedores).
- Protección de los datos montados. El agente puede borrar cualquier cosa dentro de los directorios que montes. Monta un directorio de proyecto, no un directorio de proyectos que no puedas permitirte perder.
- Protección frente a la red. El contenedor tiene salida a internet a menos que configures lo contrario. Un agente aún puede exfiltrar lo que pueda leer.
Requisitos previos: Docker Desktop o Podman Desktop
Elige un motor de contenedores. Docker si ya lo conoces, Podman si estás en Fedora o quieres rootless por defecto. Todo lo demás de esta guía funciona igual.
Docker
Docker Desktop (Mac/Windows) o Docker Engine (Linux):
- macOS: Docker Desktop para Mac
- Windows: Docker Desktop para Windows
- Linux: Docker Engine
Docker Engine 29 (noviembre de 2025) convirtió el almacén de imágenes containerd en el predeterminado en instalaciones nuevas. Docker Desktop es gratis para uso personal; los planes de pago aplican a empresas más grandes.
Verifica:
docker --version
docker compose versionDocker version 29.7.2, build ...
Docker Compose version v2.x.xPodman
Podman Desktop en Mac/Windows, paquetes nativos en Linux. Podman 6.0 (2026) eliminó el soporte de slirp4netns, CNI, iptables y cgroups v1. El modo rootful sigue funcionando bien: lo que cambió es que la red rootless ahora tiene exactamente una opción, pasta (varios Gbps en lugar de los antiguos ~570 Mbps a los que llegaba slirp4netns).
# Fedora/RHEL/CentOS
sudo dnf install podman podman-compose
# Ubuntu/Debian
sudo apt install podman podman-compose
# macOS (via Homebrew)
brew install podman podman-composeEn macOS y Windows, inicializa la máquina de Podman:
podman machine init
podman machine startVerifica:
podman --version
podman compose version
podman info --format '{{.Host.Security.Rootless}}'El último comando debería imprimir true en una instalación normal de Linux.
Versiones en esta guía
Verificado en septiembre de 2026: Docker Engine 29.7.2, Podman 6.1.0, imagen base nikolaik/python-nodejs:python3.14-nodejs26-bookworm (Node 26.8.2, Python 3.14.7). Los instaladores de CLI cambian a menudo: si una URL devuelve 404, consulta la documentación actual de la herramienta en lugar de asumir que el comando está mal.
Arquitectura del contenedor: la imagen base con Python y Node
Una imagen, dos runtimes. Casi todas las herramientas CLI de IA son un paquete de Node o un script de shell que además quiere Python para sus utilidades, y reconstruir esa pila por herramienta es tiempo perdido.
Usamos nikolaik/python-nodejs, que incluye:
- Node.js 26.8.2 con npm, además de Corepack para yarn y pnpm
- Python 3.14.7 con pip, pipenv, poetry y uv
- Un usuario no root
pncon UID 1000 y GID 1000 - Prompt Starship para un shell legible dentro del contenedor
- Siembra automática de dotfiles en el primer arranque, para que un home vacío montado por bind siga funcionando
¿Por qué Node 26 en lugar de Node 25? Node 25 era una línea impar, no LTS, y llegó a su fin de vida a mediados de 2026. Si construiste este contenedor desde una guía de 2025, tu imagen base está en un runtime EOL sin parches de seguridad. Esa es toda la razón para reconstruir.
El detalle del UID 1000 importa más de lo que parece. Como pn es UID 1000, los archivos que crea dentro de un ~/websites montado por bind quedan en propiedad del UID 1000 en tu host, que es tu usuario normal de Linux en la mayoría de las distribuciones. Si tu usuario del host no es 1000, lee la sección de permisos en la solución de problemas.
Paso 1: crea la estructura del proyecto
Ejecutar el contenedor sin root
Todo lo que sigue se hace como tu usuario normal, incluido en Podman rootless, donde no interviene sudo en ningún punto del flujo de trabajo. Esa es la razón por la que aquí no aparecen los atajos del grupo docker ni el “simplemente ejecútalo como root”.
Docker
mkdir -p ~/docker-ai-tools ~/dev-home ~/websites
cd ~/docker-ai-toolsPodman
mkdir -p ~/podman-ai-tools ~/dev-home ~/websites
cd ~/podman-ai-toolsPara qué sirve cada directorio:
~/docker-ai-toolso~/podman-ai-tools: los archivos de configuración del contenedor (Dockerfile, archivo compose,.env)~/dev-home: el directorio home del usuario del contenedor, montado por bind. Guarda las configuraciones de las CLI, el estado de autenticación y las herramientas instaladas. Sobrevive a las reconstrucciones del contenedor.~/websites: tus proyectos reales, montados por bind en el contenedor. Es lo único que el agente debería poder tocar.
No hagas chmod 777 a estos directorios
Muchas guías antiguas de contenedores te dicen que ejecutes chmod 777 en los puntos de montaje para arreglar errores de permisos. No lo hagas. Hace que cada archivo de tu directorio de proyecto sea escribible por cualquiera en la máquina.
El usuario del contenedor es UID 1000 / GID 1000, así que en Linux simplemente iguala la propiedad:
sudo chown -R 1000:1000 ~/dev-home ~/websitesSi tu usuario del host no es UID 1000, usa $(id -u):$(id -g) en lugar de los números literales y lee la sección de solución de problemas: te encontrarás con el desajuste de UID en la otra dirección.
En macOS y Windows, Docker Desktop y Podman Desktop se encargan del mapeo por ti. Ni chmod ni chown.
Paso 2: crea el Containerfile/Dockerfile
Cambio respecto a la versión de 2025
La etiqueta de la imagen base pasó de python3.14-nodejs25-bookworm a python3.14-nodejs26-bookworm. Node 25 está en fin de vida. Si copias una sola cosa de esta actualización, copia la línea FROM.
Docker
Crea ~/docker-ai-tools/Dockerfile:
# Dockerfile
# Base image with Node.js 26, Python 3.14, and package managers
FROM nikolaik/python-nodejs:python3.14-nodejs26-bookworm
SHELL ["/bin/bash", "-c"]
# The image already has a non-root user "pn" (UID 1000)
USER root
# Install system dependencies first
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
jq \
curl \
vim \
nano \
htop \
tree \
ripgrep \
rsync \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Install Starship prompt system-wide
# This works even when /home/pn is bind-mounted from the host
RUN curl -sS https://starship.rs/install.sh | sh -s -- -y \
&& mkdir -p /opt/skeleton/.config \
&& starship preset catppuccin-powerline -o /opt/skeleton/.config/starship.toml
# Minimal Bash configuration with Starship
RUN cat > /opt/skeleton/.bashrc <<'BRC'
# Bash initialized for pn user
# Enable Node Corepack and Yarn
corepack enable >/dev/null 2>&1 || true
corepack prepare yarn@stable --activate >/dev/null 2>&1 || true
# Quality of life alias
alias python=python3
# Initialize Starship prompt
if command -v starship >/dev/null 2>&1; then
eval "$(starship init bash)"
fi
BRC
# Entrypoint script to seed dotfiles on first run
RUN cat > /usr/local/bin/boot.sh <<'SH'
#!/usr/bin/env bash
set -euo pipefail
# Seed ~/.bashrc if missing (e.g. empty bind-mounted /home/pn)
if [ ! -f "/home/pn/.bashrc" ]; then
cp /opt/skeleton/.bashrc /home/pn/.bashrc
fi
# Seed Starship config if missing
mkdir -p /home/pn/.config
if [ ! -f "/home/pn/.config/starship.toml" ]; then
cp /opt/skeleton/.config/starship.toml /home/pn/.config/starship.toml
fi
# Default to interactive bash if no command was provided
if [ $# -eq 0 ]; then
set -- bash
fi
exec "$@"
SH
RUN chmod +x /usr/local/bin/boot.sh
# Switch back to the non-root user
USER pn
WORKDIR /home/pn/app
ENTRYPOINT ["/usr/local/bin/boot.sh"]
CMD ["bash"]Podman
Crea ~/podman-ai-tools/Containerfile. El contenido es idéntico al Dockerfile anterior: Podman lee Dockerfiles sin problema, solo usa el nombre Containerfile por convención.
# Containerfile
# Base image with Node.js 26, Python 3.14, and package managers
FROM nikolaik/python-nodejs:python3.14-nodejs26-bookworm
SHELL ["/bin/bash", "-c"]
# The image already has a non-root user "pn" (UID 1000)
USER root
# Install system dependencies first
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
jq \
curl \
vim \
nano \
htop \
tree \
ripgrep \
rsync \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Install Starship prompt system-wide
# This works even when /home/pn is bind-mounted from the host
RUN curl -sS https://starship.rs/install.sh | sh -s -- -y \
&& mkdir -p /opt/skeleton/.config \
&& starship preset catppuccin-powerline -o /opt/skeleton/.config/starship.toml
# Minimal Bash configuration with Starship
RUN cat > /opt/skeleton/.bashrc <<'BRC'
# Bash initialized for pn user
# Enable Node Corepack and Yarn
corepack enable >/dev/null 2>&1 || true
corepack prepare yarn@stable --activate >/dev/null 2>&1 || true
# Quality of life alias
alias python=python3
# Initialize Starship prompt
if command -v starship >/dev/null 2>&1; then
eval "$(starship init bash)"
fi
BRC
# Entrypoint script to seed dotfiles on first run
RUN cat > /usr/local/bin/boot.sh <<'SH'
#!/usr/bin/env bash
set -euo pipefail
# Seed ~/.bashrc if missing (e.g. empty bind-mounted /home/pn)
if [ ! -f "/home/pn/.bashrc" ]; then
cp /opt/skeleton/.bashrc /home/pn/.bashrc
fi
# Seed Starship config if missing
mkdir -p /home/pn/.config
if [ ! -f "/home/pn/.config/starship.toml" ]; then
cp /opt/skeleton/.config/starship.toml /home/pn/.config/starship.toml
fi
# Default to interactive bash if no command was provided
if [ $# -eq 0 ]; then
set -- bash
fi
exec "$@"
SH
RUN chmod +x /usr/local/bin/boot.sh
# Switch back to the non-root user
USER pn
WORKDIR /home/pn/app
ENTRYPOINT ["/usr/local/bin/boot.sh"]
CMD ["bash"]Ejecutar como usuario no root con USER pn
La línea USER pn del final es la relevante para la seguridad. Todo lo instalado en tiempo de ejecución, cada archivo que el agente crea y cada proceso que el agente lanza se ejecuta como UID 1000 dentro del contenedor, no como root. Los pasos de instalación siguen usando USER root temporalmente porque necesitas root para ejecutar apt-get, pero la imagen final y el contenedor en ejecución son no root.
Eso importa para el sandbox porque la mayoría de las fugas de contenedores dependen de root dentro del contenedor más un bug del kernel o una mala configuración (un socket montado, un flag privilegiado, un --cap-add). Ejecutar como UID 1000 elimina el camino fácil.
Si quieres el recorrido en profundidad de lo que los usuarios no root dentro de los contenedores te protegen y de lo que no, tenemos una guía aparte sobre ejecutar un contenedor como usuario no root.
Dos excepciones que conviene conocer:
- Puertos por debajo de 1024. Un usuario no root no puede vincularlos dentro del contenedor. Los servidores de desarrollo que ejecutarías junto a un agente (3000, 4321, 5173, 8080) están todos por encima de 1024, así que rara vez molesta.
- Instalación de paquetes del sistema.
apt-get installnecesita root. Por eso la sección “instalar un comando que falta” más abajo usadocker exec --user root.
Qué hace este Containerfile
- Parte de
nikolaik/python-nodejs: Node 26.8.2 y Python 3.14.7 ya configurados - Instala lo esencial y aburrido: git, jq, curl, vim, htop, tree, ripgrep, rsync, herramientas de compilación
- Instala Starship: un prompt que muestra la rama de git y la versión de Node/Python, para que sepas en qué contenedor estás
- Siembra configuraciones esqueleto:
boot.shcopia dotfiles en un home vacío montado por bind en el primer arranque - Se ejecuta como no root:
USER pn, UID 1000 / GID 1000 - Mantiene tu home real fuera: nada en esta imagen referencia
$HOMEdel host
Paso 3: crea la configuración de Compose
Docker
Crea ~/docker-ai-tools/docker-compose.yml:
services:
ai-tools:
build:
context: .
dockerfile: Dockerfile
image: ai-tools:latest
container_name: ai-tools
restart: unless-stopped
tty: true
stdin_open: true
environment:
- TZ=Europe/Bucharest # Change to your timezone
env_file:
- .env # API keys - do not commit this file
volumes:
- ${HOME}/dev-home:/home/pn
- ${HOME}/websites:/home/pn/app/websites
security_opt:
- no-new-privileges:true
# Uncomment only if a tool needs an OAuth callback or a local server
# ports:
# - "127.0.0.1:3000:3000" # bind to localhost, not 0.0.0.0Podman
Crea ~/podman-ai-tools/podman-compose.yml:
services:
ai-tools:
build:
context: .
dockerfile: Containerfile
image: ai-tools:latest
container_name: ai-tools
restart: unless-stopped
tty: true
stdin_open: true
environment:
- TZ=Europe/Bucharest # Change to your timezone
env_file:
- .env # API keys - do not commit this file
volumes:
- ${HOME}/dev-home:/home/pn:z
- ${HOME}/websites:/home/pn/app/websites:z
security_opt:
- no-new-privileges:true
# Uncomment only if a tool needs an OAuth callback or a local server
# ports:
# - "127.0.0.1:3000:3000" # bind to localhost, not 0.0.0.0Montaje de volúmenes explicado
~/dev-home:/home/pn: configuraciones de usuario, estado de autenticación de las CLI, dotfiles. Persiste entre reconstrucciones.~/websites:/home/pn/app/websites: tus proyectos. Edítalos desde el host, el agente los edita desde dentro.:z(Podman en Linux): reetiqueta el directorio para SELinux. Usa:zpara directorios compartidos,:Zpara los privados. Omítelo por completo fuera de Linux o si SELinux no está en modo enforcing.- No hace falta
:rw: lectura-escritura es el valor por defecto. Solo escribes:rocuando quieres quitar el acceso de escritura.
Valida el archivo antes de compilar. Compose ignora silenciosamente las erratas en algunos campos:
docker compose config
# or: podman compose configSi el YAML combinado se imprime y el servicio ai-tools tiene ambos volúmenes, todo bien. Si falta un volumen en la salida, tienes un problema de indentación.
Cuándo exponer puertos para callbacks de OAuth
La mayoría de las herramientas CLI de IA se autentican con una clave API en una variable de entorno o con un flujo de código de dispositivo (imprimen una URL, pegas un código). Ninguna de las dos necesita un puerto.
Añade un puerto solo cuando una herramienta abre un listener HTTP local para una redirección OAuth en localhost:
- Una herramienta CLI requiere autenticación OAuth mediante un callback en
localhost - Estás ejecutando un servidor de desarrollo local dentro del contenedor que quieres abrir en el navegador del host
- Una herramienta incluye una interfaz web para iniciar sesión o configurar
Vincula el puerto publicado a 127.0.0.1, no a todas las interfaces. "3000:3000" publica en todas las interfaces que tiene el host, que en un VPS significa internet. "127.0.0.1:3000:3000" no.
Paso 4: compila e inicia el contenedor
Docker
cd ~/docker-ai-tools
# Build the image
docker compose build
# Start the container detached
docker compose up -d
# Enter the container
docker exec -it ai-tools bashPara detenerlo:
docker compose downPodman
cd ~/podman-ai-tools
# Ensure the Podman machine is running (macOS/Windows only)
podman machine start
# Build the image
podman compose build
# Start the container detached
podman compose up -d
# Enter the container
podman exec -it ai-tools bashPara detenerlo:
podman compose downVerifica que realmente está en ejecución:
docker compose psNAME IMAGE COMMAND SERVICE STATUS
ai-tools ai-tools:latest "/usr/local/bin/boot.…" ai-tools Up 12 secondsUp es lo que quieres. Restarting en bucle significa que el entrypoint está fallando: consulta la solución de problemas.
La primera compilación tarda unos minutos (descarga una imagen base de ~580 MB). Las siguientes van por caché. Usa --no-cache solo cuando hayas cambiado de verdad la imagen base; tira todas las capas y vuelve a descargar todo.
Paso 5: verifica el entorno del contenedor
Dentro del contenedor, comprueba que las versiones son las que prometía la etiqueta:
# You should not be root
whoami
id -u
# Expected: pn
# Expected: 1000
# Node and package managers
node -v # v26.8.2
npm -v # 11.x
yarn -v # 4.x (via Corepack)
# Python and package managers
python3 -V # Python 3.14.7
pip --version # 25.x
poetry --version
uv --version
# Shell tooling
which starship
# Expected: /usr/local/bin/starship
# Mounts
ls -la ~/app/websites
touch ~/app/websites/.mount-test && ls -la ~/app/websites/.mount-testEse touch es la prueba que importa. Si falla, el montaje está mal y solo te enterarías más tarde, cuando el agente escriba un archivo en un directorio local del contenedor que desaparece en el próximo docker compose down.
Verifica también desde el host
ls -la ~/websites/.mount-testSi el archivo existe en tu host, el bind mount funciona en ambas direcciones. Bórralo y sigue adelante:
rm ~/websites/.mount-testPaso 6: instala las herramientas CLI de IA (Claude Code, Amp, Gemini CLI, OpenCode)
¿No sabes qué agente elegir? Comparamos las mejores herramientas y agentes de programación con IA en 2026. Instala más de una: pesan 10-100 MB cada una y no hay ninguna desventaja en tener Claude Code, Amp y OpenCode lado a lado.
A partir de aquí, todo se ejecuta dentro del contenedor, así que los comandos son idénticos para Docker y Podman. Entra una vez:
docker exec -it ai-tools bash # or: podman exec -it ai-tools bashSi un instalador necesita root
Algunos instaladores escriben en /usr/local/bin y fallan como pn. Sal y vuelve a entrar como root, ejecuta solo esa instalación y regresa:
# Docker
docker exec -it --user root ai-tools bash
# Podman
podman exec -it --user root ai-tools bashInstala la herramienta, luego haz exit y vuelve a entrar como el usuario normal pn. Las herramientas instaladas como root en /usr/local/bin siguen en el PATH para pn, porque la lectura y ejecución están permitidas para todos ahí. Todo lo instalado en el home de root se pierde para ti, así que prefiere los scripts de instalación y rutas tipo sudo npm install -g en lugar de descargas manuales.
Instala Claude Code en el contenedor
El instalador nativo es el que debes usar. No le importa tu versión de Node y se actualiza solo.
# Native installer (recommended)
curl -fsSL https://claude.ai/install.sh | bash
# Verify
claude --versionSi la configuración de Node de tu imagen base choca con el binario nativo, la vía npm sigue funcionando:
npm install -g @anthropic-ai/claude-code
claude --versionDónde acaban las cosas: el instalador nativo pone el binario en ~/.local/bin por defecto, así que sobrevive a una reconstrucción del contenedor solo porque ~/.local vive dentro del /home/pn montado por bind. Esa es la razón de que exista el montaje del home.
Instala Amp y Factory.ai
# Amp
curl -fsSL https://ampcode.com/install.sh | bash
# Factory.ai CLI
curl -fsSL https://app.factory.ai/cli | sh
# Verify
amp --version
droid --versionAmp retiró su plan gratuito durante 2025, así que revisa los precios actuales antes de construir un flujo de trabajo alrededor. Contexto sobre cómo funciona en el editor: Amp Code, agente de programación con IA.
La CLI Droid de Factory es la otra que vale la pena instalar aquí, sobre todo si quieres apuntarla a tus propios endpoints de modelos en lugar de una suscripción empaquetada:
Factory Droid CLIPuedes configurar Factory para que use tus propios proveedores (incluido un Ollama local): el ejemplo de archivo de configuración está más abajo.
Instala Gemini CLI, OpenCode y GitHub Copilot CLI
# Gemini CLI
npm install -g @google/gemini-cli
# GitHub Copilot CLI (needs a Copilot subscription)
npm install -g @github/copilot
# OpenCode
curl -fsSL https://opencode.ai/install | bash
# Verify all three
gemini --version
copilot --version
opencode --versionDos notas de la versión de 2025 de esta guía:
- GitHub Copilot CLI cambió de paquete. El antiguo paquete beta
@githubnext/github-copilot-cliestá muerto. El actual es@github/copilot. Si ves una advertencia de obsolescencia, estás en el paquete viejo. - OpenCode se instala en
~/.opencode/binen las versiones recientes, no en/usr/local/bin. Si te apareceopencode: command not foundtras una instalación limpia, añade~/.opencode/bina tuPATHen el.bashrcsembrado.
¿Quieres saltarte la gestión de seis claves API? La suscripción OpenCode Go agrupa un montón de modelos detrás de una sola clave, que es la forma más barata de ejecutar un agente en contenedor sin una cartera llena de tokens:
OpenCode GoConfiguración completa, incluido el inicio de sesión en Zen y la configuración de proveedores: guía de configuración de OpenCode. Para los detalles de Copilot (modelos, límites del plan, el nivel de $10), consulta nuestra configuración de GitHub Copilot Pro CLI.
Y si prefieres enrutar Claude Code, Codex y Gemini CLI a través de un único endpoint con una clave y facturación por modelo, para eso sirve un agregador:
Agent RouterAutenticación con las herramientas CLI (OAuth y claves API)
# Device-code or browser flows
claude # then /login inside the session
amp login
droid # then /login inside the session
opencode auth login
copilot # then /login
gemini # follow the auth promptOAuth dentro de un contenedor
El OAuth basado en navegador falla cuando la herramienta espera un callback en localhost y el puerto no es alcanzable desde el navegador de tu host. Tres soluciones, en orden de preferencia:
- Prefiere el flujo de código de dispositivo (imprime una URL y un código): no necesita ningún puerto.
- Copia la URL desde el terminal y pégala en el navegador del host. La mayoría de las herramientas lo aceptan.
- Publica el puerto del callback en
127.0.0.1en el archivo compose, luegodocker compose up -dpara recrear el contenedor.
Si la herramienta insiste en un puerto, vincúlalo a loopback:
ports:
- "127.0.0.1:3000:3000"El estado de autenticación vive en ~/dev-home, así que te autenticas una vez por vida del token y lo mantienes entre reconstrucciones. Si borras ~/dev-home, tendrás que iniciar sesión de nuevo en todo.
Trabajar con proyectos
~/websites en tu host es /home/pn/app/websites dentro del contenedor. Eso significa:
- Tú editas archivos en el host con Zed, VS Code, Cursor o vim
- El agente edita los mismos archivos dentro del contenedor
- Los cambios aparecen en ambos lados al instante (los bind mounts son en vivo, no copias sincronizadas)
- Git funciona desde cualquiera de los dos lados sobre el mismo directorio
.git
Flujo de trabajo de ejemplo: el host edita, el contenedor ejecuta
# On the host
cd ~/websites
mkdir my-new-project && cd my-new-project
git init
# Inside the container
cd ~/app/websites/my-new-project
amp "Create a Next.js app with TypeScript"Una trampa: no ejecutes el mismo gestor de paquetes en ambos lados del montaje. Si el host ejecuta npm install y el contenedor ejecuta pnpm install en el mismo directorio, se pelean por node_modules. Elige un lado como autoridad: dentro del contenedor es la opción sensata, porque las versiones del runtime están fijadas ahí.
Configurar las herramientas de IA dentro del contenedor
Las configuraciones persisten porque ~/dev-home se asigna a /home/pn. Edítalas desde cualquiera de los dos lados; una sesión de nano dentro del contenedor y code ~/dev-home/... en el host tocan el mismo archivo.
Ejemplo: modelos personalizados en Factory.ai
# Inside the container
nano ~/.factory/config.json{
"custom_models": [
{
"model_display_name": "Claude Sonnet 4.5",
"model": "claude-sonnet-4-5-20250929",
"base_url": "https://api.anthropic.com",
"api_key": "your-api-key-here",
"provider": "anthropic",
"max_tokens": 8192
},
{
"model_display_name": "GPT-5 Codex",
"model": "gpt-5-codex",
"base_url": "https://api.openai.com/v1",
"api_key": "your-openai-key-here",
"provider": "openai",
"max_tokens": 8192
},
{
"model_display_name": "Qwen 3 (Local Ollama)",
"model": "qwen3:14b",
"base_url": "http://ollama:11434/v1",
"api_key": "ollama",
"provider": "generic-chat-completion-api",
"max_tokens": 4096
}
]
}Fíjate en el base_url de Ollama: http://ollama:11434/v1, no http://localhost:11434/v1. Dentro de un contenedor, localhost es el propio contenedor. Si ejecutas Ollama como un servicio compose aparte (recomendado, ver abajo), el nombre del servicio es el hostname.
Tipos de proveedores de Factory.ai explicados
Tres tipos de proveedores:
-
anthropic: modelos Claude vía la API de Anthropic- Base URL:
https://api.anthropic.com - Usa la Messages API (
v1/messages)
- Base URL:
-
openai: modelos GPT vía la API de OpenAI- Base URL:
https://api.openai.com/v1 - Usa la Responses API (obligatoria para GPT-5)
- Base URL:
-
generic-chat-completion-api: todo lo compatible con OpenAI- Funciona con OpenRouter, Fireworks, Together AI, Ollama, vLLM y la mayoría de los agregadores
- Usa el formato OpenAI Chat Completions
Si un modelo devuelve 404, la causa habitual es haber elegido el tipo de proveedor equivocado para la forma de API de ese endpoint, no una clave mala.
Acceder a los archivos del host desde el contenedor
# Inside the container
ls -la ~/.factory/ # Factory.ai configs
ls -la ~/.amp/ # Amp configs
ls -la ~/.config/ # everything else
cat ~/.bashrc # shell configEdita los mismos archivos desde el host:
# On the host
code ~/dev-home/.factory/config.json
code ~/dev-home/.bashrc
code ~/dev-home/.config/starship.tomlLos cambios tienen efecto en el siguiente shell dentro del contenedor (o al instante, para los archivos de configuración que las herramientas releen en cada petición).
El problema de los archivos 0600
Los archivos de configuración que creas en el host como tu usuario son legibles por el UID 1000 dentro del contenedor, porque es el mismo UID en el caso común. Si tu UID del host no es 1000, un archivo con modo 0600 propiedad de, por ejemplo, UID 1002 será ilegible para pn. Arregla la propiedad cuando te lo encuentres:
sudo chown 1000:1000 ~/dev-home/.factory/config.jsonNo lo arregles con chmod 666. Las claves API no pertenecen a archivos legibles por todos.
Configuración avanzada
Ejecutar modelos de IA locales con Ollama
La versión de 2025 de esta guía te decía que instalaras Ollama dentro del contenedor del agente. No lo hagas. Infla la imagen del agente y los pesos de los modelos acaban en un volumen que se borra al reconstruir. Ejecuta Ollama como su propio servicio y deja que el agente le hable por la red de compose.
Añade un segundo servicio a tu archivo compose:
services:
ai-tools:
# ... your existing config ...
environment:
- TZ=Europe/Bucharest
- OLLAMA_HOST=http://ollama:11434
depends_on:
- ollama
ollama:
image: ollama/ollama:latest
container_name: ollama
restart: unless-stopped
volumes:
- ${HOME}/ollama:/root/.ollama
# Only publish this if you want to reach it from the host too.
# Don't publish it on a VPS.
# ports:
# - "127.0.0.1:11434:11434"Luego descarga un modelo y apunta una herramienta a él:
docker compose up -d
docker exec -it ollama ollama pull qwen3:14b
docker exec -it ollama ollama list
# From inside ai-tools, confirm the name resolves
docker exec -it ai-tools curl -s http://ollama:11434/api/tags | jq '.models[].name'Haz cuentas de RAM antes de empezar
Un modelo de 14B a Q4 necesita aproximadamente 9-10 GB de RAM para cargar y ejecutarse. Un modelo de 7B necesita unos 5 GB. En un VPS de 4 GB se quedará sin memoria (OOM) y se llevará el contenedor por delante. Pon un límite de memoria al servicio de Ollama para que no pueda hundir al host entero:
deploy:
resources:
limits:
memory: 12gSi quieres la pila completa de Ollama con una interfaz web, nuestra guía de autoalojar Ollama con Docker Compose cubre Open WebUI, la gestión de modelos y la configuración de GPU.
Configuraciones con varios contenedores
Contenedores separados por contexto evitan que las claves API y los contextos se mezclen:
~/docker-ai-tools-personal/
├── docker-compose.yml # container_name: ai-tools-personal
├── Dockerfile
└── .env # personal keys
~/docker-ai-tools-work/
├── docker-compose.yml # container_name: ai-tools-work
├── Dockerfile
└── .env # work keys, separate ~/websites mountCambia el container_name y las rutas de montaje en cada archivo. Dos contenedores con el mismo container_name se negarán a arrancar: Compose falla con un conflicto de nombres, que al menos es un fallo claro.
Compartir contenedores con el equipo
cd ~/docker-ai-tools
git initCrea un .gitignore antes del primer commit:
cat > .gitignore <<'EOF'
.env
dev-home/
websites/
EOF
git add Dockerfile docker-compose.yml .gitignore
git commit -m "Add AI tools container config"Nunca hagas commit del archivo .env
El archivo .env contiene claves API vivas. Si hiciste commit de una por accidente, la solución es rotar las claves: quitar el archivo en un commit posterior no lo quita del historial. Añade .env al .gitignore antes del primer commit, no después.
Un compañero ejecuta entonces:
git clone <your-repo>
cd <your-repo>
cp .env.example .env # fill in their own keys
mkdir -p ~/dev-home ~/websites
docker compose up -d
docker exec -it ai-tools bashEl mismo Node, el mismo Python, las mismas herramientas instaladas. Esa es la parte de entorno de desarrollo reproducible de todo esto, y es la razón por la que hacer commit del Dockerfile vale esos cinco minutos.
Integración con el IDE
El contenedor es donde se ejecutan las herramientas. Tu editor se queda en el host, donde ya viven tu renderizado de fuentes, tus extensiones y tus atajos de teclado.
VS Code Dev Containers para agentes de IA
Dev Containers permite a VS Code ejecutar su servidor dentro del mismo contenedor que usa el agente, de modo que el terminal integrado, las tareas y el agente ven runtimes idénticos. Crea ~/docker-ai-tools/.devcontainer/devcontainer.json:
{
"name": "ai-tools",
"dockerComposeFile": ["../docker-compose.yml"],
"service": "ai-tools",
"workspaceFolder": "/home/pn/app/websites",
"remoteUser": "pn",
"shutdownAction": "stopCompose",
"customizations": {
"vscode": {
"extensions": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
}
}
}Pasos:
- Instala la extensión Dev Containers (el antiguo nombre “Remote - Containers” ya no existe).
- Abre la carpeta
~/docker-ai-toolsen VS Code. - Ejecuta Dev Containers: Reopen in Container.
Si VS Code se queja de que el contenedor ya existe, lo tienes en ejecución desde docker compose up -d. Ejecuta primero docker compose down: Dev Containers quiere ser dueño del ciclo de vida.
Para una alternativa basada en terminal en macOS orientada a flujos de trabajo con agentes, mira nuestra guía del terminal cmux para agentes de programación con IA. Y si prefieres tener la IA dentro del propio editor en lugar de en un shell de contenedor:
WindsurfEdición directa de archivos desde el host
La opción más simple, y la que la mayoría de la gente acaba usando:
# On the host
code ~/websites/my-project
# In the container, the agent sees the changes immediately
cd ~/app/websites/my-project
amp "Refactor this component"Los bind mounts no sincronizan: son el mismo inodo. No hay paso de “esperar a que sincronice” ni posibilidad de una copia desfasada en un lado.
Solución de problemas
Permission denied en ~/dev-home o ~/websites
En Linux, la solución es casi siempre un desajuste de UID. Comprueba ambos lados:
# On the host
id -u
ls -lan ~/websites | head -3
# In the container
docker exec -it ai-tools idEl usuario del contenedor pn es UID 1000. Si tu usuario del host también es 1000, simplemente arregla la propiedad una vez:
sudo chown -R 1000:1000 ~/dev-home ~/websitesSi tu UID del host es distinto, o haces chown del montaje a 1000 como arriba, o cambia el user: del archivo compose a tu UID del host y define HOME:
user: "1002:1002"
environment:
- HOME=/home/pnEn Linux con SELinux, añade la etiqueta al volumen (Podman lo necesita más a menudo que Docker):
chcon -Rt container_file_t ~/dev-home ~/websitesO usa :z / :Z en la lista de volúmenes del compose en lugar de reetiquetar manualmente.
En macOS y Windows, esto es casi siempre un problema de rutas, no de permisos. Confirma que los directorios existen en el host y que no usaste ~ dentro del archivo compose: ~/websites en YAML no lo expande Compose, por eso los ejemplos usan ${HOME}/websites.
El contenedor no arranca o se reinicia constantemente
Mira los logs primero, siempre:
Docker
docker compose logs --tail 50
docker logs ai-toolsPodman
podman compose logs --tail 50
podman logs ai-toolsCausas habituales:
- Puerto ya en uso:
Error starting userland proxy: listen tcp4 0.0.0.0:3000: bind: address already in use. Encuentra al dueño conss -ltnp | grep 3000y deténlo o cambia el puerto publicado. - Fallo al descargar la imagen: normalmente una errata en la etiqueta de la imagen base o falta de red. Confirma que la etiqueta existe en la página de etiquetas de python-nodejs.
- Fallo al montar el volumen: la ruta del host no existe. Compose crea los directorios que faltan cuando el origen es una ruta relativa, pero una ruta
${HOME}/...inexistente puede producir un error confuso.mkdir -p ~/dev-home ~/websitesy reintenta. - Estado
Exited (0): el entrypoint se ejecutó y bash salió porque no hay TTY. Asegúrate de quetty: trueystdin_open: trueestán ambos en el archivo compose.
La autenticación de la herramienta de IA falla
- Prueba el flujo de código de dispositivo antes de tocar puertos. Es la opción menos frágil.
- Copia la URL de autenticación al navegador del host y complétala ahí.
- Publica el puerto del callback en
127.0.0.1y recrea el contenedor. - Comprueba el reloj. Los flujos OAuth rechazan tokens cuando la hora del contenedor se desvía.
datedentro del contenedor debería coincidir con el host; la variableTZmaneja la visualización, no el reloj en sí.
Ejemplo, Factory.ai:
droid
# then /login; if it prints a URL, paste that URL into your host browserLos cambios no se sincronizan entre host y contenedor
Confirma que los montajes realmente existen:
Docker
docker inspect ai-tools --format '{{json .Mounts}}' | jq
docker inspect ai-tools --format '{{.HostConfig.Privileged}}'Podman
podman inspect ai-tools --format '{{json .Mounts}}' | jqForma esperada:
/home/you/dev-home -> /home/pn
/home/you/websites -> /home/pn/app/websitesSi el montaje está presente pero las escrituras no llegan a ninguna parte, comprueba si el agente se ejecutó desde una ruta local del contenedor. /home/pn/app fuera de /home/pn/app/websites no está montado: solo lo está el subdirectorio. Los archivos que el agente escribe en /home/pn/app/foo.txt desaparecen en el próximo docker compose down.
Privileged debe ser false. Si es true, quita privileged: true del archivo compose antes de hacer cualquier otra cosa.
El agente borró algo que no debería
Si borró archivos dentro de ~/websites, también desaparecieron del host: los bind mounts no son snapshots.
Lo que te salva:
- Git. Haz commit antes de entregarle una tarea a un agente.
git statusdespués,git checkout -- .si salió mal. - Un backup que no esté en el mismo disco. Para
~/dev-homehablamos de configuraciones y estado de autenticación: molesto de perder, no catastrófico. - Montajes acotados. Si montas un solo directorio de proyecto en lugar de un directorio de doce, el radio de explosión es un proyecto.
Lo que no te salva: docker compose down. Elimina el contenedor, no los archivos del bind mount.
Instalar comandos que faltan en el contenedor
Las imágenes base son mínimas a propósito. Te encontrarás con command not found la primera vez que un agente quiera rg, ffmpeg o un cliente de Postgres:
bash: rg: command not found
bash: psql: command not foundDos formas de arreglarlo.
Opción 1: instalar temporalmente (solución rápida)
Docker
# Enter as root
docker exec -it --user root ai-tools bash
# Always update the index first, or apt can't find the package
apt-get update
apt-get install -y ripgrep postgresql-client
exit
docker exec -it ai-tools bashPodman
# Enter as root
podman exec -it --user root ai-tools bash
apt-get update
apt-get install -y ripgrep postgresql-client
exit
podman exec -it ai-tools bashTemporal significa temporal
Los paquetes instalados así viven en la capa escribible del contenedor y desaparecen en el próximo docker compose down o reconstrucción. Vale para “necesito psql ahora mismo”, no para algo que vayas a necesitar la semana que viene.
Olvidar el apt-get update previo es la causa más común de Unable to locate package. Ejecútalo siempre: un índice cacheado de una capa anterior suele estar desfasado.
Opción 2: actualiza el Dockerfile y reconstruye (permanente)
Añade el paquete a la línea apt-get install y reconstruye:
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
jq \
curl \
vim \
nano \
htop \
tree \
ripgrep \
rsync \
postgresql-client \
ffmpeg \
build-essential \
&& rm -rf /var/lib/apt/lists/*Luego:
Docker
cd ~/docker-ai-tools
docker compose build
docker compose up -d
docker exec -it ai-tools bash
# Verify
rg --version
psql --versionPodman
cd ~/podman-ai-tools
podman compose build
podman compose up -d
podman exec -it ai-tools bash
# Verify
rg --version
psql --versionQué sobrevive a una reconstrucción
- Paquetes del sistema (git, rg, psql, ffmpeg): reconstruidos dentro de la imagen, ahí permanentemente
- Herramientas CLI de IA: reinstaladas. Viven en el home montado por bind, así que la mayoría sobrevive si el instalador las puso en
~/.local/bino~/.opencode/bin, pero los globales de npm dentro de la imagen no - Autenticación: normalmente sobrevive en
~/dev-home; vuelve a ejecutar el comando de login si no - Configuraciones en
~/dev-home: se conservan - Proyectos en
~/websites: se conservan
Si una herramienta de IA muere tras una reconstrucción, simplemente vuelve a ejecutar su instalador. Son 30 segundos y es la razón de que los pasos de instalación sean de una línea.
Paquetes comunes que podrías necesitar
git: control de versiones, y la mayoría de los agentes escriben mensajes de commitcurl/wget: peticiones HTTP y descargasjq: parseo de JSON, útil para respuestas de APIripgrep(rg): búsqueda rápida; varios agentes lo invocan por shellvim/nano: edición dentro del contenedorhtop: ve qué está comiendo memoria cuando una compilación se atascatree: vista general de directoriosrsync: sincronización de archivosbuild-essential: toolchain de C/C++ para módulos nativos de npmpostgresql-client:psqlpara trabajo con bases de datosffmpeg: manejo de medios
Buenas prácticas de seguridad para agentes de IA en sandbox
Tu configuración de contenedor es un conjunto de capacidades. Todo lo de esta lista reduce lo que el agente puede hacer si algo sale mal.
- Nunca hagas commit de claves API. Usa
.env+env_file, o mejor, una configuración de secretos de Docker Compose - Rota las claves que hayas usado dentro del contenedor, sobre todo las que tocaron un contenedor compartido
- Monta solo
~/websitesy~/dev-home. No$HOME, no~/.ssh, no~/.aws - Nunca montes
/var/run/docker.sock. Es root en el host - Ejecuta como no root. Ya configurado con
USER pn(UID 1000) - Mantén la imagen base actualizada. Reconstruye con
--no-cachecuando cambie la etiqueta base - Vincula los puertos publicados a
127.0.0.1. No a0.0.0.0 - Define límites de recursos. Un agente desbocado debería ser lento, no fatal
Montajes de solo lectura y límites de recursos
Dos cambios de compose que recortan el radio de explosión sin romper nada:
services:
ai-tools:
# ... your existing config ...
security_opt:
- no-new-privileges:true # blocks setuid escalation inside the container
mem_limit: 8g # kill the process instead of the host
cpus: 4
pids_limit: 512 # stops fork bombs from taking down the hostVerifica que los flags se aplicaron de verdad:
docker inspect ai-tools \
--format 'mem={{.HostConfig.Memory}} pids={{.HostConfig.PidsLimit}} sec={{.HostConfig.SecurityOpt}}'Esperado: mem=8589934592, pids=512 y [no-new-privileges:true]. Si mem=0, el límite no se aplicó y el agente puede consumir toda la RAM del host.
Ahora la versión más fuerte, para cuando el agente solo necesita leer un árbol de referencia:
volumes:
- ${HOME}/dev-home:/home/pn
- ${HOME}/websites:/home/pn/app/websites
- ${HOME}/reference-docs:/home/pn/reference:ro # read-onlydocker exec -it ai-tools sh -c 'touch /home/pn/reference/x' debería fallar con Read-only file system. Esa es la prueba.
cap_drop: ALL no es gratis
Quitar todas las capacidades de Linux (cap_drop: [ALL]) es un paso real de hardening, y también rompe apt-get install dentro del contenedor en ejecución, porque root pierde CAP_CHOWN, CAP_SETUID y compañía. Elige una:
- Portátil / uso de desarrollo: omite
cap_drop. Quieres poder instalar paquetes sobre la marcha. - Contenedor de agente de larga vida: añade
cap_drop: [ALL]y haz cada instalación de paquetes reconstruyendo la imagen.
El conjunto de capacidades por defecto de Docker ya está reducido; cap_drop: ALL es el último 10% de la ganancia y el primer 90% de la molestia.
Variables de entorno y claves API
Mantén las claves fuera de la imagen y fuera de git. Compose lee .env automáticamente para la sustitución de variables, y env_file inyecta los valores en el contenedor:
# docker-compose.yml or podman-compose.yml
services:
ai-tools:
env_file:
- .env
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- OPENAI_API_KEY=${OPENAI_API_KEY}
- GEMINI_API_KEY=${GEMINI_API_KEY}# ~/.env - never commit this
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
GEMINI_API_KEY=...Las variables de entorno son visibles para el proceso
Las variables de entorno se pueden leer desde /proc/<pid>/environ dentro del contenedor y aparecen en docker inspect. Eso está bien para un contenedor de un solo usuario en tu portátil. Para cualquier cosa multiusuario, usa secretos basados en archivos. Para el panorama completo de lo que funciona y lo que no:
La otra dirección también importa: no pases todo tu entorno del host al contenedor. env_file: .env con cinco claves nombradas es deliberado. docker run -e $(env) es entregarle al agente tu token de sesión de AWS.
Si quieres la mecánica de ARG vs ENV vs la sustitución de Compose, nuestra guía sobre usar ARG y ENV para variables de entorno en Docker Compose cubre las diferencias y el orden de precedencia.
Limpieza
Eliminar contenedor e imágenes
Docker
# Stop and remove the container
docker compose down
# Remove the built image
docker rmi ai-tools:latest
# Remove dangling images, stopped containers, and unused networks
docker system prune
# Also remove unused volumes - read the warning below first
docker system prune -a --volumesPodman
podman compose down
podman rmi ai-tools:latest
podman system pruneprune -a --volumes elimina volúmenes con nombre
docker system prune -a --volumes borra todas las imágenes y volúmenes sin uso de la máquina, no solo los de este proyecto. Si otro proyecto compose usa un volumen con nombre que no toca desde hace un mes, esos datos desaparecen. Ejecuta docker volume ls primero y lee la lista.
Los bind mounts (~/websites) nunca los toca prune: viven en el sistema de archivos del host, no en el directorio de datos de Docker.
Si el uso de disco de Docker sigue creciendo y prune no basta, el culpable suele ser el almacén de capas overlay2. Cubrimos recuperar espacio en disco de /var/lib/docker/overlay2 por separado.
Para una referencia rápida de los comandos que realmente escribirás, nuestra lista de comandos esenciales de Docker vale un marcador.
Empezar de cero
docker compose down
docker rmi ai-tools:latest
# Optional: reset configs and auth state
# This deletes every CLI config and login in the container home
rm -rf ~/dev-home/*
# Optional: reset the image cache entirely
docker builder prune -af
# Rebuild
docker compose build --no-cache
docker compose up -dEse rm -rf ~/dev-home/* es el único paso irreversible de toda esta guía. Borra tus tokens de autenticación y las configuraciones de las herramientas. ~/websites queda intacto.
Docker vs Podman para herramientas de IA
El resumen honesto: para este caso de uso la diferencia es menor de lo que sugiere internet. Ambos ejecutan este mismo archivo compose, ambos soportan la misma imagen y ambos te dan el aislamiento que buscas.
| Docker | Podman | |
|---|---|---|
| Arquitectura | Cliente-servidor (daemon) | Sin daemon, fork por comando |
| Privilegio por defecto | El daemon corre como root; el modo rootless sin daemon requiere configuración | Rootless por defecto |
| Uso de recursos en reposo | ~50-100 MB para el daemon | ~0 MB, nada persistente |
| Sintaxis de Compose | docker compose |
podman compose |
| GUI de escritorio | Docker Desktop | Podman Desktop |
| SELinux | Básico | Nativo (:z / :Z) |
| Red rootless | RootlessKit con slirp4netns por defecto (pasta soportado) | Solo pasta en 6.x |
| Licencia | Docker Desktop de pago para empresas grandes | Gratis, proyecto CNCF Sandbox |
| Riesgo del socket de Docker | El socket del daemon existe y es la vía clásica de escalada | Sin daemon, sin socket |
Elige Docker si: tu equipo ya lo usa, quieres el mayor ecosistema de ejemplos e integraciones de CI, o estás en macOS y quieres la experiencia de escritorio más suave.
Elige Podman si: estás en Fedora/RHEL, quieres contenedores rootless sin configuración extra, o te importa que no haya un daemon root siempre activo al que atacar.
Para un contenedor que ejecuta un agente con acceso a shell, el argumento de “sin proceso root persistente y sin socket de Docker” es el que realmente importa para la seguridad. El mecanismo es simple: si el agente encuentra /var/run/docker.sock dentro de su contenedor, puede levantar un contenedor privilegiado y ser root en tu host en unos tres comandos.
El desglose completo de arquitectura, licencias y rutas de migración está en nuestro artículo Podman vs Docker comparados.
Ejemplos de uso reales
Ejemplo 1: Factory.ai con modelos personalizados
# Inside the container
cd ~/app/websites/my-app
droid
# In the Droid prompt, pick your model
/model
# Choose "Claude Sonnet 4.5" from custom models
"Add user authentication with Supabase, including sign-up, login, and protected routes"El valor de la configuración de modelos personalizados: puedes apuntar Factory a un agregador barato para las ediciones rutinarias y a un modelo de frontera solo cuando lo necesitas, sin cambiar nada en el host.
Ejemplo 2: Amp con contexto de proyecto
cd ~/app/websites/nextjs-blog
cat > AGENTS.md <<'EOF'
# Project: Next.js Blog
## Tech stack
- Next.js 15
- TypeScript
- Tailwind CSS
- MDX for blog posts
## Architecture
- App Router
- Server Components by default
- Client Components only when needed
## Commands
- Dev: npm run dev
- Build: npm run build
- Test: npm test
EOF
amp "Add a comments section using Supabase"AGENTS.md es la ganancia de contexto más barata que existe. Es un archivo del proyecto, así que funciona para todos los agentes que lo lean, y funciona idéntico dentro del contenedor.
Ejemplo 3: varias herramientas de IA en secuencia
# One agent implements
amp "Create a React component for a product card"
# Another reviews
claude "Review the ProductCard component and suggest performance improvements"
# A third writes tests
droid "Generate unit tests for ProductCard.tsx"Distintos modelos son buenos en cosas distintas, y ejecutarlos contra el mismo checkout significa que cada uno ve el trabajo del anterior. También es una forma barata de contrastar la salida de un agente con una segunda opinión.
Si quieres el siguiente paso —dejar que un asistente dirija despliegues de Docker en un servidor— eso es un problema distinto con un radio de explosión mayor, y escribimos sobre dejar que un asistente de IA despliegue apps Docker por separado.
Buenas prácticas para herramientas CLI de IA en contenedores
- Un contenedor por proyecto o contexto, no un contenedor para todo
- Haz commit antes de entregar una tarea. Git es tu reversión, no el contenedor
- Monta poco. Una carpeta de proyecto, no un directorio de proyectos
- Añade un
.dockerignore/.containerignorepara quenode_modulesy.gitno se envíen al contexto de compilación - Fija la etiqueta de la imagen base:
python3.14-nodejs26-bookworm, nuncalatest - Define límites de memoria y pids para que un agente desbocado degrade un servicio, no el host
- Haz backup de
~/dev-homesi reautenticarte en seis herramientas te molestaría - Reconstruye mensualmente para recoger los parches de seguridad de la imagen base
- Mantén
.envfuera de git y rota todo lo que se haya filtrado alguna vez
Preguntas frecuentes
¿Puedo ejecutar varias herramientas de IA simultáneamente?
Sí, y es la forma normal de usar esta configuración. Todas las CLI están instaladas en el mismo contenedor y comparten el proyecto montado:
# Terminal 1
docker exec -it ai-tools bash
cd ~/app/websites/project1
amp "Implement feature X"
# Terminal 2, same container
docker exec -it ai-tools bash
cd ~/app/websites/project2
claude "Review the auth code"Cada exec es una sesión independiente. Lo único que hay que evitar es que dos agentes editen el mismo archivo del mismo proyecto a la vez: se pisarán entre ellos, y eso es un problema de git que tendrás que desenredar a mano.
¿Los contenedores ralentizarán mis herramientas de IA?
No hay una ralentización medible para trabajo de CLI, con una excepción.
- La E/S de archivos sobre bind mounts es casi nativa en Linux. En macOS y Windows, Docker Desktop y Podman Desktop pasan por una VM, y las instalaciones pesadas de
node_modulesson notablemente más lentas ahí. Manténnode_modulesdentro del sistema de archivos del contenedor en lugar de en un bind mount si eso te duele. - La CPU y la memoria se comparten con el host en Linux: sin impuesto de VM.
- Las peticiones de red a las APIs de IA salen directamente. La latencia la domina el modelo, no el contenedor.
El factor de rendimiento real es el modelo y tu red, no el motor de contenedores.
¿Es esto tan seguro como una VM?
No, y quien te diga lo contrario te está vendiendo algo.
Un contenedor comparte el kernel del host. Los exploits de kernel, las capacidades mal configuradas y los flags privilegiados son vectores de fuga del contenedor. Lo que esta configuración te da es protección contra el modo de fallo realista: un agente que ejecuta rm -rf, instala un paquete global roto o reescribe la configuración de tu shell. Es una frontera muy buena para accidentes y una frontera aceptable para código malicioso.
Si necesitas ejecutar código genuinamente no confiable, usa una VM (o una microVM). Si necesitas que un agente trabaje en tus propios proyectos, un contenedor no root, con capacidades limitadas y montajes acotados es el punto correcto de coste/beneficio.
¿Cuánta RAM y disco usa esto?
Para el contenedor del agente por sí solo: aproximadamente 1-2 GB de RAM en reposo con unas cuantas CLI instaladas, y 3-5 GB de disco para la imagen base más las cachés de node_modules. Funciona bien en un VPS de 2 GB si no estás ejecutando también modelos locales.
Añade Ollama y las cuentas cambian por completo: un modelo de 7B a Q4 son ~5 GB, un modelo de 14B son ~9-10 GB, antes de contar la caché KV. No ejecutes modelos locales en nada con menos de 16 GB de RAM si quieres que sean utilizables.
¿Necesito actualizar el contenedor cuando una CLI de IA se actualiza?
No. Las reconstrucciones son solo para las versiones del SO base, Node y Python.
# Inside the container
claude update
amp update
droid update
# npm-installed tools
npm update -g @google/gemini-cli
npm update -g @github/copilotTodo lo instalado en ~/dev-home (que es donde los instaladores nativos ponen sus binarios y donde los autoactualizadores escriben) persiste entre reinicios del contenedor. Un docker compose down seguido de up no lo pierde.
¿Puedo usar esta configuración en CI/CD?
Sí, y encaja mejor que un contenedor de agente de larga vida porque el runner ya es efímero. El patrón: compila la imagen, ejecuta el agente con el repositorio montado, captura el diff.
# .github/workflows/ai-review.yml
name: AI Code Review
on: [pull_request]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build AI tools image
run: docker compose build
- name: Run AI review
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
docker compose up -d
docker exec ai-tools claude -p "Review this PR for code quality and list any bug risks"Dos cosas con las que tener cuidado: no des a los agentes de CI credenciales de escritura en tu repo (deben revisar, no hacer push), y recuerda que todo lo que el agente produce es salida no confiable: trátala como un comentario en el PR, no como una puerta que pueda bloquear merges.
¿Y si necesito una versión distinta de Node.js o Python?
Cambia la etiqueta de la imagen base. Ambos runtimes vienen de una sola imagen, así que eliges una combinación:
# Python 3.14 with Node 26 (what this guide uses)
FROM nikolaik/python-nodejs:python3.14-nodejs26-bookworm
# Python 3.13 with Node 26
FROM nikolaik/python-nodejs:python3.13-nodejs26-bookworm
# Slim variant - smaller, no build toolchain
FROM nikolaik/python-nodejs:python3.14-nodejs26-slimLas combinaciones disponibles están en la página de etiquetas de nikolaik/python-nodejs. Fija la etiqueta completa y reconstruye; no uses latest, o tu entorno “reproducible” deja de serlo en un momento arbitrario.
Ese mismo truco también es práctico: si no tienes motivo para ejecutar Python 3.14 y quieres una imagen mucho más pequeña, python3.14-nodejs26-slim más los paquetes que realmente necesitas es varios cientos de MB más liviana.
¿Dónde debería ejecutar el contenedor del agente si lo quiero en un servidor?
Cualquier VPS con 2 GB de RAM es suficiente para el contenedor del agente por sí solo. Esta es la configuración que la gente usa para “un agente en un servidor al que puedo llegar desde cualquier parte”: el contenedor corre junto a tus otros servicios, mantiene un checkout de git y te conectas con docker exec.
Una máquina de €4-5/mes es de sobra para esto. Más allá de eso estás pagando por inferencia de modelos locales, no por el contenedor.
Hetzner CloudLos precios de los VPS subieron en general en 2026: si estás reconsiderando una máquina alquilada, mira qué cambió y cuándo gana un mini PC.
Dos reglas si te vas a remoto: nunca publiques los puertos del contenedor a internet y nunca montes el socket de Docker del host dentro. Un contenedor de agente remoto con el socket de Docker montado es un shell de root remoto para cualquiera que pueda escribirle un prompt.
Conclusión
Poner las herramientas CLI de IA en contenedores te da un sandbox en el que puedes permitirte ser descuidado. No una frontera de seguridad contra un atacante decidido, sino una frontera contra el modo de fallo que realmente ocurre: un agente que ejecuta un comando que no debería.
Con lo que acabas:
- Aislamiento: los comandos de shell del agente se ejecutan contra el contenedor, no contra tus dotfiles
- Persistencia: las configuraciones y el estado de autenticación viven en
~/dev-home, los proyectos en~/websites - Reproducibilidad: el mismo Dockerfile te da a ti y a un compañero runtimes idénticos
- Reversión:
docker compose downy una reconstrucción te dejan un borrón y cuenta nueva - Múltiples configuraciones: un contenedor por contexto, con claves y montajes separados
- Un host en paz: tu Node, Python y paquetes globales de npm quedan intactos
Las tres cosas que vale la pena hacer diferente respecto a la versión de 2025 de esta guía: pasar a python3.14-nodejs26-bookworm como imagen base, dejar de instalar Ollama dentro del contenedor del agente, y no montar por bind nada que no le entregarías en un prompt de shell.
Si estás construyendo el resto de la pila, las continuaciones que más importan son Podman vs Docker comparados si aún eliges motor, la guía de configuración de OpenCode o Amp Code, agente de programación con IA para la parte de herramientas, y las mejores herramientas y agentes de programación con IA en 2026 si prefieres comparar antes de comprometerte.
¿Listo para construirlo?
mkdir -p ~/docker-ai-tools ~/dev-home ~/websites- Añade el Dockerfile y el archivo compose de los pasos 2 y 3
docker compose up -d && docker exec -it ai-tools bash- Instala el agente que quieras y autentícate
- Cierra la tapa, vete, y sabiendo que el peor caso es una reconstrucción


