Skip to main content


DockerFile


O Dockerfile é um arquivo de texto que contém um conjunto de instruções que o Docker Engine usa para criar uma imagem Docker. Ele fornece uma maneira simples e repetível de definir o ambiente de execução de um contêiner, incluindo o sistema operacional base, dependências, configurações de ambiente e comandos de execução do aplicativo.


Vamos criar um Dockerfile bem simples, para começar a entender como funciona:

Terminal
# Criar a pasta
╼ $ mkdir /tmp/docker/

# Crie nosso Dockerfile:
╼ $ echo << 'EOF' > /tmp/docker/dockerfile
FROM debian
RUN /bin/echo "Hello"
EOF

# Agora é só fazer o build da imagem (criar uma imagem Docker a partir de um Dockerfile):
╼ $ sudo docker build -t tosko:1.0 /root/docker/
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM debian
---> b5d2d9b1597b
Step 2/2 : RUN /bin/echo "Hello"
---> Running in fe11368daf04
Hello
Removing intermediate container fe11368daf04
---> 36053cf3254f
Successfully built 36053cf3254f
Successfully tagged tosko:1.0


Image Layers


Toda imagem criada a partir de um Dockerfile é composta por camadas, também conhecidas como "layers". Cada camada representa uma instrução no Dockerfile. Portanto, cada instrução adicionada ao Dockerfile resultará na criação de uma nova camada na imagem final. A primeira instrução no Dockerfile geralmente é a instrução FROM, que especifica a imagem base a ser utilizada. Essa instrução FROM é considerada a camada zero da imagem. Todas as outras camadas são construídas sobre essa camada inicial.


As camadas em uma imagem Docker são projetadas para seguir uma hierarquia, onde cada camada depende da camada anterior. Isso significa que as camadas superiores na imagem dependem das camadas inferiores para funcionar corretamente. Quando uma camada superior é modificada, o Docker reutiliza as camadas inferiores que não foram alteradas, minimizando a necessidade de transferência de dados durante a construção e distribuição de imagens Docker.


Abaixo segue um Dockerfile com duas camadas, apenas para mostrar um exemplo bem básico de Dockerfile.

Dockerfile
FROM debian

RUN /bin/echo "Hello"


Layer caching


No Docker, Layer caching refere-se ao mecanismo usado para reutilizar camadas previamente construídas de uma imagem anterior ao criar uma nova imagem. Isso acelera o processo de build ao evitar a reconstrução de camadas que não foram alteradas. Vamos pegar o Dockerfile abaixo como exemplo:

Dockerfile
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y curl
COPY app/ /app/
CMD ["sh", "/app/start.sh"]

Cada instrução no Dockerfile, como FROM, RUN, e COPY, cria uma camada independente na imagem. Essas camadas são armazenadas no cache local do Docker, e ao executar docker build, o Docker verifica se pode reutilizar camadas existentes. Se uma instrução e o contexto relacionado não mudaram desde o último build, a camada correspondente será reaproveitada.


No entanto, se houver alterações em uma camada, todas as camadas subsequentes no Dockerfile precisam ser reconstruídas, mesmo que não tenham mudado diretamente. Imagine, por exemplo, que alteramos o conteúdo do diretório /app, atualizando nosso código. Ao executar docker build novamente, apenas as camadas a partir do COPY app/ /app/ serão recriadas. O Docker aproveitará as camadas de FROM e RUN, o que significa que não será necessário baixar o repositório novamente nem reinstalar o curl, pois essas etapas já estão armazenadas no cache.


Nesse caso, o Docker criará novas camadas para as instruções COPY e CMD, enquanto reutiliza as camadas anteriores. Essa abordagem é especialmente vantajosa em imagens grandes, reduzindo significativamente o tempo de build. Em builds remotos, ela também minimiza os dados enviados, ajudando a diminuir custos de transferência.


Se preferir desativar o uso de cache durante o build, basta adicionar o parâmetro --no-cache ao comando docker build. Isso força a reconstrução de todas as camadas, garantindo que nenhuma etapa reutilize resultados anteriores.


