En el tema anterior vimos lo básico para empezar a usar Docker. En este tema vamos a ver aspectos mas avanzados de Docker y trucos a tener en cuenta para usarlo.
Mas información:
Podemos crear nuestras propias imágenes si ninguna de las que hay hace lo que nosotros necesitamos. Para ello usaremos el comando docker buildx build
.
El comando docker buildx build
se utiliza para construir imágenes de contenedor Docker a partir de un Dockerfile. A continuación se presentan las principales características y uso de este comando.
docker buildx build --tag nombre_imagen:etiqueta --file ruta_del_dockerfile ruta_contexto_ficheros
Donde:
--tag nombre_imagen:etiqueta
: Nombre y tag de la imagen que se va a crear--file ruta_del_dockerfile
: Ruta al fichero que contiene el Dockerfile. Si no se indica se usará el fichero Dockerfile
del directorio actual.ruta_contexto_ficheros
: Ruta donde se encuentran los ficheros a copiar dentro de la imagen. Esto es importante ya que las rutas absolutas del COPY
empiezan en /ruta/al/contexto
mi_aplicacion_web:1.0
sabiendo que el fichero Dockerfile
está en la carpeta actual y los ficheros a copiar están en el directorio actual.
docker buildx build -t mi_aplicacion_web:1.0 ./src
Un Dockerfile contiene instrucciones para la construcción de una imagen. Aquí se muestra una estructura básica:
# Especifica la imagen base FROM imagen_base:version # Instrucciones para configurar la imagen RUN comando1 RUN comando2 # Copia archivos locales al contenedor COPY origen destino # Documenta que puerto usa el contenedor EXPOSE puerto #El directorio de trabajo WORKDIR /directorio # Define variables de entorno ENV variable=valor # Comando para ejecutar la aplicación o servicio CMD ["comando", "argumento"]
- FROM: Indica la imagen base desde la cual se construirá la nueva imagen. La versión es opcional.
FROM eclipse-temurin:17.0.10_7-jdk
- RUN: Ejecuta comandos en una nueva capa del contenedor. Se utiliza para instalar paquetes, configurar el entorno y realizar otras acciones durante la construcción de la imagen.
RUN mkdir /opt/app
- COPY: Copia archivos desde la ruta de origen en la máquina host hacia el contenedor en la ruta de destino.
COPY ./app.jar /opt/app
ruta_al_contexto
del comando de buildx
COPY
o que la propia imagen los descargue con una comando RUN
?
Opción | Ventaja | Desventaja |
---|---|---|
Copiar ficheros desde fuera con COPY | Siguen estando aunque desparezcan de internet | Para crear la imagen es necesario que lo hayamos descargado previamente |
Descargar ficheros desde el RUN | No necesitas descargar nada previamente y es más cómodo | No podrás crear la imagen si desaparecen esos ficheros de internet. |
- EXPOSE: Informa a Docker que el contenedor escuchará en el puerto especificado en tiempo de ejecución. No publica el puerto en la máquina real ni hace realmente nada. Es simplemente para documentar en que puerto
EXPOSE 8080
- WORKDIR: Establece el directorio de trabajo para cualquier instrucción posterior en el Dockerfile. Se usando tanto para crear la imagen como en el contenedor.
WORKDIR /opt/app
- ENV: Define variables de entorno dentro del contenedor.
ENV ENTORNO=produccion
- CMD: Especifica el comando predeterminado que se ejecutará cuando el contenedor se inicie. Solo puede haber una comando CMD
CMD ["java", "-jar","/opt/app/app.jar"]
RUN
y CMD
es que RUN
se ejecuta cuando se crea la imagen con el comando docker build
,es decir configurándola mientras que CMD
se ejecuta cuando se crea el contenedor con el comando docker container run
/opt/peliculas/target/app.jar
dentro de loa imagen en la carpeta /opt/app
FROM eclipse-temurin:17.0.10_7-jdk RUN mkdir /opt/app COPY /opt/peliculas/target/app.jar /opt/app WORKDIR /opt/app EXPOSE 8080 CMD ["java", "-jar","/opt/app/app.jar"]
Docker Compose es una herramienta que permite definir y administrar aplicaciones Docker de múltiples contenedores. Utiliza un archivo YAML para configurar los servicios, las redes y los volúmenes, facilitando la definición y el despliegue de aplicaciones complejas.
Un archivo docker-compose.yml
define la configuración de una aplicación multi-contenedor. Aquí se presentan las secciones principales:
services: nombre_contenedor_1: image: imagen_servicio1 restart: opciones si se reinicia el servidor hostname: nombre del host volumes: - /ruta/host:/ruta/contenedor ports: - "puerto_host:puerto_contenedor" environment: - VARIABLE=valor tty: true stdin_open: true pull_policy: never nombre_contenedor_2: # Configuración del segundo servicio
docker-compose.yml
.
docker compose up -d
docker-compose.yml
.
docker compose down
- Muestra el estado de los contenedores definidos en el archivo docker-compose.yml
.
docker compose ps
Supongamos un archivo docker-compose.yml
para una aplicación web con un servicio de frontend y otro de base de datos:
services: peliculas_java: image: temurin_peliculas restart: always hostname: peliculas_java volumes: - /opt/peliculas/target:/opt/app ports: - "80:8080" peliculas_mysql: image: mysql:8.3.0 restart: always hostname: peliculas_java environment: - MYSQL_ROOT_PASSWORD=peliculas - MYSQL_DATABASE=peliculas - MYSQL_USER=peliculas - MYSQL_PASSWORD=peliculas
Para iniciar esta aplicación, ejecutamos:
docker compose up -d
Esto levantará los contenedores de frontend y base de datos con la configuración especificada.
Docker Compose simplifica la gestión de aplicaciones Docker complejas, permitiendo definir y coordinar múltiples servicios con facilidad.
Una posible estructura de los fichero sería esta:
/FacturaDespliegue docker-compose.yml deploy.sh /mysql /volumes /data /logs /build Dockerfile /src my.cnf /java /volumes /logs /build Dockerfile /src /opt /app facturas.jar /compilar /volumes /app pom.xml /src /scripts /target /facturas.jar //Se crea después al ejecutar el contenedor /build Dockerfile /src /root /.m2 settings.xml /opt /java .... /maven ... /app
Vamos a crea una imagen de Docker con lo siguiente para poder compilar aplicaciones Java:
Dockerfile
FROM ubuntu:24.04 # Configurar entorno no interactivo para evitar prompts en apt ENV DEBIAN_FRONTEND=noninteractive # Actualizar paquetes y instalar dependencias necesarias RUN apt-get update RUN apt-get install -y curl unzip ca-certificates # Instalar Node.js y npm desde los repositorios oficiales RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - RUN apt-get install -y nodejs RUN npm install -g sass COPY /opt /opt # Instalar JDK Temurin desde una carpeta local ENV JAVA_HOME=/opt/java ENV PATH="${JAVA_HOME}/bin:${PATH}" # Instalar Maven desde una carpeta local ENV MAVEN_HOME=/opt/maven ENV PATH="${MAVEN_HOME}/bin:${PATH}" # Crear un settings.xml básico para maven para que se conecte a los servidores RUN mkdir -p /root COPY /root /root WORKDIR /opt/app CMD ["mvn","clean","install"]
settings.xml
<?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> <mirrors> <mirror> <id>central</id> <mirrorOf>central</mirrorOf> <url>https://repo.maven.apache.org/maven2</url> <layout>default</layout> </mirror> </mirrors> </settings>
sudo docker image rm logongas/compilar:1.0.0 sudo docker buildx build --tag logongas/compilar:1.0.0 --file ./build/Dockerfile ./build/src sudo docker container run \ -it \ --rm \ --pull=never \ -v ./volumes/app:/opt/app \ --name compilar \ --hostname compilar \ -u $(id -u):$(id -g) \ logongas/compilar:1.0.0
docker image pull logongas/compilar:1.0.0
Un ejemplo de todo ésto se puede ver en:
Docker permite crear redes aisladas entre los distintos contenedores. De esa forma , si un contenedor es atacado, no se tendrá acceso por red al resto de los contenedores sino únicamente a los de su propia red.
Orden | Explicación |
---|---|
docker network create | Crea una red |
docker network rm | Borrar una red |
docker network ls | Ver las redes que hay |
docker network connect | Conecta un contenedor a una red |
docker network disconnect | Desconecta un contenedor de una red |
Para trabajar con redes, lo que debemos hacer es
docker network create nombreDeLaRed
docker network connect nombreDeLaRed nombreDelContenedor
docker container run
con la opción --network=nombreDeLaRed
docker container run \ -dit \ -v /opt/mariadb:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=root \ -p 4000:3306 \ --network nombreDeLaRed \ --name prueba_mariadb \ mariadb:10.1
¿Recuerdas la cabecera Host
de HTTP? Servía para indicar a que servidor web iba dirigida la petición ya que en una misma máquina se ponían varios servidores web.
Pues vamos ahora a configurar Docker para que todos los servidores compartan el mismo puerto (el 80). Para ello vamos a usar un servidor proxy llamado Nginx. Obviamente, Nginx va estar dentro de otro contenedor de Docker. La imagen a usar es jwilder/nginx-proxy con tag 0.7.0.
Para arrancar el servidor usamos la siguiente orden:
sudo docker container run \ -dit \ -p 80:80 \ --restart always \ -v /var/run/docker.sock:/tmp/docker.sock:ro \ --name nginx-proxy \ nginxproxy/nginx-proxy:1.1.0
Como vemos no tiene nada de especial excepto la línea -v /var/run/docker.sock:/tmp/docker.sock:ro
pero solo decir que es para que Nginx pueda acceder al Docker real de la máquina real.
Y ahora , ¿Como arrancamos nuestro servidores web? Pues simplemente hay que añadir dos parámetros y quitar uno.
-p puertoExterno:puertoInterno
ya que ahora el contenedor no se va a exponer al exterior. -e VIRTUAL_PORT=puertoInterno
: Siendo puertoInterno donde está escuchando nuestro servidor Web. Esa información la sabremos por la documentación -e VIRTUAL_HOST=nombreDominio
: Siendo nombreDominio el nombre del dominio que queremos que gestione el servidor que estamos arrancando.Veamos el siguiente ejemplo:
sudo docker container run \ -dit \ -v /home/alumno/lorenzo/httpd:/var/www/html \ -e VIRTUAL_PORT=80 -e VIRTUAL_HOST=lorenzo.daw2.pve3.fpmislata.com \ --name lorenzo_apache \ php:8.1-apache-bullseye
Vemos que el servidor está escuchando en el puerto 80 y va a gestionar las peticiones del dominio lorenzo.daw2.pve3.fpmislata.com que lleguen a Nginx.
Mas información:
Ahora para que funcione el proxy con nginx ya no podemos acceder a la máquina real con la IP sino que habrá que usar el nombre de dominio. Si no tenemos acceso a un nombre de dominio real, podemos simular nosotros uno modificando el fichero hosts
de la máquina donde vayamos a hacer la petición.
El fichero hosts simplemente contiene IPs y nombre e dominio que usará nuestro ordenador. De esa forma simularemos tener un dominio.
En los siguientes artículos se explica como modificar el fichero hosts
Crea una imagen de docker basada en eclipse-temurin:17.0.10_7-jdk
que haga lo siguiente:
RUN
:Crear el directorio /opt/app
. CMD
:Que ejecute un jara llamado japp.jar
La imagen se llamará tu-nombre-java-app
.
Crea un contenedor con esa imagen de forma que con -v
esté el japp.jar
en /opt/app
En la imagen eclipse-temurin:17.0.10_7-jdk
ya existe la carpeta /tmp
y en docker container run
se puede poner al final de la línea la orden a ejecutar.
Así que ejecuta un jar
de la siguiente manera:
sudo docker container run \ -v /mi-proyecto/target:/tmp \ eclipse-temurin:17.0.10_7-jdk \ java -jar /tmp/mi.jar
docker container run
faltan más parámetros que hay que añadir.
Ahora vamos a generar el jar
en base al código fuente, para ello vamos a usar una imagen que ya tiene maven
instalado: Docker image maven y concretamente maven:3.9.6-eclipse-temurin-17
.
Para hacerlo hay que poner el código fuente en /usr/src/mymaven
y especificar que el directorio de trabajo será ese, para ello usaremos el argumento -w
. Al final del todo indicaremos la orden de maven a ejecutar.
sudo docker container run \ -v /mi-proyecto:/usr/src/mymaven \ -w /usr/src/mymaven \ maven:3.9.6-eclipse-temurin-17 \ mvn clean package
docker container run
faltan más parámetros que hay que añadir.
-d
-i
-t
Has un script en la máquina real llamado deploy.sh
que haga lo siguiente:
jar
pero se ejecutará maven dentro del contenedor de docker cuya imagen es: maven:3.9.6-eclipse-temurin-17
jar
dentro del contenedor de docker cuya imagen es: eclipse-temurin:17.0.10_7-jdk
Crea ahora una base de datos MySQL en un contenedor docker de forma que sea la que estás usando en el módulo de servidor. Rellena la base de datos con los datos.
Compila y desplega el proyecto que usas en el módulo de servidor de forma que se genere el jar
en un contenedor de docker y se ejecute en un contenedor de docker. Este último contenedor deberá conectarse al contenedor de MySQL que has creado.
Modifica el proyecto de Maven de forma que tambien se haga lo necesario para "compilar" la parte de Angular. Esa parte la vimos en el tema 3 en el apartado de maven
Prueba ahora a modificaar el código de forma que te conectes al MySQL de tu compañero. Deberá dejarte.
Crea ahora una red llamada mi-nombre-mi-proyecto
.
Modifica la creación de los contenedores para que estén asociados a la red que acabas de crear.
Prueba que todo sigue funcionando.
Prueba ahora a modificaar el código de forma que te conectes al MySQL de tu compañero. No debería dejarte ya que estáis en redes distintas.
Modifica el script deploy.sh
de forma que en vez de crear directamente los 2 contenedores de MySQL y Java se use docker compose up
.
Para ello deberás crear un fichero llamado docker-compose.yml