Guia completo: Dockerfile

Bora aprender um pouco mais sobre dockerfile?

Vamos agora aprender um pouco mais sobre as opções que podemos utilizar quando estamos criando um dockerfile:

  • ADD -- Copia novos arquivos, diretórios, arquivos TAR ou arquivos remotos e os adiciona ao filesystem do container.

  • CMD -- Executa um comando. Diferentemente do RUN, que executa o comando no momento em que está "buildando" a imagem, o CMD irá fazê-lo somente quando o container é iniciado.

  • LABEL -- Adiciona metadados à imagem, como versão, descrição e fabricante.

  • COPY -- Copia novos arquivos e diretórios e os adiciona ao filesystem do container.

  • ENTRYPOINT -- Permite que você configure um container para rodar um executável. Quando esse executável for finalizado, o container também será.

  • ENV -- Informa variáveis de ambiente ao container.

  • EXPOSE -- Informa qual porta o container estará ouvindo.

  • FROM -- Indica qual imagem será utilizada como base. Ela precisa ser a primeira linha do dockerfile.

  • MAINTAINER -- Autor da imagem.

  • RUN -- Executa qualquer comando em uma nova camada no topo da imagem e "commita" as alterações. Essas alterações você poderá utilizar nas próximas instruções de seu dockerfile.

  • USER -- Determina qual usuário será utilizado na imagem. Por default é o root.

  • VOLUME -- Permite a criação de um ponto de montagem no container.

  • WORKDIR -- Responsável por mudar do diretório "/" (raiz) para o especificado nele.

Um detalhe superimportante de mencionar é que quando estamos trabalhando com o ENTRYPOINT e o CMD dentro do mesmo dockerfile, o CMD somente aceita parâmetros do ENTRYPOINT, conforme nosso exemplo do dockerfile anterior:

ENTRYPOINT ["/usr/sbin/apachectl"]

CMD ["-D", "FOREGROUND"]

Onde:

  • "/usr/sbin/apachectl" -- Esse é o comando.

  • "-D", "FOREGROUND" -- Esse é o argumento, o parâmetro.

No shell, por exemplo, a execução ficaria assim:

# /usr/sbin/apachectl -D FOREGROUND

Ou seja, assim você está iniciando o Apache passando a instrução para que ele seja iniciado em primeiro plano, como deve ser. :D

Para maiores detalhes sobre como criar imagens, veja essa apresentação criada pelo Jeferson: https://www.slideshare.net/jfnredes/images-deep-dive.

8.4. Multi-stage

Um importante e recente recurso adicionado ao dockerfile visa facilitar a vida de quem pretende criar imagens de containers de forma efetiva. Esse cara é o multi-stage!

O multi-stage nada mais é do que a possibilidade de você criar uma espécie de pipeline em nosso dockerfile, podendo inclusive ter duas entradas FROM.

Esse recurso é muito utilizado quando queremos, por exemplo, compilar a nossa aplicação em um container e executá-la, porém não queremos ter aquela quantidade enorme de pacotes instalados em nossos containers necessários sempre quando se quer compilar códigos de alguma linguagem, como C, Java ou Golang.

Vamos a um exemplo para que possamos entender melhor como isso funciona!

Para isso, preparei uma app escrita em Golang superavançada para o nosso teste:

# vim goapp.go
package main

import "fmt"

func main() {

    fmt.Println("GIROPOPS STRIGUS GIRUS - LINUXTIPS")

}

Achou que seria algo avançado? Impossível, fomos nós que fizemos. :D

Bem, agora vamos criar um dockerfile para criar a nossa imagem e assim executar a nossa app.

# vim Dockerfile
FROM golang

WORKDIR /app

ADD . /app

RUN go mod init goapp && go build -o goapp

ENTRYPOINT ./goapp

Pronto! Agora vamos realizar o build.

# docker build -t goapp:1.0 .

Listando a nossa imagem:

# docker image ls | grep goapp
goapp    1.0    50451808b384    11 seconds ago      781MB

Agora vamos executá-la e ver a nossa fantástica app em execução:

# docker container run -ti goapp:1.0
GIROPOPS STRIGUS GIRUS -- LINUXTIPS

Pronto! Nossa app e nossa imagem estão funcionando! Sucesso!

Porém, podemos melhorar muita coisa se começarmos a utilizar o nosso poderoso recurso, o multi-stage!

Vamos refazer o nosso dockerfile utilizando o multi-stage, entender como ele funciona e a diferença entre as duas imagens.

Vamos deixar nosso dockerfile dessa maneira:

# vim Dockerfile
FROM golang AS buildando

ADD . /src

WORKDIR /src

RUN go build -o goapp

FROM alpine:3.1

WORKDIR /app

COPY --from=buildando /src/goapp /app

ENTRYPOINT ./goapp

Perceba que agora nós temos duas entradas FROM, o que não era possível antes do multi-stage. Mas por que isso?

O que está acontecendo é que agora temos o dockerfile dividido em duas seções. Cada entrada FROM define o início de um bloco, uma etapa.

Então, em nosso primeiro bloco temos:

  • FROM golang AS buildando -- Estamos utilizando a imagem do Golang para criação da imagem de container, e aqui estamos apelidando esse bloco como "buildando".

  • ADD . /src -- Adicionando o código de nossa app dentro do container no diretório "/src".

  • WORKDIR /src -- Definindo que o diretório de trabalho é o "/src", ou seja, quando o container iniciar, estaremos nesse diretório.

  • RUN go build -o goapp -- Vamos executar o build de nossa app Golang.

Já no segundo bloco temos o seguinte:

  • FROM alpine:3.1 -- Iniciando o segundo bloco e utilizando a imagem do Alpine para criação da imagem de container.

  • WORKDIR /app -- Definindo que o diretório de trabalho é o "/app", ou seja, quando o container iniciar, estaremos nesse diretório.

  • COPY --from=buildando /src/goapp /app -- Aqui está a mágica: vamos copiar do bloco chamado "buildando" um arquivo dentro de "/src/goapp" para o diretório "/app" do container que estamos tratando nesse bloco, ou seja, copiamos o binário que foi compilado no bloco anterior e o trouxemos para esse.

  • ENTRYPOINT ./goapp -- Aqui vamos executar a nossa sensacional app. :)

Agora que já entendemos todas as linhas do nosso novo dockerfile, 'bora realizar o build dele.

# docker build -t goapp_multistage:1.0 .

Vamos executar a nossa imagem para ver se está tudo funcionando:

# docker container run -ti goapp_multistage:1.0
GIROPOPS STRIGUS GIRUS - LINUXTIPS

Será que existe diferença de tamanho entre elas? Vamos conferir:

# docker image ls | grep goapp
goapp_multistage  1.0    dfe57485b7f0    22 seconds ago    7.07MB
goapp             1.0    50451808b384    15 minutes ago     781MB

A diferença de tamanho é brutal, pois em nossa primeira imagem precisamos ter um monte de pacotes para que o build da app Golang ocorra. Já em nossa segunda imagem também utilizamos a imagem do Golang e todos os seus pacotes para buildar a nossa app, porém descartamos a primeira imagem e somente copiamos o binário para o segundo bloco, onde estamos utilizando a imagem do Alpine, que é superenxuta.

Ou seja, utilizamos o primeiro bloco para compilar a nossa app e o segundo bloco somente para executá-la. Simples assim, simples como voar! :D

Deixe um comentário