Se novas camadas forem adicionadas ao Dockerfile, apenas as camadas abaixo da que sofreu alterações precisam ser reconstruídas. As camadas acima da alteração, que permanecem inalteradas, serão reaproveitadas do cache. Isso significa que o Docker verifica camada por camada, reutilizando as que não foram impactadas pela mudança.


atenção ao criar um Dockerfile

Ao projetar as camadas em um Dockerfile, é importante considerar a estrutura para facilitar e agilizar os testes e o desenvolvimento locais. Dividir as instruções em camadas de maneira lógica e eficiente permite que o Docker reutilize as camadas do cache durante o processo de construção da imagem. Isso reduz significativamente o tempo necessário para construir imagens e executar os testes, já que apenas as camadas modificadas precisam ser reconstruídas.


O uso do cache também é aplicável aos repositórios online, como Docker Hub, AWS ECR, Azure Container Registry e Google Cloud Container Registry. Quando você envia uma imagem para um repositório online, o Docker tenta otimizar o processo de upload, enviando apenas as camadas que não existem no repositório remoto. Isso minimiza a largura de banda necessária e acelera o processo de implantação de imagens em ambientes de produção.



DockerFile multistage


O Dockerfile multistage é uma funcionalidade avançada do Docker que permite construir imagens Docker em múltiplas etapas. Essa abordagem é particularmente útil quando você precisa compilar ou construir um aplicativo dentro de um contêiner, mas não deseja incluir todas as ferramentas de compilação e dependências na imagem final. Isso resulta em imagens menores, mais eficientes e mais seguras.


dockerfile
# Etapa de compilação
FROM golang:1.16 AS build
WORKDIR /app
COPY . .
RUN go build -o myapp .

# Etapa final
FROM alpine:latest
WORKDIR /root/
COPY --from=build /app/myapp /app/myapp
CMD ["./myapp"]

O parâmetro --from=builder na instrução COPY em um Dockerfile é usado para copiar arquivos ou diretórios de um estágio anterior de um Dockerfile multistage para o estágio atual.


Neste exemplo, a primeira etapa usa a imagem oficial do Golang para compilar o aplicativo e produzir um executável chamado myapp. Na segunda etapa, apenas a imagem Alpine é usada, e o executável myapp é copiado para ela.


Na etapa final eu usei a imagem do alpine na última versão, quando for somente para executar um binário, como num projeto go, java, kotlin, podemos usar uma imagem chamada Scratch. A imagem scratch é uma opção excelente para executar binários estáticos. Vamos entender o porquê.


O que é a imagem scratch?

A imagem scratch é uma imagem base mínima fornecida pelo Docker. Ela é literalmente vazia (não contém bibliotecas, shell, ferramentas ou qualquer sistema operacional). É como começar "do zero". Uma imagem baseada em scratch terá apenas o tamanho do binário mais os arquivos necessários para a aplicação.


A imagem scratch é frequentemente considerada a base "raiz" para muitas outras imagens, mas não é usada diretamente como base na maioria das imagens que usamos.



Usando o DockerFile


Existem várias opções que você pode usar ao criar um Dockerfile para construir uma imagem Docker. Aqui estão algumas das opções mais comuns:


