Apuntes DAM
Tema 4 de 7DAW · 2º DAW

Despliegue de Aplicaciones Web

Contenedores con Docker

Contenedores frente a máquinas virtuales, imágenes, volúmenes y redes, Dockerfile con buenas prácticas y construcción en varias etapas, y Docker Compose para levantar la aplicación con su base de datos.

60 min lecturaIntermedio

1. El fin de «en mi ordenador funciona»

Un contenedor empaqueta una aplicación junto con todo lo que necesita para ejecutarse: el intérprete en la versión exacta, las librerías del sistema, las dependencias y la configuración por defecto. Ese paquete se ejecuta igual en el portátil de cualquier desarrollador, en el servidor de pruebas y en producción. Docker popularizó la idea en 2013 y hoy es la forma estándar de distribuir y desplegar aplicaciones.

Los contenedores de los barcos

Una caja estándar
Antes cada mercancía se cargaba a su manera. El contenedor estandarizó la caja y da igual lo que lleve dentro.
Cualquier transporte
El mismo contenedor viaja en barco, tren o camión. La misma imagen corre en cualquier máquina con Docker.
Apilables y aislados
Muchos contenedores en un mismo barco, sin mezclarse. Muchos contenedores en un servidor, cada uno a lo suyo.

Contenedores y máquinas virtuales

Una máquina virtual emula un ordenador completo con su propio sistema operativo, lo que la hace pesada (gigas de disco, minutos para arrancar). Un contenedor, en cambio, comparte el núcleo del sistema anfitrión y solo aísla los procesos, la red y los ficheros mediante funciones del núcleo de Linux (namespaces y cgroups). Por eso ocupa megas y arranca en segundos, y en un servidor caben decenas.

Máquina virtualContenedor
Qué incluyeSistema operativo completoSolo la aplicación y sus dependencias
Tamaño típicoGigabytesMegabytes
ArranqueMinutosSegundos o menos
AislamientoMuy fuerte (núcleo propio)Bueno, pero comparte el núcleo
Uso típicoDistintos sistemas operativos, aislamiento máximoDesplegar aplicaciones y sus servicios
Docker - La explicación que querías — Ringa Tech

2. Imágenes, contenedores, volúmenes y redes

Cuatro conceptos bastan para entender Docker. Una imagen es la plantilla de solo lectura (como una clase): se construye a partir de un Dockerfile y se guarda en un registro como Docker Hub. Un contenedor es una instancia en ejecución de una imagen (como un objeto): puedes lanzar muchos a partir de la misma. Los contenedores son desechables, así que los datos que deben sobrevivir se guardan en volúmenes. Y las redes de Docker permiten que los contenedores se hablen entre sí por su nombre.

bash
1docker run -d --name web -p 8080:80 nginx:1.27      # descarga la imagen y lanza un contenedor
2docker ps                                            # contenedores en marcha
3docker logs -f web                                   # su salida
4docker exec -it web sh                               # abrir una terminal dentro
5docker stop web && docker rm web                     # pararlo y borrarlo
6docker images                                        # imágenes descargadas

La opción -p 8080:80 publica el puerto: lo que llegue al 8080 del anfitrión se envía al 80 del contenedor. Dos contenedores no pueden publicar el mismo puerto del anfitrión, algo que comprobarás en uno de los retos.

3. Construir tu propia imagen: el Dockerfile

Un Dockerfile es la receta de una imagen: cada instrucción añade una capa. Docker guarda en caché las capas y, al reconstruir, reutiliza todas las que no han cambiado. De ahí sale la buena práctica más importante: copiar primero solo los ficheros de dependencias e instalarlas, y copiar el resto del código después. Así, cambiar una línea de tu código no obliga a reinstalar todas las dependencias.

