Dockerfile y creación de imágenes
1. ¿Qué es un Dockerfile? #
Hasta ahora hemos trabajado principalmente con imágenes creadas por otras personas:
1nginx 2debian 3ubuntu 4mysql
Pero Docker también nos permite crear nuestras propias imágenes.
Para ello podemos utilizar un archivo llamado:
1Dockerfile
Un Dockerfile es un archivo de texto que contiene las instrucciones necesarias para construir una imagen Docker.
Podemos verlo como una receta:
1DOCKERFILE 2 │ 3 ├── imagen de partida 4 ├── archivos que queremos copiar 5 ├── programas que queremos instalar 6 ├── variables de entorno 7 ├── puerto utilizado 8 └── comando que queremos ejecutar 9 ↓ 10 docker build 11 ↓ 12 IMAGEN DOCKER
Docker construye imágenes leyendo las instrucciones contenidas en ese archivo.
2. ¿Para qué sirve un Dockerfile? #
Un Dockerfile permite automatizar la creación de una imagen.
Imaginemos que queremos crear un servidor web con:
1Nginx 2+ 3nuestra página web
Podríamos hacerlo manualmente:
- Crear un contenedor.
- Entrar en él.
- Modificar los archivos.
- Configurarlo.
- Guardar los cambios.
Pero sería difícil repetir exactamente el mismo proceso.
Con un Dockerfile podemos escribir la configuración una única vez:
1FROM nginx 2COPY ./web /usr/share/nginx/html
y crear la imagen siempre que queramos.
3. Ventajas de utilizar Dockerfile #
Un Dockerfile permite:
- Automatizar la creación de imágenes.
- Repetir siempre la misma configuración.
- Compartir fácilmente cómo está construida una aplicación.
- Mantener la configuración junto con el código.
- Crear diferentes versiones de una aplicación.
- Integrarlo con sistemas de CI/CD.
La idea principal es:
1Configuración manual 2 ↓ 3difícil de repetir 4 5 6Dockerfile 7 ↓ 8proceso reproducible
4. Nombre del archivo #
El archivo normalmente debe llamarse:
1Dockerfile
Sin extensión.
No:
1Dockerfile.txt
Ni:
1dockerfile.html
Lo habitual es tener una estructura como:
1mi-proyecto/ 2│ 3├── Dockerfile 4├── index.html 5└── otros archivos
5. Primera instrucción: FROM #
La instrucción:
1FROM
indica la imagen de partida.
Por ejemplo:
1FROM nginx
significa:
1Nuestra imagen 2 ↑ 3 │ 4Imagen nginx
También podemos especificar un tag:
1FROM ubuntu:24.04
o:
1FROM debian:12
FROM define la imagen base sobre la que construiremos nuestra propia imagen.
6. Primer Dockerfile #
Vamos a crear nuestra primera imagen.
Creamos una carpeta:
1docker-web
Dentro tendremos:
1docker-web/ 2│ 3├── Dockerfile 4└── index.html
7. Crear index.html #
Creamos:
1<h1>Mi primera imagen Docker</h1> 2<p>Curso de Docker</p>
Guardamos el archivo como:
1index.html
8. Crear Dockerfile #
En la misma carpeta creamos:
1Dockerfile
con este contenido:
1FROM nginx 2COPY index.html /usr/share/nginx/html/index.html
Tenemos únicamente dos instrucciones.
9. Explicar el Dockerfile #
Primero:
1FROM nginx
utiliza Nginx como imagen base.
Después:
1COPY index.html /usr/share/nginx/html/index.html
copia nuestro archivo:
1index.html
dentro de:
1/usr/share/nginx/html/index.html
que es la ubicación de la página web servida por Nginx en este ejemplo.
10. Construir la imagen: docker build #
Para crear una imagen a partir del Dockerfile utilizamos:
1docker build
La forma habitual es:
1docker build -t <nombre_imagen> .
Por ejemplo:
1docker build -t mi-web .
Docker utiliza el Dockerfile del directorio indicado como contexto de construcción. El punto final:
1.
significa:
1directorio actual
Docker documenta docker build como alias habitual para la construcción de imágenes, actualmente usando BuildKit/Buildx por defecto en las instalaciones modernas.
11. No olvidar el punto #
El comando es:
1docker build -t mi-web .
No debemos olvidar:
1.
Podemos interpretarlo:
1docker build 2 ↓ 3construye una imagen 4 5-t mi-web 6 ↓ 7nombre/tag de la imagen 8 9. 10 ↓ 11utiliza el directorio actual 12como contexto de construcción
12. Comprobar la imagen #
Después de construir:
1docker build -t mi-web .
podemos ejecutar:
1docker images
Debería aparecer:
1REPOSITORY TAG 2mi-web latest
13. Ejecutar nuestra imagen #
Ahora podemos crear un contenedor:
1docker run -dp 8080:80 --name web1 mi-web
Accedemos:
1http://localhost:8080
Deberíamos ver:
1Mi primera imagen Docker 2 3Curso de Docker
14. Flujo completo #
Podemos resumir el proceso:
1Dockerfile 2 │ 3 │ docker build 4 ↓ 5 IMAGEN 6 │ 7 │ docker run 8 ↓ 9 CONTENEDOR
Es importante distinguir los dos comandos:
1docker build
crea una imagen.
Mientras que:
1docker run
crea un contenedor a partir de la imagen.
15. Crear un tag durante la construcción #
Podemos indicar una versión:
1docker build -t mi-web:v1 .
Después:
1docker images
podremos ver:
1mi-web v1
También podríamos crear:
1docker build -t mi-web:v2 .
La opción -t permite asignar nombre y opcionalmente tag durante la construcción.
16. COPY #
La instrucción:
1COPY
copia archivos o directorios desde nuestro proyecto hacia la imagen.
Sintaxis:
1COPY <origen> <destino>
Por ejemplo:
1COPY index.html /usr/share/nginx/html/index.html
17. Copiar una carpeta completa #
Podemos tener:
1web/ 2│ 3├── index.html 4├── estilos.css 5└── app.js
Y utilizar:
1COPY web /usr/share/nginx/html
También es habitual encontrar:
1COPY . /app
El punto:
1.
representa el contenido del contexto actual.
COPY incorpora archivos o directorios del contexto de construcción a la imagen.
18. RUN #
La instrucción:
1RUN
permite ejecutar comandos mientras se está construyendo la imagen.
Por ejemplo:
1FROM debian 2 3RUN apt update
Podemos instalar programas:
1FROM debian 2 3RUN apt update 4RUN apt install -y nano
19. ¿Cuándo se ejecuta RUN? #
RUN se ejecuta durante:
1docker build
No cuando arrancamos posteriormente el contenedor.
Por ejemplo:
1FROM debian 2 3RUN apt update 4RUN apt install -y nano
Al ejecutar:
1docker build -t debian-nano .
Docker instalará Nano dentro de la imagen.
Después:
1docker run -it debian-nano bash
Nano ya estará disponible.
RUN ejecuta comandos durante la construcción y genera una nueva capa en la imagen.
20. Ejemplo Debian con Nano #
Dockerfile:
1FROM debian 2 3RUN apt update 4RUN apt install -y nano
Construimos:
1docker build -t debian-nano .
Ejecutamos:
1docker run -it debian-nano bash
Dentro:
1nano
El editor ya estará instalado.
21. WORKDIR #
La instrucción:
1WORKDIR
establece el directorio de trabajo.
Por ejemplo:
1WORKDIR /app
A partir de ese momento, las instrucciones posteriores pueden trabajar tomando:
1/app
como directorio actual.
22. Ejemplo con WORKDIR #
Podemos escribir:
1FROM debian 2 3WORKDIR /app 4 5COPY datos.txt .
El archivo terminará en:
1/app/datos.txt
Porque:
1.
hace referencia al directorio de trabajo configurado.
WORKDIR afecta a instrucciones posteriores como RUN, CMD, ENTRYPOINT, COPY y ADD; si el directorio no existe, Docker puede crearlo.
23. ENV #
La instrucción:
1ENV
permite definir variables de entorno.
Por ejemplo:
1ENV ENTORNO=desarrollo
Después esa variable estará disponible para los procesos del contenedor.
24. Ejemplo #
Dockerfile:
1FROM debian 2 3ENV CENTRO=CEBEM 4 5CMD ["env"]
Construimos:
1docker build -t ejemplo-env .
Ejecutamos:
1docker run ejemplo-env
Entre las variables deberíamos encontrar:
1CENTRO=CEBEM
25. EXPOSE #
La instrucción:
1EXPOSE
documenta el puerto en el que nuestra aplicación espera escuchar.
Por ejemplo:
1EXPOSE 80
Un Dockerfile Nginx podría tener:
1FROM nginx 2 3COPY ./web /usr/share/nginx/html 4 5EXPOSE 80
26. EXPOSE no es lo mismo que -p #
Es importante entender esta diferencia.
1EXPOSE 80
indica o documenta que la aplicación usa ese puerto.
Pero para publicar el servicio hacia nuestro ordenador seguimos usando:
1docker run -p 8080:80 mi-web
Es decir:
1EXPOSE 2 ↓ 3describe el puerto usado por la aplicación 4 5 6-p 7 ↓ 8publica/mapea realmente el puerto
La documentación actual de Docker también aclara que EXPOSE describe el puerto de escucha de la imagen, mientras el mapeo efectivo se realiza al ejecutar el contenedor.
27. CMD #
La instrucción:
1CMD
define el comando predeterminado que se ejecutará cuando creemos un contenedor.
Por ejemplo:
1FROM debian 2 3CMD ["echo", "Hola Docker"]
Construimos:
1docker build -t saludo .
Ejecutamos:
1docker run saludo
Obtendremos:
1Hola Docker
28. Diferencia entre RUN y CMD #
Esta diferencia es fundamental.
RUN #
1RUN apt install -y nano
se ejecuta al construir:
1docker build
CMD #
1CMD ["echo", "Hola"]
se ejecuta al arrancar:
1docker run
Podemos recordarlo así:
1RUN 2 ↓ 3CONSTRUCCIÓN DE LA IMAGEN 4 5 6CMD 7 ↓ 8EJECUCIÓN DEL CONTENEDOR
29. Ejemplo sencillo con Python #
Podemos crear una pequeña aplicación.
Archivo:
1app.py
Contenido:
1print("Hola desde Docker")
Dockerfile:
1FROM python:3 2 3WORKDIR /app 4 5COPY app.py . 6 7CMD ["python", "app.py"]
30. Construir la imagen Python #
Ejecutamos:
1docker build -t python-saludo .
Después:
1docker run python-saludo
Resultado:
1Hola desde Docker
31. Explicar el ejemplo #
Tenemos:
1FROM python:3
Partimos de una imagen con Python.
1WORKDIR /app
Trabajaremos dentro de:
1/app
1COPY app.py .
Copia:
1app.py
dentro de:
1/app
1CMD ["python", "app.py"]
Cuando se ejecute el contenedor lanzará:
1python app.py
32. ADD #
En tu presentación aparece también:
1ADD
Al igual que COPY, permite añadir archivos a una imagen.
Ejemplo:
1ADD archivo.txt /app/
Sin embargo, para copiar archivos locales normalmente se recomienda utilizar:
1COPY
ADD dispone además de comportamientos adicionales, como determinados usos con archivos remotos o archivos comprimidos. Docker diferencia ambas instrucciones en su referencia oficial.
33. LABEL #
La instrucción:
1LABEL
permite añadir metadatos a nuestra imagen.
Por ejemplo:
1LABEL version="1.0"
Podemos añadir una descripción:
1LABEL description="Servidor web para prácticas de Docker"
34. USER #
Por defecto, muchas imágenes pueden ejecutar procesos con un usuario privilegiado.
La instrucción:
1USER
permite indicar qué usuario se utilizará para instrucciones posteriores y para el proceso del contenedor.
Ejemplo conceptual:
1USER app
La documentación oficial muestra USER como una de las instrucciones habituales para definir el usuario por defecto de la imagen.
35. VOLUME #
La instrucción:
1VOLUME
permite declarar un punto de montaje destinado a datos persistentes.
Por ejemplo:
1VOLUME /datos
Relaciona lo visto en el bloque anterior con la propia definición de la imagen.
36. STOPSIGNAL #
En tu presentación también aparece:
1STOPSIGNAL
Esta instrucción permite indicar qué señal debe utilizar Docker para solicitar la parada del contenedor.
Es una opción más avanzada y normalmente no será necesaria en nuestros primeros Dockerfiles.
37. ENTRYPOINT #
Otra instrucción importante es:
1ENTRYPOINT
Permite definir el ejecutable principal del contenedor.
Por ejemplo:
1ENTRYPOINT ["echo"]
Si construimos la imagen y ejecutamos:
1docker run mi-imagen Hola
el argumento puede complementar el ejecutable definido.
Para un curso inicial, lo más importante es diferenciar:
1RUN 2CMD 3ENTRYPOINT
38. RUN, CMD y ENTRYPOINT #
Podemos hacer una primera aproximación:
1RUN 2 ↓ 3ejecutar durante la construcción 4 5 6CMD 7 ↓ 8comando predeterminado al arrancar 9 10 11ENTRYPOINT 12 ↓ 13ejecutable principal del contenedor
Docker admite diferentes formas de combinar CMD y ENTRYPOINT, por lo que profundizaremos solamente cuando sea necesario.
39. Comentarios #
Podemos escribir comentarios utilizando:
1#
Por ejemplo:
1# Utilizamos nginx como imagen base 2FROM nginx 3 4# Copiamos nuestra página 5COPY index.html /usr/share/nginx/html/index.html
Los comentarios ayudan a documentar el archivo.
40. Ejemplo completo: Nginx personalizado #
Estructura:
1mi-web/ 2│ 3├── Dockerfile 4└── web/ 5 ├── index.html 6 └── estilos.css
Dockerfile:
1FROM nginx 2 3LABEL version="1.0" 4 5COPY ./web /usr/share/nginx/html 6 7EXPOSE 80
41. Construir la imagen #
Nos situamos dentro de:
1mi-web
Ejecutamos:
1docker build -t nginx-cebem:v1 .
Comprobamos:
1docker images
42. Ejecutar #
Creamos:
1docker run -dp 8080:80 --name web-cebem nginx-cebem:v1
Abrimos:
1http://localhost:8080
43. Modificar la aplicación #
Supongamos que modificamos:
1web/index.html
La imagen Docker existente:
1nginx-cebem:v1
no cambia automáticamente.
Debemos volver a construirla.
Por ejemplo:
1docker build -t nginx-cebem:v2 .
Ahora tendremos:
1nginx-cebem:v1 2nginx-cebem:v2
44. Dockerfile frente a volumen #
Puede surgir una duda:
¿Copio la web mediante Dockerfile o utilizo un volumen?
Dockerfile #
1COPY ./web /usr/share/nginx/html
Los archivos pasan a formar parte de la imagen.
Resulta apropiado para:
1crear una imagen lista para distribuir
Volumen #
1docker run -v C:/web:/usr/share/nginx/html nginx
Los archivos siguen estando en nuestro ordenador y se montan en el contenedor.
Resulta muy práctico durante:
1desarrollo
Una primera regla sencilla podría ser:
1Desarrollo 2 ↓ 3volúmenes / bind mounts 4 5 6Distribución 7 ↓ 8Dockerfile + imagen
45. .dockerignore #
Existe un archivo relacionado con Dockerfile llamado:
1.dockerignore
Permite excluir archivos y directorios del contexto de construcción.
Por ejemplo:
1node_modules 2.git 3*.log
Es parecido conceptualmente a:
1.gitignore
pero aplicado al contexto enviado al proceso de construcción de Docker.
46. Ejemplo .dockerignore #
Tenemos:
1proyecto/ 2│ 3├── Dockerfile 4├── .dockerignore 5├── app.js 6├── node_modules/ 7└── logs/
Podemos escribir:
1node_modules 2logs 3*.log
Docker no necesitará incluir esos elementos en el contexto de construcción.
47. Práctica guiada: primera imagen propia #
Paso 1 #
Crea:
1docker-practica
Paso 2 #
Dentro crea:
1index.html
con:
1<h1>Curso Docker</h1> 2<h2>Mi primera imagen</h2>
Paso 3 #
Crea:
1Dockerfile
con:
1FROM nginx 2 3COPY index.html /usr/share/nginx/html/index.html 4 5EXPOSE 80
Paso 4 #
Construye:
1docker build -t web-practica:v1 .
Paso 5 #
Comprueba:
1docker images
Paso 6 #
Ejecuta:
1docker run -dp 8080:80 --name practica-web web-practica:v1
Paso 7 #
Abre:
1http://localhost:8080
48. Modificar la versión #
Modifica:
1index.html
y añade:
1<p>Versión 2</p>
Construye:
1docker build -t web-practica:v2 .
Comprueba:
1docker images
Ahora deberían existir:
1web-practica:v1 2web-practica:v2
49. Ejercicio práctico #
Crea una imagen personalizada que cumpla:
1Imagen base: nginx 2 3Nombre de la imagen: 4web-alumno 5 6Tag: 7v1 8 9Puerto utilizado: 1080
La página debe contener:
1Nombre del alumno 2Curso 3Texto: "Servidor creado con Dockerfile"
Después:
- Construye la imagen.
- Comprueba que aparece en
docker images. - Ejecuta un contenedor llamado
web-alumno. - Publica el puerto 8085 del host hacia el 80 del contenedor.
- Accede desde el navegador.
- Modifica el HTML.
- Construye una versión
v2. - Ejecuta la nueva versión.
50. Ejercicio con Debian #
Crea un Dockerfile que:
- Parta de Debian.
- Actualice los repositorios.
- Instale Nano.
- Cree
/appcomo directorio de trabajo.
Una posible estructura sería:
1FROM debian 2 3RUN apt update 4RUN apt install -y nano 5 6WORKDIR /app
Construye:
1docker build -t debian-clase:v1 .
Ejecuta:
1docker run -it debian-clase:v1 bash
Comprueba:
1pwd
y:
1nano --version
51. Preguntas de repaso #
-
¿Qué es un Dockerfile?
-
¿Qué comando construye una imagen?
-
¿Qué significa el punto de:
1docker build -t miimagen .
-
¿Para qué sirve
FROM? -
¿Para qué sirve
COPY? -
¿Para qué sirve
RUN? -
¿Qué diferencia existe entre
RUNyCMD? -
¿Para qué sirve
WORKDIR? -
¿Qué permite hacer
ENV? -
¿Para qué sirve
EXPOSE? -
¿
EXPOSE 80publica automáticamente el puerto 80 en nuestro ordenador? -
¿Qué diferencia existe entre
EXPOSEydocker run -p? -
¿Para qué sirve
LABEL? -
¿Para qué sirve
USER? -
¿Qué diferencia hay entre copiar una web con
COPYy montarla mediante-v? -
¿Para qué sirve
.dockerignore?
52. Resumen de instrucciones #
Imagen base #
1FROM nginx
Ejecutar durante la construcción #
1RUN comando
Copiar archivos #
1COPY origen destino
Añadir archivos #
1ADD origen destino
Directorio de trabajo #
1WORKDIR /app
Variable de entorno #
1ENV NOMBRE=valor
Documentar un puerto #
1EXPOSE 80
Añadir metadatos #
1LABEL version="1.0"
Usuario #
1USER usuario
Declarar almacenamiento #
1VOLUME /datos
Comando predeterminado #
1CMD ["comando", "argumento"]
53. Comandos principales #
Construir:
1docker build -t mi-imagen .
Con tag:
1docker build -t mi-imagen:v1 .
Comprobar:
1docker images
Ejecutar:
1docker run mi-imagen:v1
Servidor web:
1docker run -dp 8080:80 mi-imagen:v1
54. Esquema final #
1 Dockerfile 2 │ 3 │ 4 docker build 5 │ 6 ↓ 7 IMAGEN DOCKER 8 │ 9 │ 10 docker run 11 │ 12 ↓ 13 CONTENEDOR
Y dentro del Dockerfile:
1FROM 2 ↓ 3imagen de partida 4 5RUN 6 ↓ 7instalar/configurar 8 9WORKDIR 10 ↓ 11directorio de trabajo 12 13COPY 14 ↓ 15añadir nuestra aplicación 16 17ENV 18 ↓ 19variables 20 21EXPOSE 22 ↓ 23documentar puerto 24 25CMD 26 ↓ 27comando de inicio
Con Dockerfile dejamos de limitarnos a ejecutar imágenes existentes y empezamos a crear imágenes reproducibles propias.