OpçãoDescrição
FROMEspecifica a imagem base a ser usada para construir a sua imagem.
RUNExecuta um comando no shell durante o processo de construção da imagem.
USERPor padrão o Docker executa tudo dentro do container como root, mas podemos usar outro usuário.
COPYCopia arquivos do host para o sistema de arquivos da imagem.
ADDSimilar ao COPY, mas com funcionalidades adicionais, como suporte a URLs e extração automática de arquivos comprimidos.
WORKDIRDefine o diretório de trabalho para os comandos subsequentes no Dockerfile.
CMDEspecifica o comando padrão a ser executado quando o contêiner for iniciado a partir da imagem.
ENTRYPOINTDefine o comando a ser executado quando o contêiner for iniciado.
EXPOSEInforma ao Docker que o contêiner escuta em determinadas portas durante a execução. Ainda é necessário usar a flag -p no Docker para que ocorra o bind da porta do host com a porta do container. A instrução EXPOSE apenas documenta quais portas o aplicativo dentro do contêiner está configurado para escutar durante a execução. No entanto, ela não publica automaticamente essas portas no host.
Caso use apenas o EXPOSE, apenas a máquina host conseguirá acessar o contêiner, usando localhost na porta especificada.
ARGÉ usado para declarar variáveis de build. Essas variáveis recebem valores atraves do parâmetro --build-arg no comando docker build. As variáveis definidas aqui não são acessíveis no Container.
ENVDefine variáveis de ambiente dentro do contêiner.
LABELAdiciona metadados à imagem, como informações de versão, descrição, mantenedor, etc.
MaintainerAutor da Imagem.
VOLUMEDefine um ponto de montagem para volumes externos dentro do contêiner. Força a criação do Volume sem nem precisar rodar o docker run com a informação de volume.

Abaixo vamos criar uma imagem personalizada, para isso, vamos usar a imagem do Debian como base. Você pode acessar a documentação oficial do Dockerfile clicando aqui.

dockerfile
# Imagem que vai servir como base (imagem do Debian)
FROM debian

# Lista de comandos que será executado na criação da imagem.
RUN apt update && apt install -y apache2 && apt-get clean

# Variáveis de ambiente
ENV APACHE_LOCK_DIR="/var/lock"
ENV APACHE_PID_FILE="/var/run/apache2.pid"
ENV APACHE_RUN_USER="www-data"
ENV APACHE_RUN_GROUP="www-data"
ENV APACHE_LOG_DIR="/var/log/apache2"

# Metadata como descrição, versão e etc...
LABEL description="WebServer"

# Volume que será montado no container
VOLUME [ "/var/www/html/" ]

# Expondo a porta 80
EXPOSE 80

# Permite que o container execute um executável, após o executável ser executado, o container também é.
# Comando = /usr/sbin/apachectl
ENTRYPOINT ["/usr/sbin/apachectl"]

# Executa um comando quando o container for executado, nesse caso será um argumento.
CMD ["-D", "FOREGROUND"]
# Arumento = -D FOREGROUND

OBS.: Se tivermos ENTRYPOINT e CMD no mesmo DockerFile, o CMD só aceitará paramêtros do ENTRYPOINT.
Para relembrar sobre CMD e ENTRYPOINT, releia aqui.


apt-get

É sempre uma má ideia executar comandos como apt-get install ou yum|dnf install no Dockerfile, executar esse tipo de comando torna o tempo de build mais demorado. Considere sempre usar uma imagem que já tem o que precisa instalar ou crie uma.


Além de que, isso cria uma nova layer na imagem. Então, se precisar fazer esse tipo de coisa, tente agrupar os comandos na mesma linha, usando && como forma de executar mais de um comando. Outra alternativa seria usar um script, mas isso não elimina o tempo do build, ainda vai demorar.


Agora vamos buildar (construir) nossa imagem.

Terminal
╼ $ docker build -t linux/apache:1.0 .
Successfully built 72882852dae6
Successfully tagged linux/apache:1.0

Após executar o comando acima, você verá que o docker vai baixar a image caso ainda nao tenha, e vai começar a atualizar verificar o repositorio buscando atualizações e depois vai começar a baixar e instalar o apache2.


Execute o comando abaixo para listar as imagens do Docker:

Terminal
╼ $ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
linux/apache 1.0 72882852dae6 48 seconds ago 243MB
debian latest 971452c94376 2 weeks ago 114MB

Agora vamos criar um container que utilize nossa imagem.

Terminal
# Criando um container que use uma imagem que nós criamos:
╼ $ docker container run -ti linux/apache:1.0

# Verificando se o apache está habilitado:
\# service apache2 status
[FAIL] apache2 is not running ... failed!

# Iniciando o apache2:
\# service apache2 start
[....] Starting Apache httpd web server: apache2AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
. ok

