Contenido

Tutorial de depuración de contenedores: aprende a usar docker logs, exec, inspect, cp y stats

Los contenedores se ejecutan en segundo plano, así que los errores no aparecen directamente en pantalla. En esta sección se recopilan las herramientas de depuración más habituales y se reproducen algunos errores comunes. Los ejemplos usan un contenedor nginx:

docker run -d --name web -p 8080:80 nginx:1.30

Ver los registros: docker logs

Puedes consultar con docker logs lo que el proceso principal del contenedor escribe en la salida estándar (stdout) y en el error estándar (stderr). Primero, envía algunas solicitudes a nginx (las rutas /page1 a /page3 no existen) y luego revisa los registros:

$ docker logs --tail 3 web
2026/10/12 23:52:59 [error] 28#28: *3 open() "/usr/share/nginx/html/page3" failed (2: No such file or directory), client: 172.18.0.1, server: localhost, request: "GET /page3 HTTP/1.1", host: "localhost:8080"
172.18.0.1 - - [12/Oct/2026:23:52:59 +0000] "GET /page3 HTTP/1.1" 404 153 "-" "curl/8.22.0" "-"
172.18.0.1 - - [12/Oct/2026:23:52:59 +0000] "GET / HTTP/1.1" 200 896 "-" "curl/8.22.0" "-"

$ docker logs --tail 2 -t web
2026-10-12T23:52:59.403434333Z 172.18.0.1 - - [12/Oct/2026:23:52:59 +0000] "GET /page3 HTTP/1.1" 404 153 "-" "curl/8.22.0" "-"
2026-10-12T23:52:59.503902089Z 172.18.0.1 - - [12/Oct/2026:23:52:59 +0000] "GET / HTTP/1.1" 200 896 "-" "curl/8.22.0" "-"
OpciónFunción
--tail 3Muestra solo las últimas 3 líneas
-tAñade al principio de cada línea la marca de tiempo registrada por Docker
-fSigue mostrando los registros nuevos (como tail -f); pulsa Ctrl + C para salir
--since 10mMuestra solo los últimos 10 minutos (también puedes indicar 2026-10-12T23:00:00)

También puedes consultar los registros de un contenedor detenido. Es el primer paso para averiguar «por qué se detuvo».

stdout y stderr

Al guardar los registros en un archivo, notarás algo extraño:

$ docker logs --since 1m web > out.log; wc -l out.log
2026/10/12 23:52:57 [notice] 1#1: using the "epoll" event method
2026/10/12 23:52:57 [notice] 1#1: nginx/1.30.5
2026/10/12 23:52:57 [notice] 1#1: built by gcc 14.2.0 (Debian 14.2.0-19)
2026/10/12 23:52:57 [notice] 1#1: OS: Linux 5.4.0-216-generic
2026/10/12 23:52:57 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2026/10/12 23:52:57 [notice] 1#1: start worker processes
2026/10/12 23:52:57 [notice] 1#1: start worker process 28
2026/10/12 23:52:59 [error] 28#28: *1 open() "/usr/share/nginx/html/page1" failed (2: No such file or directory), client: 172.18.0.1, server: localhost, request: "GET /page1 HTTP/1.1", host: "localhost:8080"
2026/10/12 23:52:59 [error] 28#28: *2 open() "/usr/share/nginx/html/page2" failed (2: No such file or directory), client: 172.18.0.1, server: localhost, request: "GET /page2 HTTP/1.1", host: "localhost:8080"
2026/10/12 23:52:59 [error] 28#28: *3 open() "/usr/share/nginx/html/page3" failed (2: No such file or directory), client: 172.18.0.1, server: localhost, request: "GET /page3 HTTP/1.1", host: "localhost:8080"
13 out.log

$ docker logs --since 1m web > all.log 2>&1; wc -l all.log
23 all.log

Aunque ya hayas redirigido la salida con >, los mensajes de inicio de nginx y los registros de error siguen apareciendo en pantalla, y el archivo solo contiene 13 líneas. Esto se debe a que docker logs mantiene separados stdout y stderr: los registros de acceso de nginx se escriben en stdout, mientras que los errores y los mensajes de inicio se escriben en stderr. > solo redirige stdout; añade 2>&1 para guardar ambos flujos en el archivo (23 líneas). Lo mismo se aplica al filtrar registros con grep: docker logs web 2>&1 | grep error.

