Saltar al contenido
sudoquest@root
Docker

Contenedores

Qué es un contenedor y cómo se maneja con Docker: arrancarlo, ver qué hace, entrar en él, pararlo y borrarlo, en un servidor de verdad.

25 min de lectura

“En mi ordenador funciona”. Es la frase más repetida en cualquier equipo que hace software: el programa va bien en el portátil de quien lo escribió y falla en el servidor, porque allí hay otra versión de Python, falta una librería o la configuración es otra. Un contenedor acaba con eso: el programa viaja con todo lo que necesita (sus librerías, sus archivos, su configuración) y arranca igual en cualquier máquina que tenga Docker.

En este tema manejas contenedores en el servidor de pruebas de Pedalea, una startup de reparto en bici. Cada sección tiene un ejercicio con una terminal: el servidor es el Ubuntu de siempre (lo del track de Linux sigue valiendo) y tiene Docker instalado.

Imagen y contenedor

Dos palabras que conviene no mezclar:

  • Una imagen es la plantilla: los archivos de un sistema mínimo y el programa ya instalado (nginx, una base de datos, tu aplicación), más qué hay que ejecutar al arrancar. No cambia nunca.
  • Un contenedor es una imagen en marcha: se crea a partir de ella, tiene sus propios cambios encima y un proceso que hace el trabajo.

Como una receta y los platos: de una imagen salen tantos contenedores como quieras, cada uno con lo suyo. Las imágenes se bajan de un registro; el de Docker se llama Docker Hub y tiene imágenes oficiales de casi todo: nginx, postgres, redis, python, debian, ubuntu…

Tu primer contenedor: docker run

docker run crea un contenedor a partir de una imagen y lo arranca. La imagen hello-world existe para probar que Docker funciona:

tecnico@srv-pruebas:~$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
4f55086f7dd0: Pull complete
Digest: sha256:5e23090353324d887c48ad5e5c56d294eab81588df9605b07d1afe895f9cc8f8
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.
…

Lo que acaba de pasar, en el orden en que lo cuenta el propio mensaje:

  1. El cliente (docker, lo que escribes) le pide al demonio (dockerd, el servicio que hace el trabajo) que arranque un contenedor de hello-world.
  2. El demonio no tiene la imagen (“Unable to find image… locally”), así que la baja del registro: capa a capa (aquí, una) y comprobando su huella (el digest).
  3. Crea un contenedor con ella y lo arranca: su programa escribe el saludo.
  4. El programa termina, y con él el contenedor se para. Sigue existiendo, parado.

Fíjate en el nombre de la imagen: hello-world es en realidad hello-world:latest. Lo de después de los dos puntos es la etiqueta (la versión); si no la pones, Docker usa latest.

Ver los contenedores: docker ps

docker ps lista los contenedores en marcha. El de hello-world ya terminó, así que no sale:

tecnico@srv-pruebas:~$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

Con -a (all) salen también los parados:

tecnico@srv-pruebas:~$ docker ps -a
CONTAINER ID   IMAGE         COMMAND    CREATED         STATUS                     PORTS     NAMES
24fae9c7b875   hello-world   "/hello"   2 seconds ago   Exited (0) 2 seconds ago             great_napier
  • CONTAINER ID: su identificador (el principio; el entero tiene 64 caracteres). Para referirte a él basta con su principio, si no hay otro igual.
  • IMAGE y COMMAND: de qué imagen salió y qué programa ejecuta.
  • STATUS: Up … si está en marcha; Exited (0) si terminó, con el código de salida de su programa entre paréntesis (0 es que fue bien).
  • NAMES: su nombre. Si no le pones uno, Docker inventa uno con un adjetivo y el apellido de alguien de la ciencia, como great_napier.

Ejercicio

Tu primer contenedor

El servidor de pruebas acaba de estrenar Docker. Comprueba que funciona con la imagen de prueba de Docker.

Preparando la máquina…

En segundo plano: -d y --name

