“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:
- El cliente (
docker, lo que escribes) le pide al demonio (dockerd, el servicio que hace el trabajo) que arranque un contenedor dehello-world. - 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).
- Crea un contenedor con ella y lo arranca: su programa escribe el saludo.
- 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 4enseña solo las cuatro últimas líneas (sin él, todas).-tpone 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) |