# Verificando novamente se o apache está funcionando:
\# service apache2 status
[ ok ] apache2 is running.

# Podemos ver se o processo dele está ativo também:
\# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 13:45 pts/0 00:00:00 bash
root 62 1 0 13:47 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 65 62 0 13:47 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 66 62 0 13:47 ? 00:00:00 /usr/sbin/apache2 -k start
root 166 1 0 13:48 pts/0 00:00:00 ps -ef

# Listando as portas ativas:
\# ss -atn
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 0.0.0.0:80 0.0.0.0:*

# Liste as interfaces do container:
\# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
52: eth0@if53: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever

Como podemos ver, o IP do nosso container é 172.17.0.2/16. Para sair do container e ainda deixar ele ativo, use a combinação de teclas Ctrl + p + q.


Após acessar o IP do Container via Browser ou com o curl, nosso container está com apache rodando e totalmente acessível, se pingarmos o container, vamos notar que temos comunicação com ele.



Usando ARG


Como já explicamos, é usado para declarar variáveis no momento do build da imagem. Podemos usar ele para evitar expor valores sensíveis no arquivo Dockerfile. Os argumentos são passados durante o build da imagem, permitindo que valores sensíveis, como senhas ou chaves de API, sejam injetados de forma segura no processo de construção da imagem.


Isso aumenta a segurança e evita a exposição de informações confidenciais no Dockerfile ou na imagem resultante. Os ARGS podem ser usados em conjunto com a instrução ENV para definir variáveis de ambiente que serão usadas durante a execução do contêiner.


Essas variáveis recebem valores direto no Dockerfile ou através do parâmetro --build-arg no comando docker build. Ela pode ser declarada de duas formas:

  1. Informando o valor da variável:

    Dockerfile
    # Declara uma variável de build com valor padrão:
    ARG APP_VERSION=1.0
  2. Deixando apenas o nome da variável, nesse caso o valor deve ser fornecido no momento do build:

    Dockerfile
    # Declara uma variável de build sem valor:
    ARG APP_VERSION

    Para buildar, fariamos assim:

    docker build -t meuapp --build-arg APP_VERSION=2.5 .

As variáveis definidas com ARG só podem ser usadas durante a construção da imagem. Elas não são acessíveis no contêiner em execução. Se você precisa de variáveis disponíveis no contêiner em execução, use o comando ENV. Uma forma de usar no container poderia mesclar ARG com ENV.

Dockerfile
ARG APP_VERSION
ENV APP_VERSION=${APP_VERSION:-1.0}

A linha ARG APP_VERSION declara a variável de build APP_VERSION sem valor. Você pode sobrescrever esse valor no comando docker build, um exemplo seria --build-arg APP_VERSION=2.5.


Já a linha ENV APP_VERSION=${APP_VERSION}, define uma variável de ambiente que será acessível no contêiner usando o valor de APP_VERSION definido anteriormente no ARG, caso não seja definido um valor no momento do build, será usado um valor padrão que é 1.0. Isso garante que a variável sempre tenha um valor e que esse valor persista no contêiner em execução.



Dockerignore


O arquivo .dockerignore é usado para especificar quais arquivos e diretórios devem ser ignorados durante o processo de construção da imagem Docker. Assim como o arquivo .gitignore é usado para o Git, o .dockerignore permite que você exclua arquivos e diretórios desnecessários ou sensíveis do contexto de construção da imagem, reduzindo assim o tamanho da imagem resultante e protegendo informações confidenciais.



Opção USER


Por padrão, a maioria dos contêineres Docker é executada com o usuário root, o que pode trazer riscos significativos de segurança, especialmente em ambientes de produção. Contudo, algumas tarefas específicas durante o processo de build e deploy podem exigir permissões de root para serem concluídas sem erros.


Após realizar essas tarefas que exigem privilégios elevados, é uma boa prática criar explicitamente um usuário não root e conceder a ele o nível mínimo de acesso necessário para executar o aplicativo. Para isso, utilize ferramentas como chmod e chown para ajustar as permissões de arquivos e diretórios.


