Debugging y trabajo diario
Cuando algo falla, borrar el contenedor y repetir comandos al azar destruye pistas. Una investigación útil empieza comparando lo esperado con lo observado.
algo falla
↓
¿qué esperaba y qué ocurre?
↓
estado → logs → configuración → puertos/montajes/red
↓
hipótesis
↓
un cambio
↓
volver a comprobar
En los ejemplos de diagnóstico, app o tastematch-app representarían el contenedor que investigamos; no son contenedores que deban seguir existiendo de unidades anteriores. Usaremos <contenedor>, <otro-contenedor>, <imagen>, <volumen> y <red> como marcadores. Sustitúyelos, incluidos los signos < y >, por los nombres reales de tu escenario antes de ejecutar cada comando.
Los seis casos ilustran cómo investigar; el laboratorio final sí prepara recursos concretos y utiliza sus nombres reales.
Conserva la evidencia
Antes de cambiar nada, anota:
- comando ejecutado;
- nombre e imagen del contenedor;
- resultado esperado;
- síntoma exacto;
- momento en que empezó.
No elimines todos los recursos ni reinicies Docker como primera reacción. Una causa concreta necesita evidencia concreta.
Primera pregunta: ¿qué estado tiene?
docker ps
docker ps -a
docker ps muestra los contenedores en ejecución; docker ps -a incluye también los detenidos y los que no llegaron a iniciarse correctamente.
Estados habituales:
| Estado | Pregunta útil |
|---|---|
running | ¿El proceso está activo pero falla el acceso o su función? |
exited | ¿Por qué terminó el proceso principal? |
created | ¿Falló antes de empezar, por ejemplo al preparar un puerto o montaje? |
El contenedor vive mientras vive su proceso principal. Un proceso que completa su trabajo puede terminar correctamente; un servidor que termina inmediatamente suele necesitar investigación.
Los logs cuentan lo que produjo el proceso
docker logs <contenedor>
docker logs --tail 50 <contenedor>
docker logs -f <contenedor>
logsconsulta la salida disponible;--tail 50limita las líneas recientes;-fsigue mensajes nuevos hasta detener el seguimiento conCtrl+C.
Busca el primer mensaje relevante, no solo la última consecuencia. Un log vacío tampoco demuestra que todo funcione: quizá el proceso no escribe allí o no llegó a iniciarse.
run crea; exec investiga lo existente
docker run → crea e inicia otro contenedor
docker exec → ejecuta un comando en uno que ya está running
Para comprobar un valor concreto:
docker exec <contenedor> printenv APP_MODE
Para abrir una shell cuando realmente ayude:
docker exec -it <contenedor> sh
Algunas imágenes contienen bash; otras solo sh, y otras ninguna shell. No asumas que una herramienta existe. Dentro puedes comprobar rutas, archivos, procesos accesibles y variables específicas, sin convertir cambios manuales en la solución definitiva.
Salir de la shell no detiene el contenedor:
exit
docker inspect: configuración efectiva
docker inspect <contenedor>
inspect puede mostrar variables y otros datos de configuración, incluidos datos sensibles. Revisa su salida completa antes de compartirla.
Permite revisar, entre otras cosas:
- imagen utilizada;
- comando y argumentos;
- variables configuradas;
- montajes;
- publicaciones;
- redes;
- estado y código de salida.
También puedes inspeccionar cada recurso:
docker image inspect <imagen>
docker volume inspect <volumen>
docker network inspect <red>
Usa la salida para responder una pregunta. No necesitas leer todos los campos en cada incidente.
Recursos básicos con docker stats
docker stats <contenedor>
Muestra consumo en tiempo real. Puede ayudar si sospechas uso anómalo de CPU o memoria, pero un número alto no explica por sí solo la causa. Termina la vista con Ctrl+C y relaciónala con estado y logs.
Diagnosticar por capas
Recorre el sistema de fuera hacia dentro:
1. Imagen
- ¿existe el tag esperado?;
- ¿se reconstruyó después del cambio?;
- ¿el contenedor usa esa imagen?
docker image ls
docker inspect <contenedor>
2. Contenedor
- ¿existe?;
- ¿está running, exited o created?;
- ¿qué comando principal ejecuta?;
- ¿qué código de salida registra?
3. Configuración
- ¿recibió las variables necesarias?;
- ¿sus valores son los esperados?;
- ¿la aplicación realmente los lee?
Evita imprimir secretos completos durante la investigación.
4. Puertos
- ¿la aplicación escucha en el puerto interno previsto?;
- ¿el contenedor está activo?;
- ¿existe la publicación correcta?;
- ¿la URL usa el puerto del host?
docker ps
docker port <contenedor>
5. Persistencia y archivos
- ¿el montaje apunta al origen y destino correctos?;
- ¿oculta contenido previo?;
- ¿es de solo lectura?;
- ¿el proceso tiene permisos adecuados?
6. Red
- ¿ambos contenedores comparten red?;
- ¿se usa el nombre correcto?;
- ¿se confundió
localhostcon otro servicio?; - ¿se usa el puerto interno del servicio?
Caso 1: el contenedor termina inmediatamente
Síntoma:
docker ps
# no aparece
docker ps -a
# <contenedor> aparece como Exited
Recoge:
docker logs <contenedor>
docker inspect <contenedor>
Posibles causas incluyen variable obligatoria ausente, archivo no encontrado o comando que termina. Si los logs dicen Falta DB_HOST, añade únicamente la configuración necesaria en una nueva ejecución y comprueba de nuevo. No cambies puertos y redes a la vez.
Caso 2: está running pero no responde
Secuencia:
- confirma
running; - revisa logs de arranque;
- identifica el puerto interno real;
- consulta
docker port; - comprueba URL, protocolo y puerto del host.
Un EXPOSE 3000 sin -p no publica. Una publicación 8080:3000 requiere navegar al 8080 del host.
Caso 3: la aplicación no llega al otro contenedor
Evidencias:
docker network inspect <red>
docker inspect <contenedor>
docker inspect <otro-contenedor>
Comprueba membresía, hostname y puerto. Si DB_HOST=localhost, la aplicación se busca a sí misma. Cambia una sola causa: utiliza el nombre del servicio compartiendo la red y vuelve a observar logs.
Caso 4: faltan archivos
Pregunta dónde debían incorporarse:
- ¿estaban en el contexto?;
- ¿los excluyó
.dockerignore?; - ¿los copió el Dockerfile?;
- ¿un montaje oculta la ruta?;
docker exec <contenedor> sh -c 'ls -la /app'
docker inspect <contenedor>
No reconstruyas repetidamente sin identificar qué etapa debía aportar el archivo.
Caso 5: Permission denied
Identifica el proceso, la ruta y la operación. Revisa si el montaje es readonly, propietario y permisos. No uses chmod 777: amplía permisos indiscriminadamente y no explica la incompatibilidad.
Caso 6: cambié código pero veo lo anterior
Comprueba la cadena:
archivo modificado
↓
imagen reconstruida con el tag esperado
↓
contenedor nuevo creado desde esa imagen
Un contenedor anterior no cambia al reconstruir. Un bind mount puede mostrar el archivo del host, mientras que COPY requiere un build nuevo. Averigua cuál de los dos mecanismos utiliza el caso.
Rutina reutilizable
- Define el resultado esperado.
- Reproduce el síntoma una vez.
- Consulta
ps -a. - Lee logs relevantes.
- Inspecciona configuración efectiva.
- Comprueba puertos, montajes y red según el síntoma.
- Formula una hipótesis verificable.
- Cambia una sola cosa.
- Repite la comprobación original.
- Registra causa y solución.
Errores al depurar
- cambiar muchas cosas impide saber cuál resolvió el problema;
- reconstruir siempre con
--no-cacheoculta qué se reutilizaba; - eliminar todos los recursos destruye evidencia y puede borrar datos;
- ignorar logs obliga a adivinar;
- modificar el contenedor manualmente crea un arreglo irreproducible;
- asumir «Docker está roto» detiene la investigación;
- imprimir entornos completos puede exponer secretos en terminales o informes.
Mini reto: laboratorio de incidencias
Los siguientes casos ya contienen el fallo que vas a investigar. Trabaja con uno cada vez: no dependen de TasteMatch ni de recursos de prácticas anteriores. Los comandos preparan las incidencias; tú eliges las comprobaciones de diagnóstico con las herramientas de esta unidad.
Usa una carpeta nueva llamada laboratorio-debug y ejecuta desde ella los comandos, salvo cuando se indique otra carpeta. Los nombres dk107-* deben estar libres. Necesitarás Docker en marcha y los puertos del host 8087, 8088 y, para la ampliación, 8089 libres. Si alguno está ocupado, cambia solo el puerto del host y la URL correspondiente.
En cada incidencia registra antes de limpiar:
síntoma
→ evidencia (estado, log, inspect o comprobación concreta)
→ hipótesis
→ causa confirmada
→ un único cambio
→ comprobación del resultado esperado
Si necesitas recrear un contenedor para aplicar una corrección, conserva primero la evidencia y modifica solo el parámetro relacionado con tu hipótesis. No cambies al mismo tiempo código, red, puertos y montajes.
A. Configuración ausente
Dentro de laboratorio-debug, crea la carpeta configuracion con estos dos archivos.
app.js:
const http = require("http");
const message = process.env.WELCOME_MESSAGE;
if (!message) {
console.error("Falta WELCOME_MESSAGE: proporciona el mensaje de bienvenida.");
process.exit(1);
}
http.createServer((_request, response) => {
response.end(message);
}).listen(3000, "0.0.0.0", () => {
console.log(`Aplicación preparada: ${message}`);
});
Dockerfile:
FROM node:22
WORKDIR /app
COPY app.js ./
CMD ["node", "app.js"]
Desde laboratorio-debug, prepara el fallo:
docker build -t dk107-config:1.0 ./configuracion
docker run -d --name dk107-config dk107-config:1.0
Resultado esperado: el servidor permanece activo y registra que está preparado. Síntoma: el contenedor termina antes de atender peticiones.
Localiza la causa mediante su estado y sus logs. Corrige únicamente la configuración de ejecución, usando Hola desde el laboratorio como mensaje. Comprueba que el contenedor permanece activo y que el log confirma el arranque, sin editar ni reconstruir la imagen. No necesitas publicar un puerto para esta comprobación.
B. Conectividad y nombre
Prepara un servidor y una petición desde un cliente en la misma red:
docker network create dk107-red
docker run -d --name dk107-servidor --network dk107-red nginx:alpine
docker run --name dk107-cliente --network dk107-red curlimages/curl:8.12.1 http://dk107-servdor
Resultado esperado: el cliente recibe el HTML de Nginx. Síntoma: la petición falla al resolver el nombre.
El hostname de la petición contiene un error deliberado. Reúne evidencia de qué contenedores participan en la red y de qué nombre tiene realmente el servidor. Corrige únicamente el hostname de la petición y repítela desde un cliente en esa misma red. Conserva primero la evidencia del cliente fallido antes de retirarlo para reutilizar su nombre. No publiques puertos ni cambies la red del servidor.
C. Está activo, pero la web no responde
Este caso utiliza su propia red y otro servidor:
docker network create dk107-puertos
docker run -d --name dk107-web --network dk107-puertos -p 8087:81 nginx:alpine
Abre http://localhost:8087. Esperamos la página de Nginx, pero la publicación apunta deliberadamente a un puerto donde este servidor no escucha. La imagen utilizada sirve HTTP en el puerto interno 80.
Demuestra con evidencia que el contenedor está running, identifica la publicación efectiva y comprueba que el servidor responde desde un cliente curlimages/curl:8.12.1 conectado a dk107-puertos, usando su nombre y el puerto interno del servicio. Haz ese cliente efímero con --rm.
Después corrige únicamente el puerto interno de la publicación al recrear dk107-web y repite la comprobación en el navegador. ¿Qué evidencia permite descartar que Docker o Nginx estén completamente detenidos?
D. Un montaje oculta el archivo esperado
Desde laboratorio-debug, crea una carpeta nueva y vacía:
mkdir vacio
Móntala deliberadamente sobre la carpeta que contiene la página de bienvenida de Nginx:
docker run -d --name dk107-archivos -p 8088:80 --mount type=bind,src="$PWD/vacio",dst=/usr/share/nginx/html,readonly nginx:alpine
$PWD representa la carpeta actual en Bash y PowerShell; también puedes sustituir el origen por su ruta absoluta. Comprueba que apunta a la carpeta vacio que acabas de crear.
Abre http://localhost:8088. Esperamos la página de bienvenida, pero obtenemos un error HTTP, normalmente 403 Forbidden.
Utiliza inspect para identificar el montaje y exec para comprobar qué archivos ve el contenedor en esa ruta. La imagen incluye index.html, pero el directorio vacío del host lo oculta. Si necesitas comparar, puedes crear un contenedor efímero de nginx:alpine sin ese montaje y consultar sus archivos con ls.
Corrige únicamente el montaje al recrear el contenedor y comprueba de nuevo la página. No copies archivos manualmente dentro del contenedor ni reconstruyas Nginx: explica por qué el archivo de la imagen no se había borrado.
E. Ampliación: sigo viendo la versión anterior
Dentro de laboratorio-debug, crea la carpeta versiones con este Dockerfile:
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html
Y este index.html:
<h1>Versión 1</h1>
Desde laboratorio-debug:
docker build -t dk107-version:1.0 ./versiones
docker run -d --name dk107-version -p 8089:80 dk107-version:1.0
Comprueba http://localhost:8089. Después cambia únicamente el contenido de index.html por:
<h1>Versión 2</h1>
Construye la segunda imagen:
docker build -t dk107-version:2.0 ./versiones
Recarga la página: sigue mostrando la versión 1. Investiga qué imagen utiliza realmente dk107-version y compárala con la que acabas de construir. Corrige únicamente la imagen elegida al crear el contenedor que sirve esa página y comprueba el resultado. No añadas un bind mount.
Limpieza del laboratorio
Cuando hayas registrado las evidencias, detén los servidores que sigan activos y elimina únicamente los contenedores de los casos realizados: dk107-config, dk107-cliente, dk107-servidor, dk107-web, dk107-archivos y, si hiciste la ampliación, dk107-version. Los clientes adicionales y las comprobaciones de archivos deben ser efímeros con --rm.
Retira después las redes dk107-red y dk107-puertos, ya sin contenedores conectados, y las imágenes locales dk107-config:1.0, dk107-version:1.0 y dk107-version:2.0 que hayas construido. No uses limpiezas globales ni elimines imágenes base que puedan utilizar otras prácticas. Puedes conservar laboratorio-debug con tus archivos y notas para repetir los casos.
El proyecto final no indicará qué comando ejecutar ante cada fallo. Esta metodología —evidencia, hipótesis, un cambio y nueva comprobación— será tu herramienta principal.