hello-world hace su trabajo y termina, pero la mayoría de contenedores son servidores: una web, una base de datos. Esos tienen que quedarse en marcha, y no quieres que te ocupen la terminal. Con -d (detach) el contenedor arranca en segundo plano, y con --name le das un nombre que puedas recordar:

tecnico@srv-pruebas:~$ docker run -d --name web nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
6b37362b3da7: Pull complete
…
Status: Downloaded newer image for nginx:latest
dac0fac0c2c0f982a72e0db23629dd09d464f767fb73f864b45b1a8751b12a57

Lo último que escribe es el id entero del contenedor nuevo. Y ahora sí sale en docker ps:

tecnico@srv-pruebas:~$ docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED        STATUS        PORTS     NAMES
dac0fac0c2c0   nginx     "/docker-entrypoint.…"   1 second ago   Up 1 second   80/tcp    web

La columna PORTS dice que nginx escucha en el puerto 80 del contenedor. Desde fuera todavía no se llega a él: publicar puertos es del tema 3.

Variables de entorno: -e

Algunas imágenes necesitan un dato para arrancar. La de postgres (una base de datos), por ejemplo, no arranca sin la contraseña de su administrador: se para nada más empezar, y su docker logs dice qué le falta. Ese dato se le pasa con -e, como una variable de entorno NOMBRE=valor:

tecnico@srv-pruebas:~$ docker run -d --name db -e POSTGRES_PASSWORD=secreto postgres
3f1c9a2b7e4d5a6c8b0f1e2d3c4b5a69788f6e5d4c3b2a1f0e9d8c7b6a5f4e3d
  • Cada variable, con su -e (se pueden poner varias).
  • Qué variables pide una imagen lo dice su documentación; y si falta alguna, sus logs.

Ejercicio

Un servidor en segundo plano

Hace falta una web de pruebas: un nginx que se quede en marcha, con un nombre que se pueda recordar.

Preparando la máquina…

¿Qué ha pasado? docker logs

Lo que un contenedor escribe en pantalla no se pierde: Docker lo guarda, y docker logs lo enseña. Es lo primero que se mira cuando algo no arranca o hace cosas raras:

tecnico@srv-pruebas:~$ docker logs --tail 4 web
2026/10/06 10:00:03 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 65536:65536
2026/10/06 10:00:03 [notice] 1#1: start worker processes
2026/10/06 10:00:03 [notice] 1#1: start worker process 29
2026/10/06 10:00:03 [notice] 1#1: start worker process 30
  • --tail 4 enseña solo las cuatro últimas líneas (sin él, todas).
  • -t pone delante de cada línea la hora exacta en que se escribió.
  • Funciona también con los contenedores parados: si uno terminó con un error, sus logs dicen por qué.

Ejercicio

¿Qué ha pasado esta noche?

Anoche alguien dejó un contenedor que avisaba de cómo había ido la copia. Ya terminó: lee lo que dijo.

Preparando la máquina…

Dentro del contenedor: docker exec

docker exec ejecuta algo dentro de un contenedor que está en marcha. Con -it (-i para que lea lo que escribes y -t para darte una terminal) y bash, entras en él:

tecnico@srv-pruebas:~$ docker exec -it web bash
root@dac0fac0c2c0:/# hostname
dac0fac0c2c0

El prompt ha cambiado: eres root en una máquina que se llama como el id del contenedor. Estás en otro Linux, el de la imagen, con sus propios archivos. La de nginx está hecha sobre Debian:

root@dac0fac0c2c0:/# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
NAME="Debian GNU/Linux"
…

Y le falta casi todo lo que no necesita para servir webs. Las imágenes son mínimas a propósito (menos que bajar y menos que pueda fallar), así que hasta ps falta:

root@dac0fac0c2c0:/# ps
bash: ps: command not found

Con exit sales de su bash y vuelves al servidor. El contenedor sigue en marcha: solo has cerrado tu terminal dentro de él.

Para una orden suelta no hace falta entrar: sin -it, docker exec la ejecuta y vuelve.