Antes das diretivas ENTRYPOINT ou CMD, adicione a instrução USER para garantir que o aplicativo seja executado como um usuário não root. Isso protege o ambiente de execução, restringindo o que pode ser acessado pelo contêiner.


Veja um exemplo:

dockerfile
# Base da imagem:
FROM alpine

# Instalação de dependências (executada como root):
RUN npm install -y xxx

# Criação de um usuário não root:
RUN adduser -h /var/myuser -s /bin/bash -D myuser

WORKDIR /var/myuser

# Copia os arquivos necessários do estágio de construção:
COPY /algum/arquivo /var/myuser

# Ajusta as permissões para o novo usuário:
RUN chown -R myuser:myuser /var/myuser

# Definição do usuário não root para execução do aplicativo:
USER myuser

# Comando de inicialização do aplicativo:
CMD ["./meu_aplicativo"]

Seguir essa prática não apenas melhora a segurança do ambiente, mas também demonstra atenção às boas práticas de desenvolvimento com Docker.



HealthCheck


O comando HEALTHCHECK no Dockerfile é usado para definir verificações de integridade que ajudam a determinar se um contêiner está funcionando corretamente. Ele executa comandos em intervalos regulares dentro do contêiner, retornando um status de saúde que pode ser usado por ferramentas como orquestradores (ex.: Docker Swarm, Kubernetes) ou pelo próprio Docker para monitorar o estado do contêiner.


Se o return code do comando definido no HEALTHCHECK for diferente de 0, o Docker marca o contêiner como unhealthy. Isso impacta diretamente como o tráfego é tratado, especialmente em orquestradores como Docker Swarm ou Kubernetes, mas o comportamento isolado depende do contexto.


Com o Docker Standalone, ou seja, sem orquestrador, o Docker não interrompe automaticamente o tráfego para o contêiner unhealthy. Contudo, ferramentas externas que monitoram o estado do contêiner (como proxies ou load balancers) podem usar o status de integridade para decidir se devem enviar tráfego para o container ou não.


Em ambientes com orquestradores (como Docker Swarm ou Kubernetes), se o contêiner estiver marcado como unhealthy, ele será removido do pool de balanceamento de carga. O orquestrador pode tomar ações automáticas, como:

  • Reiniciar o contêiner.
  • Criar um novo contêiner para substituí-lo.

Exemplo de healthcheck configurado para Docker Swarm:

Dockerfile
FROM nginx:latest

# Configuração de healthcheck
HEALTHCHECK --interval=5s --timeout=3s --retries=3 \
CMD curl -f http://localhost || exit 1


Onbuild


O comando ONBUILD no Dockerfile é usado para definir instruções que só serão executadas quando uma imagem derivada for construída. É uma forma de configurar gatilhos (triggers) que serão aplicados a futuras construções baseadas na sua imagem.


Dockerfile
ONBUILD <instrucao>

A instrução <instrucao> pode ser qualquer comando Dockerfile válido (exceto ONBUILD novamente, para evitar encadeamentos). Quando você cria a imagem base, os comandos especificados em ONBUILD não são executados. Eles são armazenados como metadados. Ao usar a imagem como base para outra construção, os comandos são disparados automaticamente.



CMD e Entrypoint


Existem dois parâmetros principais usados para definir o comando principal de um container no momento de sua inicialização: ENTRYPOINT e CMD. Se nenhum comando específico for fornecido na execução do container, o mesmo será iniciado com base nesses parâmetros (que foram definidos durante o build da imagem). No entanto, dependendo do comportamento do comando principal, o container pode encerrar logo após sua execução, concluindo a tarefa que lhe foi atribuída.


O ENTRYPOINT é o comando principal executado quando o container é iniciado. Ele deve, preferencialmente, ser um comando que mantém o terminal ativo, pois, caso contrário, o container será encerrado imediatamente após sua execução, tornando-se inútil. O ENTRYPOINT pode ser definido como um array ou como uma string única.


