Docker¶
Esta guía cubre la instalación de Docker Desktop, la explicación detallada del archivo Dockerfile y el ciclo completo para empaquetar y ejecutar una aplicación en un contenedor.
Como ejemplo se despliega una página estática servida con Apache. El mismo procedimiento aplica al backend con Spring Boot y al frontend con Angular del proyecto semestral; al final encontrarás los Dockerfile de ambos.
Repositorio de ejemplo
En github.com/docsjaveriana/html está el proyecto completo de esta guía, con su Dockerfile listo para construir.
Conceptos previos¶
Antes de escribir el primer archivo conviene tener claros tres términos, porque se usan constantemente y se confunden con facilidad.
| Término | Qué es | Analogía |
|---|---|---|
| Dockerfile | Un archivo de texto con las instrucciones para armar la aplicación | La receta |
| Imagen | El resultado de ejecutar esas instrucciones: un paquete inmutable con el sistema, las dependencias y tu aplicación | El plato ya preparado y empacado |
| Contenedor | Una instancia en ejecución de una imagen | El plato servido en la mesa |
De una misma imagen se pueden levantar muchos contenedores, cada uno con su propio nombre, sus puertos y sus variables de entorno. Si un contenedor se borra, la imagen sigue intacta.
¿Por qué usar Docker? Porque la imagen incluye todo lo que la aplicación necesita para ejecutarse: el sistema operativo base, el runtime (Java, Node), las dependencias y la configuración. Eso elimina el clásico "en mi máquina sí funciona": si la imagen corre en tu equipo, corre igual en el del profesor y en el servidor.
1. Instalar Docker Desktop¶
Docker Desktop es la aplicación de escritorio que instala el motor de Docker y le agrega una interfaz gráfica para administrar imágenes, contenedores y volúmenes. Al instalarla quedan disponibles también los comandos docker en la terminal.
1.1. Descargar el instalador¶
Descarga el instalador desde docker.com/products/docker-desktop y elige la versión de tu sistema operativo.
Descarga Docker Desktop for Windows y ejecuta el instalador.
-
Requiere WSL 2 (Windows Subsystem for Linux). El instalador lo activa; si falla, ejecuta en PowerShell como administrador:
-
Reinicia el equipo cuando el instalador lo pida.
- Si el equipo tiene la virtualización desactivada, actívala en la BIOS/UEFI (Intel VT-x o AMD-V).
Descarga Docker Desktop for Mac eligiendo el chip correcto:
- Apple Silicon (M1, M2, M3, M4)
- Intel
Arrastra Docker.app a la carpeta Aplicaciones y ábrela.
Docker Desktop debe estar abierto
Los comandos docker solo funcionan si la aplicación está ejecutándose. Si la cierras, la terminal responderá Cannot connect to the Docker daemon. En la barra de estado del sistema debe aparecer el ícono de la ballena.
1.2. Verificar la instalación¶
Abre una terminal y ejecuta:
El primer comando muestra la versión instalada. El segundo descarga una imagen mínima de prueba y la ejecuta: si ves el mensaje "Hello from Docker!", la instalación quedó correcta.
1.3. Recorrido por la interfaz¶
Docker Desktop organiza el trabajo en tres pestañas que usarás permanentemente:
| Pestaña | Para qué sirve |
|---|---|
| Images | Lista las imágenes construidas o descargadas. Desde aquí se ejecuta una imagen con el botón Run |
| Containers | Muestra los contenedores creados, si están corriendo o detenidos, y permite ver sus logs, abrir una terminal dentro o eliminarlos |
| Volumes | Administra los volúmenes, que es donde se guardan los datos que deben sobrevivir a la eliminación del contenedor (por ejemplo, una base de datos) |
2. El archivo Dockerfile¶
El Dockerfile se crea en la raíz del proyecto, sin extensión y con esa exacta escritura de mayúsculas.
2.1. Cómo funciona¶
Docker lee el archivo de arriba hacia abajo y ejecuta una instrucción a la vez. Cada instrucción produce una capa: una porción de la imagen que se guarda en caché. Si vuelves a construir la imagen y una instrucción no cambió, Docker reutiliza la capa en lugar de repetir el trabajo, y por eso la segunda construcción es mucho más rápida que la primera.
De esa mecánica se desprende una regla práctica: lo que cambia poco va arriba, lo que cambia mucho va abajo. Si copias el código fuente antes de instalar dependencias, cualquier cambio en el código invalida la caché y obliga a reinstalar todo.
2.2. El Dockerfile del ejemplo¶
# Etapa 1: build con Maven
FROM maven:3.9.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY . .
RUN mvn clean package -DskipTests
# Etapa 2: runtime solo con Java
FROM eclipse-temurin:21-jdk
WORKDIR /app
COPY --from=build /app/target/<nombre>.jar app.jar
EXPOSE 8081
CMD ["java", "-jar", "app.jar"]
2.3. Instrucción por instrucción¶
FROM httpd:2.4-alpine¶
Define la imagen base sobre la cual se construye la tuya. Siempre es la primera instrucción del archivo.
Aquí se parte de httpd, la imagen oficial de Apache HTTP Server. Lo que va después de los dos puntos es la etiqueta (tag) y se lee en dos partes:
2.4es la versión de Apache.alpineindica que la imagen usa Alpine Linux, una distribución mínima de unos pocos megabytes. Existen variantes más grandes basadas en Debian, pero para servir archivos estáticos no se necesitan.
Fija siempre la versión
Escribir FROM httpd:2.4-alpine y no FROM httpd:latest garantiza que la imagen se construya igual hoy y dentro de seis meses. latest cambia sin aviso y es una causa frecuente de builds que dejan de funcionar.
RUN rm -rf /usr/local/apache2/htdocs/*¶
Ejecuta un comando durante la construcción de la imagen, no cuando el contenedor arranca.
La imagen de Apache trae una página de bienvenida en /usr/local/apache2/htdocs/, que es su carpeta pública. Esta línea la borra para que no se mezcle con el sitio propio.
COPY ./ /usr/local/apache2/htdocs/¶
Copia archivos desde tu máquina hacia la imagen. Recibe dos rutas:
./es el origen: el directorio actual del proyecto en tu equipo./usr/local/apache2/htdocs/es el destino: la carpeta pública de Apache dentro de la imagen.
A partir de aquí, todo lo que esté en tu proyecto queda dentro de la imagen y Apache lo sirve.
EXPOSE 80¶
Documenta que la aplicación escucha en el puerto 80 dentro del contenedor. Apache usa ese puerto por defecto.
EXPOSE no publica el puerto
Es una declaración informativa, para quien lea el archivo y para las herramientas. La publicación real hacia tu máquina se hace al ejecutar el contenedor, con la opción -p. Sin -p no podrás entrar desde el navegador aunque EXPOSE esté presente.
CMD ["httpd-foreground"]¶
Define el comando que se ejecuta al arrancar el contenedor. A diferencia de RUN, esta instrucción no corre durante la construcción.
httpd-foreground arranca Apache en primer plano, y ese detalle es esencial: un contenedor vive mientras su proceso principal siga vivo. Si Apache arrancara como servicio en segundo plano, el comando terminaría de inmediato y el contenedor se detendría al instante.
2.4. Otras instrucciones frecuentes¶
Estas no aparecen en el ejemplo, pero las necesitarás en el proyecto.
| Instrucción | Qué hace |
|---|---|
WORKDIR /app |
Fija el directorio de trabajo dentro de la imagen. Las instrucciones siguientes se ejecutan allí. Reemplaza a escribir rutas absolutas en cada línea |
ENV PERFIL=prod |
Define una variable de entorno que queda disponible cuando el contenedor corre |
ARG VERSION=1.0 |
Define una variable disponible solo durante la construcción. Se pasa con docker build --build-arg |
ENTRYPOINT ["java","-jar","app.jar"] |
Igual que CMD, pero fija el ejecutable: lo que se pase al docker run se agrega como argumento en lugar de reemplazar el comando |
USER appuser |
Ejecuta el contenedor con un usuario sin privilegios en vez de root. Es una buena práctica de seguridad |
2.5. El archivo .dockerignore¶
Junto al Dockerfile conviene crear un .dockerignore. Funciona igual que un .gitignore: indica qué no debe copiarse a la imagen.
Sirve para tres cosas: la imagen pesa menos, la construcción es más rápida y —lo más importante— evita filtrar secretos. Un archivo .env con contraseñas copiado dentro de la imagen queda ahí para cualquiera que la descargue.
3. Construir la imagen¶
Desde la carpeta donde está el Dockerfile:
El comando tiene dos partes que suelen confundirse:
-t sitio-estatico-apache:1.0asigna el nombre y la versión de la imagen. Es como la identificas después; sin esto Docker le pone solo un identificador y se vuelve difícil de encontrar.- El punto final es el contexto de construcción: la carpeta que Docker envía al motor y desde la cual se resuelven las rutas de los
COPY. No es opcional y no es decoración.
Durante la construcción verás una línea por cada instrucción del archivo. Al terminar, la imagen aparece en la pestaña Images de Docker Desktop, o con:
4. Ejecutar el contenedor¶
| Opción | Qué hace |
|---|---|
-d |
Ejecuta en segundo plano (detached) y devuelve el control de la terminal. Sin esta opción, la terminal queda ocupada mostrando los logs |
--name sitio-web |
Le da un nombre al contenedor. Si se omite, Docker genera uno aleatorio del estilo nostalgic_curie |
-p 8080:80 |
Publica el puerto: 8080 en tu máquina apunta al 80 del contenedor. Se lee de izquierda a derecha, host → contenedor |
sitio-estatico-apache:1.0 |
La imagen que se va a ejecutar |
Con este mapeo el sitio queda en http://localhost:8080.
Si el puerto está ocupado
El error port is already allocated significa que otro proceso usa ese puerto en tu máquina. Cambia solo el número de la izquierda: -p 9090:80. El de la derecha depende de la aplicación y no se toca.
1. Busca la imagen creada en la pestaña Images —en este caso sitio-estatico-apache— y presiona Run.

2. Despliega Optional settings y llena dos campos:
- Container name: el nombre del contenedor, en este caso
WEB. - Host port: el puerto de tu máquina. En la imagen se usó
80, que corresponde al:80/tcpdel contenedor.

3. Presiona Run.
Como aquí el puerto del host es el 80, el sitio queda en http://localhost (el navegador asume el puerto 80 cuando no se escribe ninguno).
Es el mismo -p de la línea de comandos
El campo Host port de este formulario equivale al número de la izquierda en -p 8080:80. Poner 80 aquí es lo mismo que escribir -p 80:80.
5. Verificar que está corriendo¶
Debes ver el contenedor con el nombre que le diste —en este caso WEB— y la columna STATUS en Up.

docker ps muestra solo los contenedores en ejecución; docker ps -a incluye también los detenidos, que es lo que necesitas cuando algo no arrancó.
En la pestaña Containers aparece el contenedor con el nombre con el que lo creaste, en este caso WEB.

Abre el navegador en la dirección correspondiente al puerto que publicaste y verifica que el sitio cargue.
Si el navegador no responde
Revisa los logs antes de cualquier otra cosa: son la única fuente confiable de qué pasó dentro del contenedor.
En Docker Desktop, el mismo contenido está al hacer clic sobre el contenedor en la pestaña Containers.
6. Detener y limpiar¶
docker stop sitio-web # detiene el contenedor
docker start sitio-web # lo vuelve a arrancar
docker rm sitio-web # lo elimina (debe estar detenido)
docker rmi sitio-estatico-apache:1.0 # elimina la imagen
Detener no es eliminar
Un contenedor detenido sigue ocupando espacio y sigue reservando su nombre. Si intentas crear otro con el mismo --name obtendrás name is already in use: primero elimina el anterior con docker rm.
Para liberar espacio de imágenes, contenedores y cachés sin usar:
prune -a borra todo lo que no esté en uso
Elimina todas las imágenes que no tengan un contenedor asociado, incluidas las que descargaste y tardan en volver a bajar. Úsalo cuando realmente necesites espacio.
7. Dockerfile para el proyecto¶
Los dos archivos siguientes son los que necesitarás en la segunda entrega, donde se evalúa el empaquetamiento de la solución en contenedores.
Ambos usan construcción multietapa (multi-stage build): una primera etapa compila el proyecto con todas sus herramientas y una segunda copia únicamente el resultado. Así la imagen final no carga con Maven, el JDK completo ni node_modules, y pasa de cientos de megabytes a unas pocas decenas.
7.1. Backend con Spring Boot¶
# --- Etapa 1: compilación ---
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
# Primero solo el pom: si no cambia, Docker reutiliza las dependencias descargadas
COPY pom.xml .
RUN mvn dependency:go-offline
# Ahora sí el código fuente
COPY src ./src
RUN mvn clean package -DskipTests
# --- Etapa 2: ejecución ---
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
# Copia únicamente el .jar generado en la etapa anterior
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Tres detalles que vale la pena señalar:
AS buildle pone nombre a la primera etapa para poder referenciarla después.COPY pom.xmlva antes queCOPY src, justamente por la regla de la caché: elpom.xmlcambia poco y las dependencias se descargan una sola vez, mientras que el código cambia en cada commit.COPY --from=buildes lo que hace la magia: toma el.jarde la etapa de compilación y lo lleva a la imagen final, que solo tiene el JRE y no el JDK ni Maven.
7.2. Frontend con Angular¶
# --- Etapa 1: compilación ---
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# --- Etapa 2: servir los archivos compilados ---
FROM nginx:alpine
COPY --from=build /app/dist/mi-app/browser /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Verifica la ruta de dist
El nombre de la carpeta dentro de dist/ es el del proyecto en tu angular.json, y desde Angular 17 incluye el subdirectorio browser/. Ejecuta npm run build localmente y revisa qué se generó antes de escribir esa ruta.
Fíjate en el patrón repetido: npm ci corre después de copiar solo package*.json, por la misma razón que el pom.xml en Spring Boot. Y nginx -g "daemon off;" cumple la misma función que httpd-foreground en el ejemplo de Apache: mantener el proceso en primer plano para que el contenedor no muera.
8. Comandos de referencia¶
| Comando | Qué hace |
|---|---|
docker build -t nombre:tag . |
Construye una imagen desde el Dockerfile del directorio actual |
docker images |
Lista las imágenes disponibles |
docker run -d --name X -p 8080:80 img |
Crea y ejecuta un contenedor |
docker ps / docker ps -a |
Lista contenedores en ejecución / todos |
docker logs -f nombre |
Muestra los logs; -f los sigue en tiempo real |
docker exec -it nombre sh |
Abre una terminal dentro del contenedor |
docker stop / start / restart |
Controla el ciclo de vida del contenedor |
docker rm nombre |
Elimina un contenedor detenido |
docker rmi imagen |
Elimina una imagen |
docker system prune -a |
Libera espacio eliminando lo que no está en uso |
9. Errores frecuentes¶
| Mensaje | Causa | Solución |
|---|---|---|
Cannot connect to the Docker daemon |
Docker Desktop está cerrado | Ábrelo y espera a que el ícono deje de animarse |
port is already allocated |
El puerto del host ya está ocupado | Cambia el número de la izquierda en -p |
name is already in use |
Ya existe un contenedor con ese nombre, aunque esté detenido | docker rm <nombre> y vuelve a ejecutar |
COPY failed: no such file or directory |
La ruta de origen no existe en el contexto de construcción | Verifica que el archivo exista y que no esté excluido en .dockerignore |
| El contenedor arranca y se detiene de inmediato | El proceso principal terminó | El comando de CMD/ENTRYPOINT debe correr en primer plano. Revisa docker logs |
exec format error |
La imagen se construyó para otra arquitectura | En equipos Apple Silicon, construye con docker build --platform linux/amd64 ... si el destino es x86 |