Si un programa escribe los registros en un archivo dentro del contenedor (por ejemplo, /var/log/app.log), docker logs no podrá mostrarlos. Las imágenes oficiales suelen redirigir los registros a stdout / stderr; también se recomienda que los programas que escribas los envíen directamente a stdout.

Entrar en un contenedor: docker exec

Ejecuta comandos dentro de un contenedor en ejecución:

$ docker exec web cat /etc/nginx/conf.d/default.conf | head -n 12
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

$ docker exec -u nginx web whoami
nginx

$ docker exec -e DEBUG=1 web sh -c "echo DEBUG=\$DEBUG"
DEBUG=1
FormaFunción
docker exec -it web bashAbre un shell interactivo (si la imagen es ligera y no incluye bash, usa sh)
docker exec web comandoEjecuta un solo comando y devuelve la salida directamente a tu terminal
docker exec -u nginx web …Ejecuta como el usuario indicado
docker exec -e KEY=VALUE web …Define una variable de entorno

exec inicia un proceso adicional; salir (exit) no detiene el contenedor.

Si el contenedor se cierra al iniciarse, no puedes usar exec porque no está en ejecución. En ese caso, puedes iniciar un contenedor nuevo con --entrypoint, sin ejecutar el servicio original, y entrar directamente para inspeccionarlo:

$ docker run --rm --entrypoint sh nginx:1.30 -c "ls /docker-entrypoint.d"
10-listen-on-ipv6-by-default.sh
15-local-resolvers.envsh
20-envsubst-on-templates.sh
30-tune-worker-processes.sh

En Docker Desktop, también puedes abrir un shell desde la pestaña Exec de la página de detalles del contenedor:

Pestaña Exec de Docker Desktop
Pestaña Exec de la página de detalles de un contenedor en Docker Desktop, equivalente a docker exec -it. Imagen: documentación oficial de Docker (Apache-2.0)

Consultar la configuración y el estado: docker inspect

docker inspect muestra la configuración y el estado completos de un contenedor (JSON). Usa --format para obtener los campos que necesitas:

$ docker inspect --format '{{.State.Status}} {{.State.ExitCode}}' web
running 0

$ docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web
172.18.0.2

$ docker inspect --format '{{json .HostConfig.PortBindings}}' web
{"80/tcp":[{"HostIp":"","HostPort":"8080"}]}

$ docker inspect --format '{{.Path}} {{.Args}}' web
/docker-entrypoint.sh [nginx -g daemon off;]
Qué quieres saberCampo
Por qué se detuvo.State.ExitCode, .State.Error, .State.OOMKilled
Qué carpetas están montadas.Mounts
Variables de entorno.Config.Env
Comando que se ejecutó realmente.Path, .Args
Dirección IP.NetworkSettings.Networks
Asignación de puertos.HostConfig.PortBindings
Número de reinicios.RestartCount

Copiar archivos: docker cp

Puedes copiar archivos entre el host y un contenedor incluso cuando el contenedor está detenido:

$ docker cp web:/etc/nginx/nginx.conf ./nginx.conf

$ head -n 5 nginx.conf

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;

$ echo "<h1>copied in</h1>" > index.html

$ docker cp ./index.html web:/usr/share/nginx/html/

$ curl -s http://localhost:8080
<h1>copied in</h1>

Esto resulta útil para sacar un archivo de configuración como referencia o para introducir temporalmente un archivo y probarlo. Para un uso permanente, escríbelo en el Dockerfile o móntalo con un Volume.

Recursos y procesos: docker stats, top y diff

$ docker stats --no-stream
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT    MEM %     NET I/O          BLOCK I/O       PIDS
4a43d09d0bcb   web       0.00%     7.59MiB / 1.919GiB   0.39%     2.79kB / 2.5kB   15MB / 8.19kB   2

$ docker top web
PID                 USER                TIME                COMMAND
1180                root                0:00                nginx: master process nginx -g daemon off;
1238                101                 0:00                nginx: worker process
  • docker stats: CPU, memoria, red y E/S de disco. Si no añades --no-stream, los datos se actualizan continuamente (pulsa Ctrl + C para salir). Si no has establecido un límite de memoria, LIMIT es la memoria total del host (o de la máquina virtual de Docker Desktop).
  • docker top: procesos dentro del contenedor. La imagen de nginx no incluye el comando ps (consulta Tu primer contenedor); docker top te permite verlos desde fuera.