Já o CMD, embora tenha funcionalidades semelhantes ao ENTRYPOINT, é opcional e geralmente usado para fornecer argumentos padrão ou comandos complementares ao ENTRYPOINT. Quando ambos são definidos, o CMD atua como complemento do ENTRYPOINT, sendo passado como parâmetro.


Se você definir apenas o CMD no Dockerfile, ele será usado como o comando principal e será executado quando o container iniciar, desde que nenhum outro comando seja especificado no momento da execução do container. O CMD é sobrescrito se um comando for fornecido no docker run. Para garantir que o comando principal não seja substituído, use ENTRYPOINT em vez de CMD.


É possível criar um Dockerfile sem especificar CMD ou ENTRYPOINT. No entanto, ao criar uma imagem sem esses parâmetros, o container precisará que um comando seja especificado no momento da execução, caso contrário, o Docker retornará um erro. Criar um Dockerfile sem especificar CMD ou ENTRYPOINT é útil em cenários onde o comando principal será definido dinamicamente, como no docker run, Docker Swarm, ou Docker Compose. Isso oferece máxima flexibilidade e é uma prática comum ao criar imagens base ou genéricas.


A escolha entre CMD e ENTRYPOINT pode ser simples ou complexa, dependendo do comportamento desejado para o contêiner. Para tomar a melhor decisão, faça as seguintes perguntas:

  1. O contêiner deve sempre iniciar um binário específico, sem que esse comportamento seja facilmente alterado?

  2. O usuário deve ter a flexibilidade de adicionar argumentos ao executar o contêiner?


Essas questões ajudam a definir a abordagem ideal. Por exemplo, se a intenção é garantir que um comando específico sempre seja executado ao iniciar o contêiner, ENTRYPOINT é a melhor opção. Para modificar o ENTRYPOINT de um contêiner em tempo de execução, é necessário usar explicitamente a opção --entrypoint="...". Já o CMD pode ser facilmente substituído ao iniciar o contêiner sem a necessidade de usar um argumento, apenas sendo necessário informar o comando que o container deve executar.


Se a ideia é permitir que os usuários adicionem argumentos para modificar o comportamento do contêiner, o ideal é definir ENTRYPOINT no momento da build e deixar o CMD para que os usuários passem argumentos adicionais. Outra alternativa é usar apenas CMD e permitir que o usuário escolha o comando executado ao iniciar o contêiner, caso nenhum comando seja fornecido, o que estiver declarado em CMD será executado.


O mais importante é entender como cada instrução influencia a imagem e o contêiner, evitando comportamentos inesperados e garantindo a previsibilidade da execução.


Instruções de build têm efeito apenas durante a criação da imagem, enquanto que instruções de runtime afetam o comportamento do contêiner quando ele é executado a partir da imagem.

Algumas instruções afetam ambos os estágios.



Shell vs. Exec


O Docker permite definir comandos usando duas formas diferentes. Essas formas afetam como RUN, CMD e ENTRYPOINT são executados.

  • Shell Form
    Usa um shell (sh ou bash no Linux e cmd.exe no Windows).

  • Exec Form
    Executa o comando diretamente, sem um shell.



Shell Form

O comando é interpretado por um shell, isso permite encadear comandos com o uso de && e ;, redirecionar saídas (>, |) e substituir variáveis ($VAR). Um exemplo de uso no Dockerfile seria:

RUN apt-get update && apt-get install -y curl
CMD echo "Olá, mundo!"

A desvantagem dessa abordagem é que o processo é executado como subprocesso do shell, o que pode impedir o recebimento correto de sinais como SIGTERM (importante para containers de longa duração).



Exec Form

Executa diretamente o binário sem um shell intermediário, isso evita o problemas com subprocessos e melhora o manuseio de sinais (SIGTERM, SIGINT). Um exemplo de uso no Dockerfile seria:

ENTRYPOINT ["/usr/bin/curl"]
CMD ["--help"]

A desvantagem dessa abordagem é que ela não suporta substituição de variáveis ($VAR), pipes (|) ou encadeamento de comandos (&&).



