Hasta ahora hemos aprendido a descargar imágenes que otros han creado. En esta lección damos el paso decisivo: construir nuestras propias imágenes. Una imagen propia empaqueta tu aplicación junto con todo lo que necesita para ejecutarse (código, dependencias, configuración), de forma que pueda correr de manera idéntica en cualquier máquina con Docker. Veremos el comando docker build, qué es el contexto de construcción, cómo se relaciona con el Dockerfile, cómo etiquetar la imagen al construirla con -t, y cómo la caché de capas acelera enormemente las reconstrucciones. Terminaremos construyendo una imagen sencilla de principio a fin.

El Comando docker build

docker build es el comando que toma un conjunto de instrucciones (escritas en un fichero llamado Dockerfile) y produce una imagen. Su forma más básica es:

docker build .
  • docker build: invoca el proceso de construcción.
  • .: el contexto de construcción, normalmente el directorio actual. Este punto es muy importante y lo explicamos en detalle más abajo.

Por defecto, docker build busca un fichero llamado exactamente Dockerfile en la raíz del contexto. Si está en otra ubicación o tiene otro nombre, se indica con -f:

docker build -f docker/Dockerfile.prod .
  • -f docker/Dockerfile.prod: especifica la ruta del Dockerfile a usar.
  • .: el contexto sigue siendo el directorio actual.

El Contexto de Construcción

El contexto de construcción es el conjunto de ficheros y carpetas que Docker envía al demonio (daemon) para construir la imagen. Cuando ejecutas docker build ., Docker empaqueta todo el contenido del directorio actual y lo transfiere al motor de Docker.

Esto tiene dos consecuencias prácticas importantes:

  • Solo se pueden copiar a la imagen ficheros que estén dentro del contexto. Una instrucción como COPY ../otro/fichero . falla, porque ../otro está fuera del contexto.
  • Si el contexto contiene carpetas pesadas e innecesarias (como node_modules, .git o ficheros temporales), la construcción será más lenta porque hay que transferir más datos.

Para excluir ficheros del contexto se usa un fichero .dockerignore, similar en sintaxis a .gitignore:

node_modules
.git
*.log
dist
.env
  • Cada línea es un patrón de fichero o carpeta que no se enviará al demonio.
  • Esto acelera la construcción y evita filtrar información sensible (como .env) dentro de la imagen.

Verás algo así al inicio de la build, indicando cuánto contexto se ha transferido:

[+] Building 0.4s (8/8) FINISHED
 => transferring context: 2.3kB
  • Cuanto menor sea ese tamaño, más rápida será la transferencia. Un .dockerignore bien configurado mantiene ese número bajo.

Relación entre docker build y el Dockerfile

El Dockerfile es un fichero de texto con una receta paso a paso de cómo construir la imagen. Cada línea es una instrucción que Docker ejecuta en orden. El comando docker build lee ese fichero y va creando la imagen capa por capa.

La relación es directa:

  • El Dockerfile describe qué hay que hacer (partir de una base, copiar código, instalar dependencias, definir el comando de arranque).
  • docker build es el motor que ejecuta esas instrucciones y produce la imagen resultante.

Veamos un Dockerfile mínimo para entender la mecánica (los detalles de cada instrucción se estudian en la siguiente lección):

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]
  • FROM: imagen base de la que partimos.
  • WORKDIR: directorio de trabajo dentro de la imagen.
  • COPY: copia ficheros del contexto a la imagen.
  • RUN: ejecuta un comando durante la construcción.
  • CMD: comando que se ejecutará cuando arranque un contenedor de esta imagen.

Etiquetar al Construir con -t

Si construyes sin la opción -t, la imagen queda sin nombre y solo se identifica por un ID hexadecimal difícil de recordar. La opción -t (de tag) le asigna un nombre legible:

docker build -t miapp:1.0 .
  • -t miapp:1.0: nombra la imagen miapp con el tag 1.0.
  • Si omites el tag, Docker usa latest por defecto: docker build -t miapp . produce miapp:latest.

Puedes asignar varios nombres a la vez repitiendo -t, algo muy útil para marcar la misma imagen como una versión concreta y como latest:

docker build -t miapp:1.0 -t miapp:latest .
  • Ambos tags apuntan a la misma imagen. Así puedes referirte a ella tanto por su versión exacta como por latest.

Tras construir, comprueba que la imagen existe:

docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
miapp        1.0       a1b2c3d4e5f6   10 seconds ago   180MB
miapp        latest    a1b2c3d4e5f6   10 seconds ago   180MB
  • Observa que ambos tags comparten el mismo IMAGE ID: son la misma imagen con dos nombres.

La Caché de Capas Durante el Build

Cada instrucción del Dockerfile genera una capa de la imagen. Docker cachea el resultado de cada capa y, en construcciones posteriores, reutiliza las capas que no han cambiado en lugar de rehacerlas. Esto hace que la segunda y sucesivas builds sean mucho más rápidas.

La regla de la caché es simple: Docker reutiliza una capa si la instrucción y sus entradas son idénticas a una build anterior. En cuanto una capa cambia, todas las capas posteriores se reconstruyen, aunque sus instrucciones no hayan variado.

Observa la salida de una segunda build sin cambios:

 => CACHED [2/5] WORKDIR /app
 => CACHED [3/5] COPY package*.json ./
 => CACHED [4/5] RUN npm ci
 => CACHED [5/5] COPY . .
  • CACHED indica que esa capa se ha reutilizado de la caché, sin ejecutarse de nuevo.

Por eso el orden de las instrucciones importa muchísimo. Compara estos dos enfoques:

# Orden INEFICIENTE: el código se copia antes que las dependencias
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm ci
CMD ["node", "server.js"]
# Orden EFICIENTE: dependencias primero, código después
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]
Enfoque Qué ocurre al cambiar una línea de código
Ineficiente COPY . . cambia, así que npm ci se vuelve a ejecutar (lento)
Eficiente Solo cambia el COPY . . final; npm ci se reutiliza de la caché (rápido)
  • En el enfoque eficiente, copiamos primero package*.json (que cambia poco) e instalamos dependencias. Esa capa costosa solo se rehace cuando cambian las dependencias, no cada vez que tocas el código.

Ejemplo Completo: Construyendo una Imagen Sencilla

Vamos a construir, paso a paso, una pequeña aplicación web en Node.js.

Paso 1: Estructura del Proyecto

miweb/
├── Dockerfile
├── .dockerignore
├── package.json
└── server.js

Paso 2: El Código de la Aplicación

server.js:

const http = require('http');
const server = http.createServer((req, res) => {
  res.end('Hola desde Docker\n');
});
server.listen(3000, () => console.log('Escuchando en el puerto 3000'));

package.json:

{
  "name": "miweb",
  "version": "1.0.0",
  "main": "server.js"
}

Paso 3: El Dockerfile

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev || true
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Explicación línea a línea:

  • FROM node:18-alpine: partimos de una imagen ligera con Node.js 18.
  • WORKDIR /app: todas las instrucciones siguientes se ejecutan dentro de /app.
  • COPY package*.json ./: copiamos solo los manifiestos de dependencias primero, para aprovechar la caché.
  • RUN npm ci --omit=dev || true: instala las dependencias de producción. (El || true solo evita el fallo en este ejemplo, que no tiene dependencias reales.)
  • COPY . .: copia el resto del código de la aplicación.
  • EXPOSE 3000: documenta que el contenedor escucha en el puerto 3000.
  • CMD ["node", "server.js"]: comando que arranca la aplicación al iniciar el contenedor.

Paso 4: El .dockerignore

node_modules
.git
*.log
  • Evita transferir node_modules y otros ficheros innecesarios al contexto, acelerando la build.

Paso 5: Construir y Ejecutar

docker build -t miweb:1.0 .
  • Construye la imagen y la nombra miweb:1.0.
docker run -d -p 8080:3000 miweb:1.0
  • -d: ejecuta el contenedor en segundo plano.
  • -p 8080:3000: mapea el puerto 8080 del host al 3000 del contenedor.
  • Ahora la aplicación responde en http://localhost:8080.

Para comprobar que funciona:

curl http://localhost:8080
  • Debería devolver Hola desde Docker.

Errores Comunes y Consejos

  • Olvidar el . final: docker build -t miapp falla porque no se indica el contexto. El punto es obligatorio.
  • Intentar copiar ficheros fuera del contexto: COPY ../algo . no funciona; todo lo que copies debe estar dentro del directorio de contexto.
  • Contexto enorme por falta de .dockerignore: si transfieres node_modules o .git, la build se ralentiza notablemente.
  • Copiar el código antes que las dependencias: invalida la caché en cada cambio. Copia primero los manifiestos (package*.json, requirements.txt, etc.).
  • No etiquetar la imagen con -t: te quedas con imágenes anónimas difíciles de identificar y gestionar.
  • Consejo: usa docker build --no-cache . cuando sospeches que la caché está dando resultados desactualizados y quieras forzar una reconstrucción completa.

Ejercicios

Ejercicio 1

Construye una imagen a partir de un Dockerfile en el directorio actual, asignándole el nombre api con el tag 2.0 y también el tag latest en un solo comando.

Ejercicio 2

Tienes un Dockerfile que copia el código antes de instalar dependencias. Explica qué problema de caché provoca y reescribe el orden para solucionarlo.

Ejercicio 3

Tras una primera build, cambias una línea de server.js y reconstruyes. ¿Qué capas se reutilizarán de la caché y cuáles se reconstruirán, suponiendo el orden eficiente (dependencias primero)?

Soluciones

Solución 1

docker build -t api:2.0 -t api:latest .

Se repite la opción -t para asignar dos nombres a la misma imagen. Ambos tags comparten el mismo IMAGE ID.

Solución 2

El problema es que al copiar todo el código antes de instalar dependencias, cualquier cambio mínimo en el código invalida la capa COPY y obliga a reejecutar la instalación de dependencias, que es lenta. La solución es copiar primero los manifiestos:

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]

Así la capa de npm ci solo se rehace cuando cambian las dependencias, no cuando cambias el código.

Solución 3

Se reutilizarán de la caché las capas hasta RUN npm ci (incluida), porque ni la imagen base ni los manifiestos ni el comando de instalación han cambiado. Se reconstruirán a partir de COPY . ., ya que el código ha cambiado, y por tanto también todas las capas posteriores a esa.

Conclusión

En esta lección hemos aprendido a construir imágenes propias con docker build: el papel del contexto de construcción y del .dockerignore, la relación entre el comando y el Dockerfile, cómo etiquetar con -t, y cómo la caché de capas y el orden de las instrucciones determinan la velocidad de las reconstrucciones. Además hemos construido y ejecutado una imagen sencilla de principio a fin.

En la siguiente lección, Conceptos Básicos de Dockerfile, estudiaremos en profundidad cada instrucción del Dockerfile (FROM, RUN, COPY, ADD, WORKDIR, CMD, ENTRYPOINT, EXPOSE, ENV), la diferencia crucial entre CMD y ENTRYPOINT, y analizaremos un Dockerfile completo comentado línea a línea.

© Copyright 2026. Todos los derechos reservados