$ docker diff web
C /run
A /run/nginx.pid
C /etc
C /etc/nginx
C /etc/nginx/conf.d
C /etc/nginx/conf.d/default.conf
C /usr
C /usr/share
C /usr/share/nginx
C /usr/share/nginx/html
C /usr/share/nginx/html/index.html
C /var
C /var/cache
C /var/cache/nginx
A /var/cache/nginx/client_temp
A /var/cache/nginx/fastcgi_temp
A /var/cache/nginx/proxy_temp
A /var/cache/nginx/scgi_temp
A /var/cache/nginx/uwsgi_temp

docker diff enumera los archivos que se añadieron (A), modificaron (C) o eliminaron (D) en la capa escribible del contenedor con respecto a la imagen original. Puedes ver que index.html, que acabas de copiar con docker cp, aparece como modificado, mientras que nginx modificó automáticamente default.conf al iniciarse (para habilitar IPv6). Todos estos cambios desaparecen al eliminar el contenedor.

Casos frecuentes

El contenedor se cierra justo después de iniciarse

$ docker run -d --name broken postgres:18
Unable to find image 'postgres:18' locally
18: Pulling from library/postgres
ecc510c1e359: Already exists
4a3cafbebf6d: Pull complete
28ced28a309b: Pull complete
ab69e2551985: Pull complete
1f04dfa92b9f: Pull complete
5a487c9461a5: Pull complete
e0d4a6e58470: Pull complete
496ce2ef5b2f: Pull complete
c8145e79cc2b: Pull complete
d23c975c4693: Pull complete
06ea90d3bcc9: Pull complete
dc25540d793f: Pull complete
7967f593457f: Pull complete
Digest: sha256:74935e72241653ca55e0414067e6d8763aceb8a810eb51b452253ec3dcfc4336
Status: Downloaded newer image for postgres:18
05a9084084fc2b744e4414e95323159bbc7455a1872784176f7333d31e875bda

$ docker ps -a --filter name=broken
CONTAINER ID   IMAGE         COMMAND                  CREATED         STATUS                     PORTS     NAMES
05a9084084fc   postgres:18   "docker-entrypoint.s…"   4 seconds ago   Exited (1) 4 seconds ago             broken

$ docker logs broken
Error: Database is uninitialized and superuser password is not specified.
       You must specify POSTGRES_PASSWORD to a non-empty value for the
       superuser. For example, "-e POSTGRES_PASSWORD=password" on "docker run".

       You may also use "POSTGRES_HOST_AUTH_METHOD=trust" to allow all
       connections without a password. This is *not* recommended.

       See PostgreSQL documentation about "trust":
       https://www.postgresql.org/docs/current/auth-trust.html

docker run -d imprime un ID de contenedor y podrías pensar que todo salió bien, pero docker ps -a muestra que se cerró (Exited (1)) unos segundos después. docker logs te indica directamente la causa: no se configuró POSTGRES_PASSWORD. El orden de diagnóstico es siempre: consulta el estado con docker ps -a → averigua la causa con docker logs.

El puerto ya está en uso

$ docker run -d -p 8080:80 nginx:1.30
c7b7a64eab9314885fb41d5ac82352c519240a17ea2e2e53098a0536a3264e6c
docker: Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint youthful_aryabhata (aff407d4a63df5baee04263bfdc54732e45cb69016b909a8e809c9010d81e926): Bind for 0.0.0.0:8080 failed: port is already allocated

Run 'docker run --help' for more information

El contenedor web ya está usando el puerto 8080 del host. Fíjate en que la primera línea sigue mostrando un ID de contenedor: el contenedor ya se creó, pero no pudo iniciarse y permanecerá en estado Created en docker ps -a. Recuerda eliminarlo. Para solucionarlo, cambia el puerto del host (-p 8081:80) o averigua qué lo está usando (docker ps; si es un programa que no pertenece a Docker, usa sudo lsof -i :8080).

Error de formato del ejecutable