tecnico@srv-pruebas:~$ docker exec web ls /usr/share/nginx/html
50x.html
index.html

Ejercicio

Dentro del contenedor

La web de pruebas está en marcha en el contenedor web. Entra en él y mira la página que sirve.

Preparando la máquina…

Parar, arrancar y borrar

docker stop para un contenedor: le avisa a su programa con una señal (SIGTERM, o la que diga su imagen) para que termine bien, como un apagado ordenado. docker start arranca el mismo contenedor otra vez, con todo lo que tenía:

tecnico@srv-pruebas:~$ docker stop web
web
tecnico@srv-pruebas:~$ docker ps -a
CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS                      PORTS     NAMES
dac0fac0c2c0   nginx         "/docker-entrypoint.…"   11 seconds ago   Exited (0) 1 second ago               web
24fae9c7b875   hello-world   "/hello"                 14 seconds ago   Exited (0) 14 seconds ago             great_napier
tecnico@srv-pruebas:~$ docker start web
web

docker restart hace las dos cosas seguidas.

Si el programa no hace caso del aviso, Docker espera 10 segundos y lo mata con SIGKILL. Pasa, por ejemplo, con un contenedor que solo ejecuta sleep: tarda en pararse y termina con el código 137 (128 + 9, el número de SIGKILL):

tecnico@srv-pruebas:~$ docker ps -a
CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS                      PORTS     NAMES
e0423228857a   debian        "sleep infinity"         12 seconds ago   Exited (137) 1 second ago             colgado
…

Un Exited (137) quiere decir “no paró solo: lo mataron”. Si no lo paraste tú, conviene mirar sus logs.

docker rm borra un contenedor (y todo lo que tenía dentro). Uno en marcha no se deja:

tecnico@srv-pruebas:~$ docker rm web
Error response from daemon: cannot remove container "/web": container is running: stop the container before removing or force remove

O lo paras antes, o lo fuerzas con docker rm -f (que lo mata sin avisar). Y para los de usar y tirar, docker run --rm lo borra solo en cuanto termina.

Ejercicio

Parar y arrancar

Hay que parar la web de pruebas un momento (para cambiarle la configuración) y volver a ponerla en marcha.

Preparando la máquina…

Ejercicio

Limpiar lo que sobra

En el servidor quedan dos contenedores de pruebas que ya no hacen falta: uno parado y otro todavía en marcha. Bórralos sin tocar la web.

Preparando la máquina…

Todos sus datos: docker inspect

docker inspect enseña todo lo que Docker sabe de un contenedor, en JSON: cuándo se creó, su estado, su configuración, su IP… Son cientos de líneas; con -f (format) eliges el dato:

tecnico@srv-pruebas:~$ docker inspect -f '{{.State.Status}}' web
running
tecnico@srv-pruebas:~$ docker inspect -f '{{.NetworkSettings.IPAddress}}' web
172.17.0.2

Quién puede usar Docker

El cliente habla con el demonio por un archivo especial, el socket /var/run/docker.sock, que es de root y del grupo docker. Quien no es ninguna de las dos cosas recibe un permission denied while trying to connect to the Docker daemon socket. Para que alguien use Docker sin sudo se le mete en el grupo (sudo usermod -aG docker ana) y tiene que volver a entrar para que valga, como con cualquier grupo.

Resumen

Orden Qué hace
docker run imagen crea un contenedor y lo arranca (bajando la imagen si hace falta)
docker run -d --name x imagen lo mismo, en segundo plano y con nombre
docker run --rm … lo borra solo al terminar
docker ps / docker ps -a los contenedores en marcha / también los parados
docker logs x lo que ha escrito (--tail N, -t)
docker exec -it x bash una terminal dentro (exit para salir)
docker exec x orden una orden suelta dentro
docker stop x / start x / restart x pararlo, arrancarlo, las dos cosas
docker rm x / docker rm -f x borrarlo / aunque esté en marcha
docker container prune borrar todos los parados
docker inspect x todos sus datos (-f para uno)