Dockerfile
1# Etapa 1: construir
2FROM node:22-alpine AS build
3WORKDIR /app
4COPY package.json package-lock.json ./
5RUN npm ci
6COPY . .
7RUN npm run build
8
9# Etapa 2: imagen final, solo con lo necesario para ejecutar
10FROM node:22-alpine
11WORKDIR /app
12ENV NODE_ENV=production
13COPY package.json package-lock.json ./
14RUN npm ci --omit=dev
15COPY --from=build /app/dist ./dist
16USER node
17EXPOSE 3000
18HEALTHCHECK CMD wget -qO- http://localhost:3000/salud || exit 1
19CMD ["node", "dist/server.js"]
  • Versión concreta en FROM (node:22-alpine, nunca latest): la imagen debe ser reproducible.
  • Construcción en varias etapas: las herramientas de compilación se quedan en la primera etapa y la imagen final es pequeña.
  • USER distinto de root: si alguien compromete la aplicación, no controla el contenedor.
  • .dockerignore con node_modules, .git y .env, para no copiar basura ni secretos a la imagen.
  • Con apt-get, instala y limpia la caché en el mismo RUN (rm -rf /var/lib/apt/lists/*).

4. Varios contenedores juntos: Docker Compose

Una aplicación real necesita varios servicios: la aplicación, la base de datos, quizá Redis y un Nginx delante. Docker Compose los describe en un único fichero YAML y los levanta juntos con docker compose up -d, creando una red común en la que cada servicio se encuentra por su nombre.

compose.yaml
1services:
2  proxy:
3    image: nginx:1.27-alpine
4    ports: ["80:80", "443:443"]
5    volumes: ["./nginx.conf:/etc/nginx/conf.d/default.conf:ro"]
6    depends_on: [app]
7
8  app:
9    build: .
10    env_file: .env                        # DB_HOST=db, DB_PASS=…
11    depends_on:
12      db: { condition: service_healthy }
13    restart: unless-stopped
14
15  db:
16    image: mariadb:11
17    environment:
18      MARIADB_DATABASE: tienda
19      MARIADB_USER: tienda
20      MARIADB_PASSWORD_FILE: /run/secrets/db_pass
21      MARIADB_RANDOM_ROOT_PASSWORD: "1"
22    secrets: [db_pass]
23    volumes: ["datos:/var/lib/mysql"]      # los datos sobreviven al contenedor
24    healthcheck:
25      test: ["CMD", "healthcheck.sh", "--connect"]
26
27volumes:
28  datos:
29
30secrets:
31  db_pass: { file: ./secretos/db_pass.txt }

Fíjate en que solo el proxy publica puertos: la aplicación y la base de datos quedan en la red interna, sin exponerse a internet. Para escalar a muchos servidores se usan orquestadores como Kubernetes, que reparten los contenedores entre máquinas, los reinician si fallan y hacen despliegues progresivos, pero los conceptos son los mismos que acabas de ver.

Un contenedor no es una máquina virtual

No entres en un contenedor a instalar cosas o cambiar ficheros a mano: al recrearlo se pierde todo. Cualquier cambio se hace en el Dockerfile o en el compose.yaml, se reconstruye y se vuelve a desplegar.
DOCKER De NOVATO a PRO! (CURSO COMPLETO EN ESPAÑOL) — Pelado Nerd

5. Reto: revisa un Dockerfile

Las herramientas como Hadolint revisan los Dockerfile en busca de malas prácticas. En este reto programas una versión sencilla que detecta las cuatro más frecuentes.

Retos de Bash en el IDE

Escribe el script y pulsa Ejecutar tests: se ejecuta en un Linux real con Bash y se prueba con varios casos, algunos ocultos.

🖥️BashRevisar un DockerfileMedio

La entrada es un Dockerfile. Escribe un aviso por línea, en este orden, cuando corresponda: «Imagen base sin versión concreta» (algún FROM sin etiqueta o con :latest), «apt-get install sin limpiar la caché» (un RUN con apt-get install sin rm -rf /var/lib/apt/lists), «Se copia todo el código antes de instalar las dependencias» (un COPY . aparece antes del RUN que ejecuta npm install/npm ci, composer install o pip install) y «El contenedor se ejecuta como root» (no hay ninguna instrucción USER). Si no hay ningún aviso, escribe «Sin avisos».

⏳
Dockerfile mejorable
⏳
Dockerfile correcto
⏳
apt-get y latest
⏳
Test oculto #4
0/4 tests pasados

¿Has terminado este tema?

Crea una cuenta gratis para guardar qué temas has terminado, subir de nivel y ganar medallas.

Guardar mi progreso