$ docker run --rm --platform linux/arm64 alpine:3.23 uname -m
Unable to find image 'alpine:3.23' locally
3.23: Pulling from library/alpine
ace1621be7ff: Pull complete
Digest: sha256:85fe1e81d6758c208f3e1eed4338a1997e19d4be002d4dd32d3100c9a8c010a0
Status: Image is up to date for alpine:3.23
exec /bin/uname: exec format error

Este host es amd64 (x86_64) y se ha forzado la ejecución de una imagen arm64. El programa no puede ejecutarse en esta CPU. En la práctica, lo más habitual es construir una imagen en un Mac con Apple Silicon e intentar ejecutarla en un host en la nube x86 convencional. Consulta la solución en Imágenes multiplataforma.

Memoria insuficiente (OOM)

Cuando el contenedor supera el límite establecido con --memory, el kernel de Linux termina el proceso a la fuerza:

  • docker ps -a muestra Exited (137).
  • docker inspect --format '{{.State.OOMKilled}}' nombre-del-contenedor muestra true.

Si aparece el código 137 pero OOMKilled es false, normalmente el proceso terminó a la fuerza por docker kill o porque docker stop agotó el tiempo de espera. Aumenta --memory o comprueba si el programa tiene una fuga de memoria.

no hay espacio en el dispositivo

Docker está usando demasiado espacio en disco. Primero, ejecuta docker system df para comprobarlo:

$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          6         2         1.026GB   757.3MB (73%)
Containers      3         2         122.9kB   4.096kB (3%)
Local Volumes   1         0         0B        0B
Build Cache     0         0         0B        0B

RECLAIMABLE indica el espacio que puedes liberar. Para saber cómo, consulta Liberar espacio en disco.

Resumen del proceso de diagnóstico

  1. docker ps -a: ¿El contenedor está en ejecución? ¿Cuál es su código de salida?
  2. docker logs contenedor (si hace falta, añade 2>&1 | grep): ¿Qué dice el propio programa?
  3. Comprueba el código de salida: 1 consulta los registros, 127 indica que no se encontró el comando, 137 que se forzó la terminación o hubo un OOM, y 139 que el programa se bloqueó (segfault).
  4. docker inspect: comprueba que las variables de entorno, los montajes, los puertos y el comando ejecutado coincidan con lo esperado.
  5. docker run -it --rm --entrypoint sh imagen: entra y ejecuta los comandos manualmente para averiguar qué falta.

Resumen

  • Primero comprueba el estado y el código de salida con docker ps -a; después, consulta docker logs.
  • Entra en un contenedor en ejecución con docker exec y extrae archivos con docker cp.
  • Consulta la configuración y el estado con docker inspect -f, y el uso de recursos con docker stats.
  • Si un contenedor se cierra justo después de iniciarse, inicia uno nuevo con --entrypoint sh para diagnosticarlo manualmente.

Ejercicios

Ejercicio 1: Ejecuta docker run --name broken alpine:3.23 ls /not-exist y usa los métodos de este capítulo para averiguar por qué falla y cuál es su código de salida.

Respuesta
docker ps -a --filter name=broken
docker logs broken
docker inspect -f "{{.State.ExitCode}}" broken

Los registros muestran ls: /not-exist: No such file or directory; el código de salida es 1.

Ejercicio 2: Inicia docker run -d --name w nginx:1.30-alpine, crea un archivo dentro del contenedor y, después, ¿qué comando puedes usar para enumerar los archivos que han cambiado en el contenedor respecto a la imagen?

Respuesta

docker diff w. A significa añadido, C modificado y D eliminado. Consulta Cómo funciona internamente para conocer el principio.

Preguntas frecuentes

¿Qué hago si el contenedor se cierra al iniciarse y no puedo entrar con exec?

Primero revisa docker logs contenedor; después, inicia un contenedor nuevo con docker run --rm -it --entrypoint sh imagen y ejecuta manualmente el comando original para ver el error.

¿Hay demasiados registros en docker logs?

Usa --tail 50 para ver solo las últimas 50 líneas o --since 10m para ver solo los últimos 10 minutos; luego filtra con 2>&1 | grep palabra_clave. Consulta todas las opciones en [docker logs](/es/docker/docker-logs/).

¿Dónde puedo consultar qué significa un mensaje de error?

Consulta [Guía completa de mensajes de error comunes](/es/docker/errors/), con la salida real y la solución de 25 errores.

Actualizado 14 de octubre de 2026