Como criar uma docker image para sua API feita com Java?
Tags: docker, java, webdev, api
Published: 2023-02-21 14:52:23

#Introdução
Uma **_Docker Image_** é um pacote executável que contém todas as ferramentas e softwares necessários para executar uma aplicação, incluindo, além do próprio código-fonte, bibliotecas, dependências, configurações e variáveis de ambiente. Ao executar uma **_Docker Image_** o resultado é a criação de um **_container_** , que é o ambiente isolado onde o conteúdo da imagem irá rodar. 

O **_container_** é como se fosse uma miniatura do sistema operacional, que isola a aplicação do ambiente em que está sendo executada. Esse isolamento permite que ela funcione de forma independente do sistema operacional ou hardware da máquina utilizada. 

As **_Docker Images_** permitem que nós possamos criar ambientes de desenvolvimento e produção consistentes, sem conflitos ou problemas de compatibilidade. É o fim definitivo para o problema "_Mas na minha máquina funciona!_".

Além disso, as imagens podem ser compartilhadas no **_Docker Hub_**, e reutilizadas por outras equipes de desenvolvimento, tornando o processo de desenvolvimento mais eficiente e escalável. 

O objetivo deste tutorial será a criação de uma imagem especificamente para uma API Java com Spring Boot. Será usado **_Maven_** para gerenciamento e build, na sua versão **_3.8.7_**.

