Abrindo mares: Dockerfile, o que precisamos saber!
Tags: docker, dockerfile, development, beginners
Published: 2023-06-16 02:28:22

Para pensar sobre o que são os Dockerfiles e o que fazemos com eles é bom ter em mente que sempre estamos lidando com containers. Containers são criados a partir de imagens e imagens são criadas usando os famosos Dockerfiles.

Dockerfiles são arquivos de texto onde colocamos algumas **sequências** de comandos para gerar nossa imagem da aplicação.

É importante lembrar da palavra SEQUÊNCIA pois é exatamente isso que vamos fazer em um Dockerfile: escrever uma sequência de comandos para serem executados. Podemos até chamar de "receita de bolo" para criar uma imagem de nosso app.

Mas temos que ficar atentos a algumas regras básicas para escrever essa nossa receita.

## Formato

A estrutura de um Dockerfile é bem parecida com um *yaml* mas com algumas particularidades.

Este é um exemplo de formatação de um Dockerfile:

```
# Comentário
INTRUÇÃO argumento
```

A convenção para a escrita de Dockerfile é: Escrever as instruções em LETRA MAIÚSCULA para facilitar a separação do que é uma instrução e o que é um argumento.

Segue um exemplo de como é um Dockerfile real, que no caso executa o build de um app Go.

```docker
FROM golang:1.20-alpine

COPY go.* ./
RUN go mod download && go mod verify

COPY . ./
RUN CGO_ENABLED=0 go build -o /app/server ./cmd/main.go

EXPOSE 8080

CMD["/app/server"]
```

Primeiro e com letras maiúsculas vem os comandos para o Docker entender o que queremos fazer. Logo após vem os argumentos que compõe os comandos.

## Comandos mais utilizados


### **FROM**

Onde escolhemos a nova Base Image e dizemos para o Docker que estamos iniciando um novo build. Por exemplo, no exemplo acima, estamos usando a `golang:1.20-alpine` como Base Image pois estamos criando um app Go.

```docker
FROM golang:1.20-alpine
```

“Poxa Rafa, mas onde eu arrumo o nome dessas Base Images? São aleatórios?” E a resposta é sempre não. Podemos encontrar base images no [Docker Hub](https://hub.docker.com/) oficiais das linguagens, como [Golang](https://hub.docker.com/_/golang), [Java](https://hub.docker.com/_/openjdk), [Node](https://hub.docker.com/_/node) e utilizar a convenção para colocar os parâmetros.

A convenção para o FROM é:

```docker
FROM <image>[:<tag>]
```


### **COPY**

O comando COPY copia os arquivos ou pastas do `<src>`, no caso a pasta onde esta o Dockerfile, e os adiciona para o destino final, que é a imagem que estamos criando.

```docker
COPY . ./
```

O COPY  é geralmente utilizado seguindo esta convenção:

```docker
COPY <src>... <dest>
```

Sempre colocamos o que queremos copiar primeiro `<src>` e logo após para onde queremos copiar `<dest>`. No exemplo onde usamos o `. ./`, estamos dizendo que queremos copiar todos os arquivos do nosso diretório atual (onde esta o Dockerfile) para a raiz do diretório de destino.


### **RUN**

Este é o comando que executa qualquer comando.  É como se você estivesse dentro de um `bashfile` dizendo que quer fazer algum comando. Por exemplo, `RUN ["/bin/bash", "-c", "echo hello"]` é como se rodássemos o comando direto do terminal do container. Dessa forma podemos executar todos os comandos que são necessários.

```docker
RUN go mod download && go mod verify
```

Acima podemos ver o primeiro comando RUN que executamos dentro de nosso Dockerfile.
Ele baixa todas as dependências do projeto e as instala na imagem e, em seguida, verifica se todas as dependências baixadas não foram alteradas para que nossa aplicação rode. 

O RUN pode ser executado de duas maneiras com base nessa convenção:

```docker
#1
RUN <comando>
#2
RUN ["executável", "parametro1", "parametro2"]
```

Aqui vimos as duas maneiras, tanto passando o comando logo em seguida do RUN, quanto chamando um executável no caso do comando echo.


### EXPOSE

Aqui é onde falamos para o Docker por qual porta o container vai interagir em tempo de execução. Podemos escolher qualquer porta, tanto UDP quanto TCP. Se não especificarmos o protocolo, é assumido por padrão que vamos utilizar TCP.

```docker
EXPOSE <port> [<port>/<protocol>...]
```

Em nosso exemplo utilizamos a `8080` para expor nosso serviço e como não passamos o protocolo, ele assumirá o TCP. Assim quando rodarmos o nosso container e acessarmos a URL  [`http://localhost:8080/`](http://localhost:8080/) teremos nosso serviço rodando.


### CMD

E por último, mas não menos importante, o CMD, que tem uma função parecida com o RUN de executar os comandos, porém é reservado para o contexto de runtime. Ou seja, quando executamos o container é o comando que é executado de imediato. 

```docker
CMD ["/app/server"]
```

Esses são os principais comandos que utilizamos no dia-a-dia para criar imagens e buildar projetos 🙂. 


## A receita, resumo

Se você parar e analisar friamente, os passos para criar uma imagem usando Docker são praticamente os mesmos passos que fazemos para rodar uma aplicação em nosso computador pela primeira vez. 

Assim como uma receita de bolo.

| Configurar o SDK         | `FROM golang:1.20-alpine`                                 |
| ------------------------ | ------------------------------------------------------- |
| Clonar o projeto         | `COPY . ./`                                               |
| Instalar as dependências | `RUN go mod download && go mod verify`                    |
| Fazer o build            | `RUN CGO_ENABLED=0 go build -o /app/server ./cmd/main.go` |
| Executá-lo               | `CMD["/app/server"]`                                      |



Assim, fica claro de entender o que temos que fazer e quais passos temos que seguir para qualquer aplicação que queremos criar uma imagem, independente da linguagem ou ferramentas que estamos utilizando. 

E pra finalizar, como sempre deixo aqui o link da [documentação oficial](https://docs.docker.com/engine/reference/builder/) de Dockerfile para vocês se aprofundarem ainda mais em outros comandos.