CMD ou Entrypoint?


Uma dica simples é usar ENTRYPOINT junto com Exec Form, isso evita subprocessos e garante um uso correto de sinais. Para CMD devemos usar Exec Form, mas pode usar Shell Form se precisar de recursos do shell. Mas se usar ENTRYPOINT e CMD juntos, sempre use Exec Form.



docker-entrypoint.sh


O docker-entrypoint.sh é um script usado como entrypoint em imagens Docker. Ele é executado quando um container é iniciado e geralmente é usado para configurar ou inicializar o ambiente do container antes de executar o comando principal da aplicação. Em outras palavras, é o comando ou conjunto de comandos que são executados automaticamente quando um container é iniciado a partir de uma imagem que contém esse script.


Quando um container é iniciado, o Docker procura por um script chamado docker-entrypoint.sh e o executa. Os argumentos passados ao comando docker run são passados como argumentos para o script. Resumindo, o docker-entrypoint.sh é muito útil em cenários quando existe a necessidade de rodar comandos logo que o container é criado e não na geração da imagem.


O uso desse script é útil para configuração do ambiente antes da execução do serviço, criação de diretórios, ajuste de permissões, geração de arquivos de configuração entre outras tarefas. Ao usar docker-entrypoint.sh com ENTRYPOINT, o CMD ainda desempenha um papel importante, já que ele é usado para passar argumentos para ENTRYPOINT, funcionando como um valor padrão que pode ser sobrescrito.


Para que o script docker-entrypoint.sh seja executado, você precisa defini-lo explicitamente como o ENTRYPOINT no Dockerfile. Caso contrário, ele não será chamado automaticamente. No script de entrada (docker-entrypoint.sh), utilizamos exec "$@" para repassar os argumentos do CMD para o ENTRYPOINT:

#!/bin/sh

# Faz o script parar em caso de erro:
set -e

# Configuração antes de executar o comando principal:
echo "Iniciando serviço com o comando: $@"

# Executa o comando real passado pelo CMD:
exec "$@"

Você pode se perguntar o motivo de usar o comando exec ao invés de simplesmente usar o comando diretamente. Se apenas chamarmos o comando diretamente no docker-entrypoint.sh, o processo principal será um subprocesso do shell (sh), e não o PID 1 do contêiner. Quando usamos exec, o processo do shell é substituído pelo processo real do comando, garantindo que ele seja o PID 1 e receba sinais corretamente.


O local padrão para esse script é /usr/local/bin/docker-entrypoint.sh e o conteúdo seria:

#!/bin/sh
set -e

# Run command with node if the first argument contains a "-" or is not a system command. The last
# part inside the "{}" is a workaround for the following bug in ash/dash:
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=874264
if [ "${1#-}" != "${1}" ] || [ -z "$(command -v "${1}")" ] || { [ -f "${1}" ] && ! [ -x "${1}" ]; }; then
set -- node "$@"
fi

exec "$@"

Esse script docker-entrypoint.sh fornecido no exemplo é um padrão frequentemente usado em imagens oficiais do Docker Hub (nesse exemplo eu peguei de uma imagem do Node.js) para garantir flexibilidade e funcionalidade consistente em containers. O docker-entrypoint.sh é configurado como ENTRYPOINT no Dockerfile para criar a imagem, dessa forma, o script seja sempre será executado e vai preparar o ambiente do container antes de rodar qualquer outro comando.


Com isso, o script assegura que o container funcione com comandos personalizados fornecidos no docker run, CMD ou docker-compose e impede que o container retorne um erro caso nenhum comando principal seja fornecido. Quando uma imagem Docker possui um ENTRYPOINT definido como um comando, você precisa sobrescrevê-lo explicitamente para alterar o comportamento padrão do container. Isso ocorre porque o ENTRYPOINT sempre será executado, a menos que seja sobrescrito no momento da execução.

# Sobrescrevendo um entrypoint para ter acesso ao shell:
docker run --entrypoint "" -it minha-imagem sh