
Tras la pertinente introducción teórica para tener una perspectiva global del mundo Jakarta EE, y con el software necesario instalado en nuestro equipo, ha llegado el momento de ponernos manos a la obra y crear la primera aplicación. Y, por supuesto, también veremos cómo «ejecutarla» en el servidor de aplicaciones WildFly de forma manual o mediante un entorno de desarrollo (IntelliJ y Eclipse).
Tabla de contenidos
Proyecto con Maven
Empecemos creando un proyecto web Java estándar con Apache Maven. Aunque no vamos a estudiarla, considero oportuno hacer un breve repaso al funcionamiento de la herramienta por excelencia, con permiso de Gradle, para la gestión y construcción de proyectos Java.
Maven
Haciendo una simplificación extrema, Maven genera un fichero comprimido denominado artefacto. Este fichero empaqueta una aplicación de tipo web (.war, es lo que nosotros queremos), «empresarial» (.ear, lo veremos cuando examinemos la tecnología EJB), una librería o aplicación ejecutable (.jar), o incluso código fuente y documentación javadoc.
Revisemos la estructura de carpetas de un proyecto Maven de tipo web. Es importante conocerla bien.

- src/main. Aquí están los ficheros con las fuentes («sources»), divididos en tres grupos.
- java. Es el código fuente.
- resources. «Recursos» que utiliza el código, como por ejemplo ficheros .properties, y que forman parte del classpath. Cuando la aplicación se empaqueta en un artefacto, el contenido de esta carpeta se copia de forma recursiva en la raíz del directorio en el que se ubican los ficheros .java ya compilados en .class.
La carpeta META-INF contiene los ficheros requeridos por algunas especificaciones como, por ejemplo, el archivo persistence.xml. Al empaquetarse la aplicación, Maven crea en ella el fichero MANIFEST.MF con detalles sobre la compilación, así como otros que contienen información acerca de las dependencias -librerías que usa el proyecto- definidas en el pom.xml. - webapp. Los ficheros de «recursos» específicos de una aplicación web: html, css, JavaScript, imágenes, etc. Dentro encontramos la carpeta especial WEB-INF. En el artefacto .war, en esta carpeta se copia el código fuente compilado (classes), las librerías de las dependencias (lib) y cualquier fichero que hayamos puesto en esa ruta.
- src/test. Todos los recursos utilizados en exclusiva para implementar las pruebas. No estarán presentes en el empaquetado final de la aplicación.
- target. Es la carpeta de trabajo. En ella se generan, entre otros, los ficheros con el código compilado, artefactos intermedios e informes de la ejecución de pruebas. Aunque siempre esté ahí, en realidad no forma parte del proyecto y debe excluirse del sistema de control de versiones.
El fichero pom.xml
En la raíz del proyecto (la estructura de carpetas que acabamos de describir), se encuentra el fichero pom.xml. Lo usamos para definir, entre otra información, cómo se construye el artefacto y las dependencias que utiliza.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.danielme.jakartaee</groupId>
<artifactId>hello</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>11</maven.compiler.release>
<jakarta-ee.version>9.0.0</jakarta-ee.version>
<maven.compiler.version>3.8.1</maven.compiler.version>
<maven.war.version>3.3.1</maven.war.version>
</properties>
<build>
<finalName>hello</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>${maven.war.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>${jakarta-ee.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
El ejemplo anterior de fichero pom.xml contiene una configuración básica para una aplicación web basada en Jakarta EE. De hecho, pertenece al proyecto de ejemplo hello con el que vamos a trabajar en este capítulo. Analicemos brevemente su contenido.
- Los datos que caracterizan al proyecto.
- groupId: Es el identificador de la organización o persona que lo desarrolla. La costumbre es utilizar una ruta de paquetes. En mi caso, es la dirección invertida de mi blog, criterio muy habitual (org.apache, org.hibernate, com.liferay, etc).
- artifactId: El identificador específico del proyecto, y único para un mismo groupId. En definitiva, su nombre.
- version: La versión actual. Iré actualizando y ampliando los ejemplos del curso pero no cambiaré su versión, de ahí que haya añadido el sufijo SNAPSHOT («instantánea») para que Maven sepa que se trata del código más reciente de la 1.0.0.
- packaging: El tipo de artefacto a generar. El predeterminado es jar, así que ponemos war.
- El artefacto con la aplicación se llamará «hello.war» (finalName). Si no proporcionamos el nombre, Maven lo compondrá con los valores de artifactId y version (hello-1.0-SNAPSHOT.war)
- Las librerías de Maven (plugins) que vamos a utilizar para construir el artefacto. Hay dos: uno lo genera (maven-war-plugin) y el otro compila el código (maven-compiler-plugin) para la versión de Java especificada con la propiedad maven.compiler.release. No es necesario declarar estos plugins si no se va a realizar ninguna configuración de los mismos, pero recomiendo hacerlo para indicar su versión y así evitar sorpresas desagradables. De lo contrario, Maven utiliza unas versiones bastante antiguas.
- Me gusta definir variables en el bloque properties con los números de versiones. La idea es reutilizarlas si tenemos varios módulos de una misma librería que comparten la numeración. Otra ventaja es que vemos de un vistazo todas las librerías y versiones.
Entre las propiedades, fijémonos en project.build.sourceEncoding. La utiliza el plugin maven-resources-plugin para establecer la codificación de los ficheros. Recomiendo definirla con el valor UTF-8 y así evitar problemas causados por usar el proyecto tanto en Windows como en Linux. Obsérvese que acabo de mencionar un plugin que no aparece en el pom: no necesitamos realizar ninguna configuración adicional y Maven ya lo utiliza. En este caso concreto, indicar el número de versión no me parece tan relevante como en los casos de maven-war-plugin y maven-compiler-plugin.
- El punto de mayor interés son las dependencias. Por omisión, y al igual que los plugins, se descargan de los repositorios centrales de Maven, un auténtico paraíso de librerías de código abierto que podemos inspeccionar con la web mvnrepository.com. Estas dependencias no son sino artefactos y se van guardando en nuestro equipo en un «repositorio local» para evitar descargarlas cada vez que sean necesarias.
Hay dos alternativas a la hora de añadir las dependencias con las APIs de Jakarta EE.
- Utilizar una «superdependencia» que incluye un conjunto de especificaciones. Podemos incluirlas todas (jakarta.jakartaee-api), o bien las utilizadas de forma habitual en el desarrollo de aplicaciones web con jakarta.jakartaee-web-api, el llamado perfil web (Jakarta EE Web Profile) del que hablamos en su momento.
- Definir solo las correspondientes a las especificaciones que necesitemos. Tendríamos algo tal que así.
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
Lo habitual es decidirse por la primera opción. La razón es muy sencilla y se explicó en el capítulo dos: todas las dependencias de Jakarta EE son de tipo «provided» porque están disponibles, tanto las APIs como sus implementaciones, en el entorno de ejecución (el servidor). No se empaquetan en la aplicación (la carpeta /WEB-INF/lib en hello.war), así que lo más cómodo es hacer que todas las APIs estén a nuestra disposición e ir usando lo que necesitemos. Otra ventaja es la sencillez del pom.
Página HTML
Con el pom anterior ya tenemos el proyecto hello listo para ser publicado, pero es un «cascarón vacío» que no hace nada. Demasiado triste; vamos a añadir alguna funcionalidad. Por ejemplo, la página principal de la aplicación que se verá cuando accedamos su dirección raíz. Tenemos que crearla en el fichero /src/main/webapp/index.html.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hello</title>
<link rel='icon' href='img/favicon.png' type='image/x-icon'>
<style>
body {
max-width: max-content;
margin: auto;
text-align: center;
}
</style>
</head>
<body>
<h1>Hello - Jakarta EE</h1>
<img src="img/jakartaee.png">
</body>
</html>
Las líneas marcadas son de especial interés porque enlazan imágenes situadas en la carpeta /src/main/webapp/static/img. Con la excepción del contenido de WEB-INF, todos los ficheros de webapp son accesibles vía web añadiendo su ruta relativa a la url raíz que la aplicación tiene en el servidor, por ejemplo {url aplicación}/img/jakartaee.png.

Servlet de ejemplo
Comencemos a utilizar Jakarta EE con una de las especificaciones más importantes: Jakarta Servlet, API que gestiona peticiones y respuestas HTTP. Es la base de todas las aplicaciones web Java y, aunque casi nunca la usaremos directamente, siempre está ahí, por ejemplo «oculta» detrás de Jakarta Server Faces o Jakarta REST.
Por ahora, es suficiente con saber que un servlet, definido de forma sencilla y práctica, es una clase que recibe una petición HTTP y devuelve una respuesta, por ejemplo una cadena con el HTML de una página web.
A continuación se muestra el servlet que vamos a usar. Una petición de tipo GET realizada a la dirección indicada en la anotación @WebServlet provocará la invocación del método doGet. Sus argumentos son la petición (request) y la respuesta asociada (response). Esta última consiste en una cadena de texto tal y como indica el valor de content-type, un parámetro del header de la respuesta..
package com.danielme.jakartaee.hello;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/helloServlet")
public class HelloServlet extends HttpServlet {
private static final String HELLO_MESSAGE = "Jakarta EE rocks!!";
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
PrintWriter printWriter = response.getWriter();
response.setContentType("text/plain;charset=UTF-8");
printWriter.print(HELLO_MESSAGE);
}
}
La siguiente imagen ofrece una visión general del funcionamiento de la aplicación que hemos construido.

A través del protocolo HTTP, por ejemplo usando un navegador, llega la petición GET al servidor quien determinará el servlet que debe atenderla. Su método service se invocará como parte de un flujo de trabajo que puede implicar a varios servlets. En algún momento se ejecuta nuestro método doGet.
Todo lo anterior es realizado por un componente denominado «contenedor de servlet» (servlet container), requisito de la especificación. Su principal cometido es la ejecución de los servlets. Forma parte del servidor web que debe incluir, valga la redundancia, todo servidor capaz de ejecutar aplicaciones web Java. En el caso de WildFly, el servidor web incluido es Undertow. Este nombre nos será muy familiar: aparecerá con frecuencia en los registros de la bitácora (logs).
Construcción y despliegue
Manual (Maven)
Veamos ahora cómo publicar la aplicación en WildFly (este proceso es distinto en cada servidor). En primer lugar, generamos el artefacto .war. Mencioné en el capítulo anterior que el proyecto incluye en su raiz el wrapper de Maven, una pequeña herramienta que permite utilizar una versión de Maven concreta sin instalarla en el equipo.

Con el wrapper podemos usar Maven desde la línea de comandos llamando al script mvnw.cmd (Windows) o mvnw (Linux). Indicamos las acciones (goals) que necesitamos.
(Linux) ./mvnw clean package (Windows) mvnw.cmd clean package
Clean borra el contenido de target con el fin de asegurar que la aplicación se genera desde cero. Por su parte, package crea el artefacto realizando las tareas necesarias, lo cual incluye la compilación del código y la ejecución de las pruebas si las hubiera. Si todo va bien, veremos algo como lo siguiente.

El resultado está en target.

Nuestra aplicación es hello.war. Recuerda que este nombre y el formato están definidos en el pom.xml. El tamaño del fichero es ridículo a pesar de que tenemos disponible en el proyecto todas las APIs de Jakarta EE. Asimismo, sabemos que es un archivo comprimido, y si el lector es curioso puede ver su contenido con una utilidad de compresión. Llama la atención la carpeta WEB-INF classes porque no la tenemos en el proyecto. Contiene la estructura de carpetas de /src/main/resources tal cual y los compilados de /src/main/java.

¡Ya casi estamos! Ahora es el turno de WildFly. Lo iniciamos ejecutando el script standalone (*) ubicado en la carpeta bin dentro del directorio donde lo hayamos descomprimido. Usamos standalone.sh en Linux y standalone.bat en Windows. El script ejecuta el servidor con la versión de Java que tengamos configurada en el sistema operativo. Admite algunos parámetros que ya iremos viendo cuando los necesitemos.
(*) WildFly tiene dos modos de ejecución: independiente (standalone) o formando parte de un clúster (dominio). En el curso solo usaremos el primero.
La publicación de hello.war puede hacerse con la consola de administración de la que hablaré al final del capítulo, o bien copiándolo en la carpeta /standalone/deployments de WildFly. En ambos casos, en la bitácora del servidor podemos seguir el despliegue del war. Las salidas predeterminadas para la bitácora son la terminal en la que se ejecuta el servidor y el fichero {wildfly}/standalone/log/server.log.
16:14:14,960 INFO [org.jboss.as.server] (ServerService Thread Pool -- 44) WFLYSRV0010: Deployed "hello.war" (runtime-name : "hello.war")
Aprenderemos a trabajar con el sistema de logging de WildFly en el próximo capítulo.
El siguiente video muestra todo el proceso en Linux.
WildFly -o Undertow, si somos rigurosos- «escucha» en la url http://localhost:8080. En esa dirección muestra una pantalla de bienvenida.

¡Esto marcha! La aplicación está accesible dentro de ella con el nombre del fichero .war.

Probemos el servlet que escucha en la dirección http://localhost:8080/hello/helloServlet .

Detenemos WildFly cerrando la ventana de la terminal o consola en la que se está ejecutando. También es posible abrir la herramienta de administración para línea de comandos, conocida como CLI (command line interface) con jboss-cli.sh o jboss-cli.bat, según corresponda, y lanzar dentro de ella el comando shutdown. Al iniciar CLI, ejecutamos el comando connect para que se conecte a una instancia de WildFly que esté en ejecución en nuestra máquina. Adjunto un video para que se vea más claro.
CLI no está limitado a servidores locales y permite administrar cualquiera indicando en el comando connect su dirección y puerto de administración. Si hiciera falta, nos solicitará las credenciales de un usuario administrador (veremos esto al final del capítulo).

Es importante que nos familiaricemos con esta poderosa herramienta que usaremos de forma recurrente a lo largo del curso.
IntelliJ
De serie, o «out-of-the-box», IntelliJ Ultimate proporciona todo lo necesario para trabajar con proyectos Maven y servidores de aplicaciones. Además, incluye numerosas herramientas que nos asisten en la programación con JEE\Jakarta EE. Veremos algunas a lo largo del curso.
Con IntelliJ podemos crear un nuevo proyecto desde cero utilizando un asistente (File->New Project), pero queremos importar uno ya existente, así que pulsamos la opción File->New from Existing Sources… y seleccionamos la carpeta con el proyecto. El IDE lo configura como una aplicación web de tipo Maven. Tras la importación, navegaremos por los ficheros con el explorador de la vista «Project» en el que vemos, entre otros elementos, las dependencias como «External Libraries».
Es importante asegurar que el IDE compila el código con la JDK adecuada. Con la opción de menú File->Project Structure… se muestra la siguiente pantalla. En «Project SDK» seleccionamos la instalación de Java (recordemos que en el capítulo anterior me decidí por la OpenJDK 11 de AdoptOpenJDK). Si queremos usar una que no esté en el listado, la añadimos con el botón «Edit».

Es preciso destacar un par de aspectos peculiares de IntelliJ que pueden resultar sorprendentes la primera vez que lo usemos.
- En una instancia solo hay un proyecto. Para usar varios a la vez, cada uno debe abrirse en su propio IntelliJ. Dicho de otro modo, cada proyecto tiene su ventana.
- Los ficheros se guardan de forma automática a medida que se van editando.
Nota. Todo lo que hemos visto hasta ahora es válido para IntelliJ Community, la versión gratuita del IDE. Sin embargo, la integración con servidores de aplicaciones es exclusiva de la versión Ultimate (de pago).
Vamos a desplegar el proyecto. Creamos una configuración de lanzamiento (Run Configuration). Pulsamos en Run->Edit Configurations para abrir la pantalla correspondiente, y en la sección de la izquierda pulsamos + y seleccionamos JBoss Local. En Windows 10, es posible que aparezca la pantalla del cortafuegos para que demos permisos de conexión al IDE.

Rellenamos este formulario.
En el desplegable Application Server seleccionamos el servidor a utilizar. Si aparece vacío, pulsamos el botón Configure… para indicar dónde se encuentra.

La configuración predeterminada nos sirve. No obstante, recomiendo desactivar el check After launch para evitar que se abra un navegador web cada vez que finalice un nuevo despliegue.
Solo queda indicar el artefacto a desplegar. Nos vamos a la pestaña Deployment, pulsamos + (o Alt + Insert) y seleccionamos artifact .

Se abre un pequeño cuadro de diálogo y marcamos la versión exploded del war para que IntelliJ pueda publicar en el servidor los archivos que vamos modificando sin necesidad de empaquetarlos.

Guardamos los cambios y ya podemos desplegar hello en WildFly desde la parte superior derecha de la ventana principal con el botón que tiene forma de «play». También podemos hacerlo en modo depuración con el icono del «bichito».

Nota: en la carpeta deployments no debe estar el fichero hello.war que pusimos en la sección anterior dedicada al despliegue manual.
La salida estándar del servidor está en la vista de servicios.
Con los botones de la izquierda podemos gestionar el servidor y el despliegue del proyecto. Si realizamos cambios mientras se encuentre en ejecución, pulsando el botón Update o su atajo de teclado Control + F10 se muestra este diálogo.

La primera opción actualiza los ficheros que no sean código fuente Java. Estos cambios se aplican «en caliente», esto es, se reflejan enseguida. Pero si modificamos un .java, no queda más remedio que desplegar toda la aplicación con la tercera alternativa.
El comportamiento de esta actualización se puede personalizar en la configuración de ejecución. Con Show Dialog desmarcado, la orden Update realiza la tarea indicada en el desplegable y no se muestra la ventana de selección.

Eclipse
En demasiadas ocasiones, si queremos disfrutar en Eclipse de funcionalidades que IntelliJ ofrece de serie tenemos que instalar complementos o plugins. En el caso de WildFly, necesitamos JBoss Tools, conjunto de utilidades oficiales de Red Hat disponibles en el Marketplace (Help->Eclipse Marketplace…).

Este componente incluye muchos módulos y su instalación completa puede tardar bastante. Ahora mismo solo vamos a necesitar «JBoss AS, WildFly & EAP Server Tools», pero recomiendo tener paciencia e instalarlas todas. En cualquier caso, tras instalar y reiniciar Eclipse, importamos el proyecto con la opción File->Import->Existing Maven Projects.

Es suficiente con indicar la carpeta con el proyecto.
Podemos navegar por sus ficheros y librerías con el explorador que aparece a la izquierda. A diferencia de IntelliJ, en una misma ventana de Eclipse se muestran múltiples proyectos: todos los pertenecientes al espacio de trabajo («workspace») seleccionado. Nada nos impide abrir otra instancia de Eclipse y escoger un workspace distinto.

Comprobemos la JDK que Eclipse usa para compilar hello. Queremos que sea OpenJDK 11, y si su instalación no está registrada tendremos que añadirla. Abrimos el menú contextual del proyecto en el explorador para pulsar Properties. Nos vamos a la sección «Java Build Path», en concreto la pestaña «Libraries».
Marcamos la JRE que aparece y pulsamos Edit para mostrar la pantalla de selección.

Hay tres alternativas.
- Execution Enviroment. Aquí no seleccionamos una instalación de Java, sino un «entorno de ejecución» (Java 11, Java 15, etc). Eclipse aplica la versión de Java configurada para el entorno.
- Alternate JRE. Indicamos una instalación de Java.
- Workspace Default. La predeterminada en el IDE en cada momento para todo el espacio de trabajo.
Dado que queremos forzar el uso de una versión concreta, marcamos la segunda opción y la seleccionamos del desplegable. Si no aparece en el listado, con «Installed JREs..» se muestra una pantalla con las disponibles desde la que podemos añadir una nueva instalación con Add….

A continuación, intentemos desplegar hello con la opción «Run As» de la barra de herramientas (botón «play» en un círculo verde) o con el menú contextual (botón derecho del ratón) desplegable desde la carpeta con el nombre del proyecto en el explorador. También podemos hacer el despliegue en modo depuración con el icono del «bichito».

Al ser la primera vez, carecemos de una configuración para hacer el despliegue, así que el asistente nos pedirá definir un servidor. Seleccionamos nuestra versión de WildFly. Si vamos a usar una muy reciente es posible que no aparezca. Es el caso de la captura siguiente donde la más actual es la 21, a pesar de que en ese momento ya se había publicado la 22. Cuando nos encontremos ante esta situación, además de intentar actualizar JBoss Tools, seleccionamos la versión más alta. Por experiencia, no suele haber problemas.

Al final del asistente, indicamos el directorio de instalación de WildFly (también tenemos la posibilidad de descargar la versión seleccionada en la pantalla anterior), y la JDK de Java 11 que se usará para ejecutarlo.

Eclipse procederá a realizar el despliegue de forma inmediata. En la vista Console tenemos la salida del servidor.
Para utilizar el servidor, nos vamos a la vista Servers. Si no aparece, la activamos en el menú Window->Show View->Server. En esta pantalla lo arrancamos -incluso en modo depuración- y detenemos, aunque estas órdenes también las tenemos en la barra de acciones principal del IDE. Además, con el menú contextual de esta vista podemos añadir más servidores y agregar o eliminar artefactos a los mismos. Los servidores se configuran para todo el espacio de trabajo y pueden usarse con cualquiera de sus proyectos.
En la captura anterior se ve la pantalla de configuración del servidor, mostrada al hacer doble clic en él. El despliegue de los cambios que vayamos realizando mientras el servidor está en ejecución se personaliza en la sección «Publishing». Con las opciones predeterminadas, las modificaciones de ficheros que no sean código Java, por ejemplo HTML, se aplican cuando los archivos se guardan. Por el contrario, los cambios de código implican un despliegue completo, algo que podemos ordenar desde el menú contextual del proyecto dentro de la vista Servers con «Full Publish», o, de forma poco elegante, reiniciando WildFly.

Resulta más cómodo utilizar el atajo de teclado «Control+Shift+y», y pulsar «f» justo después. Es un decir porque la combinación es complicada, así que recomiendo cambiarla por una más práctica accediendo con Window->Preferences a la configuración de Eclipse, en concreto a General>Keys.
Consola de administración de WildFly
WildFly incluye una consola de administración, disponible en http://localhost:9990/console. Entre otras muchas funcionalidades, permite desplegar y retirar artefactos. Antes de utilizarla por primera vez, es necesario configurar sus usuarios con el script {WILDFLY}/bin/add/add-user, el .sh en Linux y el .bat en Windows. El siguiente video muestra cómo habilitar un típico usuario administrador llamado admin. Es obligatorio crear una contraseña.
La aplicación está en la pantalla «Deployments». Podemos eliminarla o desplegar cualquier otro artefacto.
Además de la interfaz web, el sistema de administración ofrece una API REST en la dirección http://localhost:9990/management. No la usaremos directamente, pero será necesaria cuando implementemos pruebas automáticas que hagan uso de WildFly.
Código de ejemplo
El código de ejemplo para este capítulo se encuentra en GitHub (todos los proyectos están en un único repositorio). Contiene un fichero Dockerfile correspondiente a otro capítulo. Para más información sobre cómo utilizar GitHub, consultar este artículo.