02 - Docker
Los proyectos no viven solo del código: alrededor hay una base de datos, un almacenamiento de ficheros, un proxy… Cada pieza es un servidor ajeno, con su instalación y su configuración. Docker permite empaquetar cada pieza con todo lo que necesita y ejecutarla aislada y reproducible, en cualquier máquina y sin instalar nada más.
1. Entornos que no coinciden
Instalar y configurar cada servidor a mano tiene dos problemas: el trabajo no se traslada (lo que funciona en una máquina no va igual en otra: versiones y configuraciones distintas) y las piezas comparten el sistema, así que una actualización toca a todas. Docker resuelve ambos: cada servidor se empaqueta en una imagen con su versión exacta y corre en un contenedor aislado del resto.
2. Imagen, contenedor y registro
| Concepto | Qué es |
|---|---|
| Imagen | Plantilla inmutable con el sistema de ficheros y el comando de arranque; tiene una versión (*tag*) |
| Contenedor | Proceso aislado en ejecución, creado a partir de una imagen |
| Registro | Repositorio de imágenes; Docker Hub es el público más usado |
La imagen es la plantilla; el contenedor, la instancia. De una imagen salen tantos contenedores como se quieran.
| Máquina virtual | Contenedor | |
|---|---|---|
| Qué incluye | SO invitado completo + aplicación | Solo la aplicación y sus dependencias |
| Arranque | Minutos (arranca un SO) | Milisegundos (es un proceso) |
| Coste | Gigabytes por VM | Megabytes; comparte el kernel del host |
Ambos aíslan; el contenedor comparte el kernel del host y por eso es tan ligero. Para servidores de aplicación es el enfoque dominante.
Los nombres de imagen siguen el patrón usuario/imagen:tag: usuario identifica al publicador y es opcional —si falta, la imagen es oficial—; imagen es obligatorio; tag señala la versión y también es opcional. Sin tag se descarga latest, que no siempre es la más nueva: es la versión que los creadores etiquetaron como tal. Fijar la versión (mariadb:11, no mariadb) es lo reproducible.
3. Imágenes
Docker Hub es el registro público: en la web se busca y se distinguen las imágenes oficiales. En una terminal sin entorno gráfico, lo mismo lo hace docker search.
| Comando | Qué hace |
|---|---|
docker search <imagen> | busca imágenes con ese nombre en el registro |
docker pull <imagen> | descarga la imagen |
docker images | lista las imágenes del sistema local |
docker rmi <imagen> | elimina una imagen local, por REPOSITORY:TAG o IMAGE ID |
docker tag | crea un alias de una imagen existente |
docker save / docker load | exporta o importa imágenes en un fichero .tar, sin registro |
$ docker search mariadb $ docker pull mariadb:11 $ docker images $ docker rmi mariadb:11
- La salida de
docker imagesmuestraREPOSITORY,TAG,IMAGE ID, fecha y tamaño. - Los comandos tienen variantes largas equivalentes (
docker image pull,docker image rm…); las cortas son de uso habitual. - Una imagen en uso por algún contenedor no se deja eliminar: primero hay que eliminar el contenedor.
4. Contenedores
docker run crea y arranca un contenedor desde una imagen; si no está en local, la descarga antes. Sus opciones completan la configuración del contenedor en el momento de crearlo:
| Opción | Efecto |
|---|---|
-d | en segundo plano (*detached*), sin ocupar la terminal |
-p host:contenedor | publica el puerto del contenedor en el host |
–name | nombre para referirse al contenedor sin depender del ID |
-e VAR=valor | variable de entorno: así se configura el servidor (contraseñas, rutas…) |
-v volumen:ruta | guarda datos fuera del contenedor (sección 5) |
–restart always | lo vuelve a arrancar si el contenedor o la máquina se reinician |
$ docker run -d -p 8080:80 nginx # un servidor web en el 8080 del host
$ docker run -d --name db \
-e MARIADB_ROOT_PASSWORD=secreto \
-p 3306:3306 mariadb:11
El contenedor se para y se arranca sin perderse; se elimina y ya no hay nada:
$ docker ps # contenedores en ejecución (con -a, también los parados) $ docker stop db # lo detiene $ docker start db # lo vuelve a arrancar: el mismo contenedor, con lo que tiene dentro $ docker logs -f db # sigue la salida del contenedor $ docker rm db # lo elimina (con -f también si está en marcha)
- Solo
rmdestruye el contenedor y con él lo que guardó dentro; los datos valiosos van en volúmenes (sección 5).
docker exec ejecuta comandos en un contenedor en marcha, sin detenerlo:
$ docker exec -it db bash # una terminal dentro del contenedor (para salir, exit) $ docker exec db ls /app # un comando suelto, sin terminal
-itasigna un terminal interactivo: es lo que permite abrir una shell.- El contenedor debe estar en ejecución; si no, no se puede hacer
exec.
5. Puertos y volúmenes
Puertos: -p 9000:9000 conecta el puerto del host con el del contenedor. Sin él, el servicio no es accesible desde fuera; entre contenedores no hace falta: se ven por la red interna (tema 08).
Volúmenes: el sistema de ficheros del contenedor es efímero: al eliminar el contenedor se pierde lo que guardó dentro. Un volumen vive fuera del contenedor y persiste:
$ docker run -d --name db -v db-data:/var/lib/mysql mariadb:11
Los datos de MariaDB quedan en el volumen db-data y sobreviven al contenedor.
| Forma | Sintaxis | Cuándo usarla |
|---|---|---|
| Volumen nombrado | -v db-data:/var/lib/mysql | datos que deben sobrevivir al contenedor; los gestiona Docker y no los liga a una ruta del host |
| Bind mount | -v ./html:/usr/share/nginx/html | trabajar con una carpeta concreta de la máquina: el host es dueño de los ficheros |
$ docker volume create db-data # crear el volumen por adelantado $ docker volume inspect db-data # sus datos: el Mountpoint es la carpeta real del host
- Los nombrados son la forma normal de persistir datos de servidores (bases de datos, almacenamiento).
- Los bind mounts sirven para compartir una carpeta del host con el contenedor: configuración, código, contenido estático.
6. Dockerfile
Los servidores oficiales ya vienen empaquetados; la aplicación propia se define en un Dockerfile: un fichero de texto que describe cómo se construye una imagen —de qué parte, qué añade y con qué comando arranca.
| Instrucción | Qué hace |
|---|---|
FROM | imagen base de la que se parte; toda imagen empieza aquí |
RUN | ejecuta un comando durante la construcción (instalar paquetes…) |
COPY / ADD | copia ficheros del host a la imagen (ADD admite URLs y descomprime) |
WORKDIR | directorio de trabajo para las siguientes instrucciones |
ENV | variables de entorno fijas de la imagen |
EXPOSE | documenta el puerto que usa la aplicación; no lo publica (eso lo hace -p) |
CMD | comando por defecto al arrancar el contenedor; se puede sobreescribir |
ENTRYPOINT | el programa principal, fijo; lo que se añade en docker run son sus argumentos |
FROM python:3 WORKDIR /app COPY app.py . CMD ["python", "app.py"]
$ docker build -t miapp:1.0 . # construye la imagen desde este directorio $ docker run miapp:1.0
- La imagen base oficial de python ya trae el intérprete; el Dockerfile solo añade el código y el arranque.
- El fichero se llama exactamente
Dockerfile, sin extensión: Docker lo busca con ese nombre. -tfija nombre y *tag* de la imagen (miapp:1.0); sintagtomaríalatest.- La imagen construida corre igual en cualquier máquina. Si la aplicación espere peticiones, publica su puerto como cualquier contenedor (
-p).
7. Redes
Cada contenedor nace aislado en su propia red. Para que dos contenedores se vean, comparten una red: un conmutador virtual —de tipo bridge por defecto— donde se alcanzan por el nombre del contenedor, como un hostname.
$ docker network create mi-red $ docker run -d --name db --network mi-red mariadb:11 $ docker run -d --name app --network mi-red alpine $ docker exec app ping db # db responde a su nombre: la red los une
| Comando | Qué hace |
|---|---|
network create | crea la red (bridge por defecto) |
network connect / disconnect | añade o retira un contenedor en marcha a/de la red |
network ls | lista las redes |
network inspect | detalle de la red: driver, contenedores conectados y sus IP |
network rm | elimina la red, sin contenedores conectados |
- Otros *drivers* existen —
hostcomparte la pila de red de la máquina,noneaísla del todo,overlayconecta varias máquinas—; para el uso habitual basta elbridgede siempre. - La clave del módulo está aquí: entre contenedores no son necesarias las direcciones ni los puertos publicados; los nombres bastan. Lo retoma la arquitectura del tema 08.
8. docker compose
Con varios contenedores, los comandos manuales no escalan: hay que recordar orden, puertos y variables de cada uno. Docker Compose describe el sistema completo —servicios, redes y volúmenes— en un fichero compose.yaml y lo levanta con un solo comando:
services:
web:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
db:
image: mariadb:11
environment:
MARIADB_ROOT_PASSWORD: secreto
MARIADB_DATABASE: miapp
volumes:
- db-data:/var/lib/mysql
volumes:
db-data:
- Cada entrada de
serviceses un servicio: la receta de su contenedor —imagen, puertos, entorno, volúmenes—. - Los volúmenes nombrados se declaran al final, una vez, y los servicios los comparten.
- El *bind mount* de la sección 5 se usa igual; en compose.yaml, una ruta que empieza por
.es relativa al propio fichero. - Compose crea una red propia del proyecto entre los servicios:
webalcanza adbpor su nombre, con la red de la sección 7 hecha automáticamente.
| Comando | Qué hace |
|---|---|
docker compose up -d | crea, si faltan, y arranca todos los servicios, en segundo plano |
docker compose start / stop | arranca o para los contenedores existentes, sin eliminarlos |
docker compose ps | estado de los servicios |
docker compose logs -f | la salida de todos, ordenada por servicio |
docker compose down | para y elimina contenedores y red; con -v, también los volúmenes |
docker compose down elimina contenedores pero conserva los volúmenes nombrados: los datos sobreviven. Es el modo normal de reiniciar el sistema; -v solo si se quieren borrar de verdad.
Con un solo servicio Compose parece excesivo; su valor aparece al describir sistemas con varios servidores: es el caso del tema de arquitectura (tema 08).