##Instalando o Docker
Primeiro é necessário instalar o **_Docker_** no seu computador. A própria [documentação da ferramenta](https://docs.docker.com/engine/install/) já traz tutoriais simplificados, com passo-à-passo específico para cada sistema operacional, basta escolher uma opção e seguir os comandos.

##Criando o Dockerfile
O **_Dockerfile_** é o arquivo que conterá as instruções para a construção da **_Docker Image_**, como uma receita de cozinha que descreve todos os ingredientes e passos necessários para criar uma refeição. No caso do **_Dockerfile_**, as instruções descrevem o que deve ser incluído na imagem Docker, como o sistema operacional, as bibliotecas e as dependências, e como configurar o ambiente para a aplicação. 

O primeiro passo é criar um arquivo de nome **_Dockerfile_**, sem extensão, dentro da pasta raiz da aplicação.

```
- app
    - .idea/
    - .mvn/
    - src/
    - target/
    - .gitignore
    - Dockerfile
    - HELP.md
    - mvnw
    - mvnw.cmd
    - pom.xml
```

O arquivo irá apontar para uma **_Docker Image_** que será usada como base para a criação da nossa própria imagem e também conterá todos os comandos necessários para a build da aplicação e sua execução.

##Escolhendo a imagem base
Por termos escolhido o **_Maven 3.8.7_** como compilador e gerenciamento e o **_JDK 19_** para a execução da aplicação Java, deveremos usar como base a **_Docker Image_** [maven:3.8.7-eclipse-temurin-19-alpine](https://hub.docker.com/layers/library/maven/3.8.7-eclipse-temurin-19-alpine/images/sha256-035256f20554c181fc63b92efe7c813c85e5eb7e294be4dc365bbe4da74dd083?context=explore), que já contém ambas as ferramentas e suas dependências instaladas sob um sistema operacional linux. 

A escolha da imagem base vai variar conforme as necessidades, sendo possível utilizar desde uma imagem base de um Ubuntu novo, e dentro do próprio **_Dockerfile_** configurar comandos para que o **_Maven_** e a **_JDK_** sejam instalados, quanto utilizar uma imagem já com ferramentas pré-instaladas, como é o caso da [maven:3.8.7-eclipse-temurin-19-alpine](https://hub.docker.com/layers/library/maven/3.8.7-eclipse-temurin-19-alpine/images/sha256-035256f20554c181fc63b92efe7c813c85e5eb7e294be4dc365bbe4da74dd083?context=explore).

##Adicionando a imagem base ao arquivo Dockerfile
Agora iremos adicionar conteúdo ao arquivo Dockerfile, e o primeiro passo será apontar para a imagem base, e isso é feito com o comando **_`FROM image-name`_**.

```Dockerfile
# Start with maven:3.8.7-eclipse-temurin-19-alpine base image
FROM maven:3.8.7-eclipse-temurin-19-alpine

# Maintaner data 
LABEL maintainer="Mateus Vinícius <mateus.limavn@gmail.com>"

# End
``` 
O comando **_`LABEL`_** é usado para adicionar informações e metadados à imagem que está sendo criada, como o nome do mantenedor, a versão da imagem, a descrição da aplicação e etc. Essa prática ajuda na organização e identificação das imagens, já que o comando `docker images` exibe essas informações registradas no **_`LABEL`_** juntamente com as informações da própria imagem.

##Adicionando comandos para a build da aplicação
Após apontarmos a imagem que será usada como base, é hora de configurar os comandos que serão executados para fazer a build da nossa aplicação Java. O build será feito com o comando `mvn package`.

```dockerfile
# Start with maven:3.8.7-eclipse-temurin-19-alpine base image
FROM maven:3.8.7-eclipse-temurin-19-alpine

LABEL maintainer="Mateus Vinícius <mateus.limavn@gmail.com>"
# Set the working directory to /app
WORKDIR /app

# Copy the source code to the container
COPY . .

# Build the application with Maven
RUN mvn package

#End
```

O comando `WORKDIR` define o diretório de trabalho padrão para as próximas instruções do arquivo. Isso permite que os comandos que serão executados saibam qual é o caminho do diretório da aplicação sem que seja necessário repetir o caminho absoluto a todo momento. O padrão é usar o diretório `/app`.

Já o comando `COPY <fonte> <destino>` copia o conteúdo da imagem para o container que será criado, que é o ambiente de execução da aplicação. Nesse caso todo o conteúdo da imagem será copiado para o container.

Para fazer a build da aplicação Java utilizaremos o comando `mvn package` do **_Maven_**, por isso ele é chamado com o `RUN`, que serve para executar outros comandos ou instruções.

##Definindo as variáveis de ambiente
A maioria dos projetos utilizam variáveis de ambiente, elas são usadas tanto pelas aplicações quanto pelo próprio sistema operacional para acessar as configurações definidas.

No caso da aplicação Java que é usada de exemplo, as variáveis de ambiente estão sendo chamadas dentro do arquivo **_application-prod.properties_** da seguinte forma:

```properties
spring.datasource.url=${DATASOURCE_URL}
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.datasource.username=${DATASOURCE_USERNAME}
spring.datasource.password=${DATASOURCE_PASSWORD}
```

No caso, os valores `${DATASOURCE_URL}`, `${DATASOURCE_USERNAME}` e `${DATASOURCE_PASSWORD}` são as variáveis de ambiente. Elas estão definindo a _url_ de conexão com o banco de dados, o usuário `root` desse banco e a sua senha.

Pra facilitar o reaproveitamento da imagem o ideal é que essas propriedades de configuração da aplicação que sejam dinâmicas passem a ser atribuídas à variáveis de ambiente, assim o desenvolvedor poderá modificá-las antes de executar o container. 

Mas é importante, também, atribuir valores padrão à essas variáveis, e é pra isso que serve o comando `ENV`.

```Dockerfile
# Start with maven:3.8.7-eclipse-temurin-19-alpine base image
FROM maven:3.8.7-eclipse-temurin-19-alpine

LABEL maintainer="Mateus Vinícius <mateus.limavn@gmail.com>"
# Set the working directory to /app
WORKDIR /app

# Copy the source code to the container
COPY . .

# Build the application with Maven
RUN mvn package

# Set environment variables if needed
ENV DATASOURCE_URL=jdbc:postgresql://localhost:5432/db
ENV DATASOURCE_USERNAME=postgres
ENV DATASOURCE_PASSWORD=postgrespassword

#End
```

##Informando as portas que o container estará executando
As portas que serão expostas para que a API receba requisições são definidas com o comando `EXPOSE`, e, nesse caso, utilizaremos a padrão **_8080_**.

```Dockerfile
# Start with maven:3.8.7-eclipse-temurin-19-alpine base image
FROM maven:3.8.7-eclipse-temurin-19-alpine

LABEL maintainer="Mateus Vinícius <mateus.limavn@gmail.com>"
# Set the working directory to /app
WORKDIR /app

# Copy the source code to the container
COPY . .

# Build the application with Maven
RUN mvn package

# Set environment variables if needed
ENV DATASOURCE_URL=jdbc:postgresql://localhost:5432/db
ENV DATASOURCE_USERNAME=postgres
ENV DATASOURCE_PASSWORD=postgrespassword

# Expose default Spring Boot port
EXPOSE 8080

#End
```

##Definindo o comando para execução do arquivo jar
Agora que o container já fez a build da aplicação, configurou as variáveis de ambiente e expôs as portas para requisições, chegou o momento de definir o comando para executar o arquivo `jar` que é gerado pela build do **_Maven_**.

Para isso iremos utilizar o comando `CMD`, que recebe uma array de argumentos que será executado em uma linha do terminal.

O comando para executar uma aplicação java, que está compilada num arquivo `jar`, e utilizando o profile de produção é `java -jar target/myapp-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod`, portanto, o comando `CMD` correspondente será `CMD ["java", "-jar", "target/myapp-0.0.1-SNAPSHOT.jar", "--spring.profiles.active=prod"]`.

Ao final nosso arquivo Dockerfile ficará com o seguinte conteúdo:
```Dockerfile
# Start with maven:3.8.7-eclipse-temurin-19-alpine base image
FROM maven:3.8.7-eclipse-temurin-19-alpine

LABEL maintainer="Mateus Vinícius <mateus.limavn@gmail.com>"
# Set the working directory to /app
WORKDIR /app

# Copy the source code to the container
COPY . .

# Build the application with Maven
RUN mvn package

# Set environment variables if needed
ENV DATASOURCE_URL=jdbc:postgresql://localhost:5432/db
ENV DATASOURCE_USERNAME=postgres
ENV DATASOURCE_PASSWORD=postgrespassword

# Expose default Spring Boot port
EXPOSE 8080

# Run the jar file
CMD ["java", "-jar", "target/myapp-0.0.1-SNAPSHOT.jar", "--spring.profiles.active=prod"]

#End
```

##Criando a Docker Image
Já com o **_Docker_** instalado e o arquivo **_Dockerfile_** configurado, podemos realizar a build da imagem. Para isso, basta abrir no _terminal_ ou _prompt de comando_ a pasta da aplicação - a mesma em que o arquivo **_Dockerfile_** se encontra - e executar o comando `docker image build -t myapp-api .`

O **_Docker_** irá executar todos os comandos presentes no arquivo **_Dockerfile_**, o que inclui a build da aplicação usando o `mvn build`, e, ao fim, irá gerar a imagem automaticamente.

Para ver e executar a imagem gerada, basta rodar o comando `docker images` no _terminal_ ou _prompt de comando_.

##Conclusão
Com a imagem gerada rodar a aplicação se torna muito mais fácil, já que basta rodar o comando `docker run <nome-da-imagem>` para que o container de execução seja criado.

Agora você pode salvar essa imagem num repositório no **_Docker Hub_** para executar em qualquer servidor ou máquina pessoal, sem se preocupar com a configuração do ambiente ou problemas de incompatibilidade. Sua aplicação pode rodar em qualquer lugar!