Olá! Na lição anterior, você construiu com sucesso sua primeira imagem Docker para uma API .NET. No entanto, deixamos uma questão em aberto: como o contêiner sabe qual comando executar ao ser iniciado? Nossa imagem contém a aplicação compilada, mas ainda não tem uma instrução de "ponto de partida".
Nesta lição, vamos resolver exatamente isso. O nosso objetivo é que você consiga diferenciar entre CMD e ENTRYPOINT para definir o comando de inicialização de um contêiner. Essas duas instruções do Dockerfile são fundamentais para criar imagens autoexecutáveis e configuráveis. Fiel ao seu interesse em entender "como as coisas funcionam", não vamos apenas ver a sintaxe, mas também mergulhar no comportamento interno, explorando como a escolha entre elas afeta o ciclo de vida e a robustez do contêiner.
Vamos continuar com o projeto HelloApi da lição anterior.
A Anatomia de um Comando de Inicialização
Quando um contêiner é iniciado, o Docker precisa saber qual processo deve ser o principal. Este processo é o "coração" do contêiner; se ele terminar, o contêiner para. As instruções CMD e ENTRYPOINT são as formas de especificar esse processo.
Antes de nos aprofundarmos, é importante conhecer as duas formas de sintaxe que ambas as instruções suportam:
- Forma
exec(preferencial):INSTRUCTION ["executável", "param1", "param2"]- Esta é a forma recomendada. Ela executa o comando e seus parâmetros diretamente, sem um shell intermediário.
- Forma
shell:INSTRUCTION comando param1 param2- Esta forma executa o comando dentro de um shell (
/bin/sh -c). É mais simples para comandos básicos, mas tem implicações importantes para o gerenciamento de processos e sinais, como veremos mais adiante.
- Esta forma executa o comando dentro de um shell (
Usando CMD para Definir um Comando Padrão
A instrução CMD tem um propósito principal: fornecer um comando padrão para um contêiner em execução. A palavra-chave aqui é "padrão", pois ele é facilmente substituível.
Vamos adicionar um CMD ao nosso Dockerfile da lição anterior para que ele execute nossa API. O comando que queremos executar é dotnet /app/publish/HelloApi.dll.
Modifique seu Dockerfile para incluir a instrução CMD no final:
# Estágio 1: Build
FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /app
# Otimização de cache de camadas (da lição anterior)
COPY *.csproj .
RUN dotnet restore
COPY . .
# Publica a aplicação
RUN dotnet publish -c Release -o /app/publish
# Configura a porta
ENV ASPNETCORE_URLS=http://+:8080
# --- NOVA INSTRUÇÃO ---
# Define o comando padrão para iniciar a aplicação
CMD ["dotnet", "/app/publish/HelloApi.dll"]
Agora, vamos construir e testar:
- No seu terminal, no diretório do projeto, reconstrua a imagem com uma nova tag:
docker build -t hello-api:2.0 . - Execute um contêiner a partir da nova imagem:
docker run --rm -d -p 8080:8080 --name my-api hello-api:2.0- Note que não precisamos mais especificar um comando (
nginx -g 'daemon off;') como fazíamos nas primeiras lições. OCMDcuidou disso. - Acesse
http://localhost:8080em seu navegador. Você deverá ver a mensagem "API is running!".
- Note que não precisamos mais especificar um comando (
A Flexibilidade do CMD: Substituição
A principal característica do CMD é que qualquer comando que você passar para docker run após o nome da imagem irá substituir completamente o CMD do Dockerfile.
- Primeiro, pare o contêiner anterior:
docker stop my-api. - Agora, execute este comando:
O contêiner iniciará e lhe dará um prompt de shell interativo (docker run --rm -it hello-api:2.0 /bin/bash-it) em vez de iniciar a API .NET. O comando/bin/bashsubstituiu["dotnet", "/app/publish/HelloApi.dll"]. Isso é extremamente útil para depuração e inspeção.
Para uma demonstração visual dessa substituição, assista ao trecho a seguir.
Docker Interview Question | ENTRYPOINT vs CMD | Docker hands-on Interview Prep | ENTRYPOINT CMD diff
Este vídeo do canal DGR Uploads demonstra claramente como o comando docker run pode substituir a instrução CMD definida em um Dockerfile.
Assista da marca de 4:50 até 6:38. Observe como o primeiro docker run executa o CMD padrão e o segundo docker run com um comando adicional o substitui.
Usando ENTRYPOINT para Definir o Executável Principal
A instrução ENTRYPOINT tem um propósito diferente: configurar um contêiner para ser executado como um executável específico.
Ao contrário do CMD, o ENTRYPOINT não é facilmente substituído. Em vez disso, os argumentos passados para docker run são anexados ao ENTRYPOINT.
Vamos modificar nosso Dockerfile para usar ENTRYPOINT:
# ... (instruções anteriores) ...
# Define a porta
ENV ASPNETCORE_URLS=http://+:8080
# --- MUDANÇA DE CMD PARA ENTRYPOINT ---
# Define o executável principal do contêiner
ENTRYPOINT ["dotnet", "/app/publish/HelloApi.dll"]
Construa e teste novamente:
- Reconstrua a imagem:
docker build -t hello-api:3.0 . - Execute o contêiner da mesma forma:
docker run --rm -d -p 8080:8080 --name my-api hello-api:3.0
O resultado parece o mesmo: a API inicia. A mágica acontece quando tentamos passar argumentos.
O Comportamento do ENTRYPOINT: Anexação
Vamos tentar "substituir" o comando como fizemos antes. Primeiro, pare o contêiner my-api.
docker run --rm -it hello-api:3.0 /bin/bash
Você receberá um erro! O Docker tentou executar dotnet /app/publish/HelloApi.dll /bin/bash, passando /bin/bash como um argumento para a sua API, o que não faz sentido para a aplicação.
Isso torna o ENTRYPOINT ideal para imagens que são projetadas para fazer uma coisa específica, como executar sua API ou uma ferramenta de linha de comando. Ele transforma seu contêiner em algo que se comporta como um executável.
Docker ENTRYPOINT vs CMD With Examples - Docker Development Tips & Tricks
Este vídeo de Manuel Castellin oferece um excelente exemplo prático de ENTRYPOINT, mostrando como ele pode ser usado para 'empacotar' uma ferramenta de linha de comando como o dig em um contêiner.
Assista de 3:08 a 4:59. Preste atenção em como o ENTRYPOINT torna o comando dig o executável principal, e qualquer argumento passado para docker run é simplesmente encaminhado para o dig.
O Melhor dos Dois Mundos: ENTRYPOINT + CMD
A combinação de ENTRYPOINT e CMD é o padrão mais poderoso e flexível. As regras são:
ENTRYPOINTespecifica o executável principal.CMDespecifica os argumentos padrão para oENTRYPOINT.
Isso cria um contêiner que tem um propósito fixo (o ENTRYPOINT), mas permite que o usuário substitua facilmente os argumentos padrão (o CMD).
Vamos ver um exemplo clássico. Imagine uma imagem para executar pings:
ENTRYPOINT ["ping", "-c", "3"]
CMD ["localhost"]
- Se você executar
docker run <imagem>, o comando executado seráping -c 3 localhost. - Se você executar
docker run <imagem> google.com,google.comsubstitui oCMDe o comando executado seráping -c 3 google.com.
Teste seu entendimento!
Você tem um Dockerfile para uma aplicação .NET que aceita um argumento de linha de comando para especificar o ambiente (--environment Production). Você quer que o padrão seja Development. Como você usaria ENTRYPOINT e CMD?
ENTRYPOINT ["dotnet", "MyApp.dll"]
# Qual seria a instrução CMD?
Show answer
A instrução CMD seria:CMD ["--environment", "Development"]
Isso permite que um usuário execute o contêiner e, se ele quiser usar o ambiente de produção, ele pode simplesmente executar docker run <imagem> --environment Production, substituindo os argumentos padrão.
A Fundo: exec vs shell e a Importância do PID 1
Agora, vamos à parte que você, com seu background em engenharia e interesse nos mecanismos internos, achará mais interessante. Por que a forma exec (["comando", "param"]) é tão preferível à forma shell (comando param)?
A resposta está no gerenciamento de processos e sinais dentro do contêiner.
How better management of processes in Docker can greatly improve a container’s lifecycle
Para entender a fundo o problema, leia este artigo do blog Theodo. Ele introduz conceitos fundamentais do Unix (processos, PID 1, sinais) e explica como uma má configuração do contêiner afeta seu ciclo de vida.
Leia as seções 'Context: The process tree and signal management', 'End of the lifecycle for containers' e 'How a bad lifecycle impacts teams and users'. Concentre-se em entender o que são PID 1, SIGTERM e SIGKILL, e as consequências (como atrasos de 10s ou 30s) quando os sinais não são propagados corretamente.
Como você leu, o processo com PID 1 em um contêiner é especial. É ele quem recebe os sinais do sistema operacional do host, como o SIGTERM que o Docker envia quando você executa docker stop. Um processo que recebe SIGTERM deve iniciar um desligamento gracioso (graceful shutdown).
-
Forma
shell:ENTRYPOINT /bin/sh -c "dotnet ..."- O shell (
/bin/sh) se torna o PID 1. - O shell então inicia sua aplicação como um processo filho.
- Problema: A maioria dos shells não encaminha sinais para seus filhos. Quando
docker stopenviaSIGTERM, o shell o recebe, mas sua aplicação .NET não. Ela continua rodando. - O Docker espera (por padrão, 10 segundos) e, como o contêiner não parou, ele envia um
SIGKILL, que mata o processo à força. Isso impede um desligamento limpo, podendo corromper dados ou deixar conexões abertas.
- O shell (
-
Forma
exec:ENTRYPOINT ["dotnet", ...]- Sua aplicação (
dotnet) se torna o PID 1. - Quando
docker stopenviaSIGTERM, sua aplicação o recebe diretamente. - O host do ASP.NET Core é projetado para lidar com
SIGTERMe iniciar um desligamento gracioso. O contêiner para de forma rápida e limpa.
- Sua aplicação (
O blog oficial do Docker tem um excelente artigo que aprofunda essa distinção.
Choosing Between RUN, CMD, and ENTRYPOINT
Agora, vamos ler a fonte oficial. Este artigo do blog do Docker resume as diferenças e, mais importante, explica claramente a distinção entre as formas 'shell' e 'exec' e suas implicações.
Leia as seções 'What is PID 1 and why does it matter?', 'Shell and exec forms', e a tabela 'Key differences between shell and exec'. Isso solidificará sua compreensão sobre o porquê da forma exec ser a melhor prática para aplicações em produção.
O artigo também inclui um fluxograma útil para ajudar a decidir qual forma usar.

Conclusão
Nesta lição, você aprendeu a diferença fundamental entre CMD e ENTRYPOINT e, mais importante, as implicações de como você os escreve.
Principais pontos a reter:
CMDdefine um comando padrão e facilmente substituível. Ideal para dar flexibilidade ao usuário da imagem.ENTRYPOINTdefine o executável principal do contêiner, transformando-o em uma ferramenta específica. Argumentos dedocker runsão anexados a ele.- A combinação
ENTRYPOINT ["executável"]eCMD ["argumento-padrão"]é o padrão mais robusto e flexível. - A forma
exec(["...", "..."]) é crucial para aplicações de produção, pois permite que sua aplicação se torne PID 1 e lide corretamente com sinais do sistema (comoSIGTERM), garantindo desligamentos graciosos.
Nosso Dockerfile para a API .NET está agora mais robusto, usando ENTRYPOINT ["dotnet", "/app/publish/HelloApi.dll"]. No entanto, se você olhar o tamanho da nossa imagem (docker images hello-api), verá que ela é bem grande. Isso ocorre porque ela inclui todo o SDK do .NET, que é necessário para compilar, mas não para executar a aplicação.
Na próxima lição, vamos resolver esse problema. Você aprenderá a implementar builds de múltiplos estágios (multi-stage builds), uma técnica poderosa para criar imagens de produção pequenas e seguras, separando o ambiente de compilação do ambiente de execução.