Conteúdo
Tutorial de instruções do Dockerfile: guia completo de FROM, RUN, COPY, CMD e ENTRYPOINT
Na seção anterior, você criou seu primeiro Dockerfile com um exemplo simples. Nesta seção, vamos explicar cada instrução e comparar, com pequenos experimentos, alguns pares de instruções que costumam ser confundidos.
Regras básicas
- As instruções não diferenciam maiúsculas de minúsculas, mas é comum escrevê-las em maiúsculas para distingui-las dos parâmetros.
- Elas são executadas em sequência, de cima para baixo. A primeira instrução (exceto
ARGe comentários) precisa serFROM. - Linhas iniciadas com
#são comentários. - Se uma linha ficar muito longa, você pode quebrá-la usando
\.
FROM: imagem base
FROM python:3.14-slim
FROM --platform=linux/amd64 node:24-alpine
FROM scratch # Imagem totalmente vazia, usada para programas compilados estaticamente
FROM golang:1.27 AS builder # Dá um nome à etapa em um build em vários estágios
RUN: executar comandos durante o build
# Formato shell: executa por meio de /bin/sh -c; você pode usar && e pipes
RUN apt-get update && apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
# Formato exec: não passa pelo shell
RUN ["pip", "install", "--no-cache-dir", "flask"]
Cada RUN cria uma camada. Escreva apt-get update e apt-get install no mesmo RUN. Caso contrário, a camada de update em cache pode ficar desatualizada e instalar pacotes antigos. Limpar a lista de pacotes no final (rm -rf /var/lib/apt/lists/*) reduz o tamanho da imagem. Veja a diferença na prática em Cache de build e otimização.
COPY e ADD: copiar arquivos
COPY package.json package-lock.json ./
COPY src/ ./src/
COPY --chown=app:app . .
COPY --from=builder /app/dist ./dist # Copia arquivos de outra etapa do build
ADD é parecido com COPY, mas se comporta de outra forma com arquivos tar compactados. Experimente: prepare um files.tar.gz (com files/a.txt e files/b.txt) e copie-o usando cada instrução:
FROM alpine:3.23
COPY files.tar.gz /copy/
ADD files.tar.gz /add/
CMD ["sh", "-c", "echo '--- COPY:'; ls -R /copy; echo '--- ADD:'; ls -R /add"]
$ docker build -q -t add-demo .
sha256:2466246e51b76cd547fa913fce223fccc227ae19b9e53006b9ce203e1afc8ec4
$ docker run --rm add-demo
--- COPY:
/copy:
files.tar.gz
--- ADD:
/add:
files
/add/files:
a.txt
b.txt
COPYcopioufiles.tar.gzsem alterações.ADDdescompactou automaticamente o arquivo. Assim,/addcontém diretamentefiles/a.txtefiles/b.txt.
A origem de ADD também pode ser uma URL (que será baixada automaticamente). Esses comportamentos automáticos podem causar surpresas. A recomendação oficial é usar COPY em situações comuns e recorrer a ADD apenas quando precisar descompactar arquivos.
WORKDIR: diretório de trabalho
WORKDIR /app
As instruções seguintes, como RUN, COPY e CMD, serão executadas nesse diretório. Se ele não existir, será criado automaticamente. Use WORKDIR em vez de escrever RUN cd /app: cada RUN usa um shell independente, então o cd não continua na linha seguinte.
ENV e ARG: variáveis
| ARG | ENV | |
|---|---|---|
| Quando entra em vigor | Apenas durante o build | Durante o build e a execução do contêiner |
| Como definir | docker build --build-arg NAME=valor | No Dockerfile ou com docker run -e |
| Fica visível dentro do contêiner? | Não | Sim |
Experimento:
FROM alpine:3.23
ARG APP_VERSION=dev
ENV APP_ENV=production
RUN echo "building version $APP_VERSION" > /build-info.txt
CMD ["sh", "-c", "cat /build-info.txt; echo APP_ENV=$APP_ENV; echo APP_VERSION=${APP_VERSION:-<not set>}"]
$ docker build -q --build-arg APP_VERSION=1.2.0 -t args-demo .
sha256:3537c3e70baef148c3481f97bf325963b4809a15b806a2e38fed7e8eb5f50619
$ docker run --rm args-demo
building version 1.2.0
APP_ENV=production
APP_VERSION=<not set>
$ docker history --no-trunc --format "{{.CreatedBy}}" args-demo | head -n 3
CMD ["sh" "-c" "cat /build-info.txt; echo APP_ENV=$APP_ENV; echo APP_VERSION=${APP_VERSION:-<not set>}"]
RUN |1 APP_VERSION=1.2.0 /bin/sh -c echo "building version $APP_VERSION" > /build-info.txt # buildkit
ENV APP_ENV=production
- O valor
APP_VERSION=1.2.0informado durante o build foi usado porRUN(e gravado em/build-info.txt), mas já não fica visível durante a execução do contêiner (<not set>). - O
APP_ENVdefinido porENVfica visível dentro do contêiner. - Observe a segunda linha de
docker history:RUN |1 APP_VERSION=1.2.0 .... O valor deARGfica registrado no histórico da imagem e qualquer pessoa que obtenha a imagem poderá vê-lo.
Portanto, nunca use
ARGouENVpara transmitir senhas ou chaves de API. Para usar dados confidenciais durante o build, useRUN --mount=type=secret. Consulte Variáveis de ambiente e arquivos .env.
Um ARG declarado antes de FROM só pode ser usado em FROM. Para usá-lo depois de FROM, declare novamente ARG nome.
EXPOSE: documentar portas
EXPOSE 3000
EXPOSE 53/udp
EXPOSE serve apenas para documentação: informa qual porta o contêiner usará, mas não a publica. Para mapear a porta de fato, você ainda precisa usar docker run -p. Seu único efeito prático é em conjunto com -P, que mapeia todas as portas de EXPOSE para portas aleatórias do host (a imagem nginx tem EXPOSE 80):
$ docker run --rm -d --name exp -P nginx:1.30
857560427b1218d038314394f03cc0eb8edf950062304efaf2f5f3dc995f6ef6
$ docker port exp
80/tcp -> 0.0.0.0:32768
80/tcp -> [::]:32768
$ docker rm -f exp
exp
USER: execute como usuário não root
Por padrão, o contêiner executa como root. Use um usuário comum para limitar os danos caso o programa seja comprometido:
FROM alpine:3.23
RUN addgroup -S app && adduser -S -G app app
USER app
CMD ["sh", "-c", "whoami; id"]
$ docker build -q -t user-demo .
sha256:bdcaab4c6133a5446cdaaa1ca3353e01a690dc9fd0647458543abcec0d6041f1
$ docker run --rm user-demo
app
uid=100(app) gid=101(app) groups=101(app),101(app)
$ docker run --rm alpine:3.23 whoami
root
Muitas imagens oficiais já incluem um usuário comum. Por exemplo, a imagem node tem o usuário node, então você pode usar USER node diretamente.
VOLUME: declarar um diretório de dados
VOLUME /var/lib/data
Declare que esse diretório deve armazenar dados persistentes. Se você não montar um volume ao executar o contêiner, o Docker criará automaticamente um Volume anônimo (a seção Volume traz um exemplo prático). Na prática, geralmente você especifica isso diretamente com docker run -v ou no Compose; talvez não seja necessário incluir VOLUME no seu Dockerfile.
HEALTHCHECK: verificação de integridade
O Docker executa periodicamente o comando de verificação de integridade. O código de saída 0 indica que está saudável; um valor diferente de 0 indica que não está saudável:
FROM nginx:1.30-alpine
HEALTHCHECK --interval=5s --timeout=3s --retries=3 \
CMD wget -q --spider http://localhost/ || exit 1
Experimente: depois de iniciar, aguarde alguns segundos e então exclua a página inicial de propósito para que o nginx retorne um erro:
$ docker run -d --name health health-demo
52bfa4cce8024630797ba6f33ba99dea44f80399fbadd617b2d04a8ac7f8e3b1
$ docker ps --filter name=health
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
52bfa4cce802 health-demo "/docker-entrypoint.…" 1 second ago Up Less than a second (health: starting) 80/tcp health
$ docker ps --filter name=health
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
52bfa4cce802 health-demo "/docker-entrypoint.…" 9 seconds ago Up 8 seconds (healthy) 80/tcp health
$ docker exec health rm /usr/share/nginx/html/index.html
$ docker ps --filter name=health
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
52bfa4cce802 health-demo "/docker-entrypoint.…" 29 seconds ago Up 28 seconds (unhealthy) 80/tcp health
$ docker inspect --format '{{range .State.Health.Log}}{{.ExitCode}} {{.Output}}{{println}}{{end}}' health
0
1 wget: server returned error: HTTP/1.1 403 Forbidden
1 wget: server returned error: HTTP/1.1 403 Forbidden
1 wget: server returned error: HTTP/1.1 403 Forbidden
1 wget: server returned error: HTTP/1.1 403 Forbidden
$ docker rm -f health
health
A sequência de estados: health: starting (acabou de iniciar) → healthy (verificação aprovada) → após excluir a página inicial, 3 falhas consecutivas (--retries=3) → unhealthy. .State.Health.Log registra o código de saída e a saída de cada verificação.
- A imagem precisa incluir ferramentas como
curlouwgetpara que você possa fazer isso (wgetjá vem no alpine). - O Compose pode aguardar até que um serviço fique
healthypara iniciar os serviços que dependem dele. Consulte a sintaxe de compose.yaml.
CMD e ENTRYPOINT: comando de inicialização
Ambos determinam o que será executado quando o contêiner iniciar. A diferença está em se podem ser substituídos e como são combinados.
CMD: pode ser substituído por completo
FROM alpine:3.23
CMD ["echo", "hello from CMD"]
$ docker build -q -t cmd-demo .
sha256:45b2af259ce828e227961bcc620b973758a1f757d948a7e23e056781afba324d
$ docker run --rm cmd-demo
hello from CMD
$ docker run --rm cmd-demo echo overridden
overridden
O comando que você escreve depois do nome da imagem ao executar docker run substitui por completo CMD.
ENTRYPOINT: programa principal fixo
FROM alpine:3.23
ENTRYPOINT ["echo", "ENTRYPOINT says:"]
CMD ["default argument"]
$ docker build -q -t ep-demo .
sha256:32414489000b71c255d808cfd4cda73764b526525229dece3a8f1fd5487fc6d1
$ docker run --rm ep-demo
ENTRYPOINT says: default argument
$ docker run --rm ep-demo my argument
ENTRYPOINT says: my argument
$ docker run --rm --entrypoint date ep-demo
Mon Oct 12 23:59:17 UTC 2026
- Quando
ENTRYPOINTeCMDsão usados juntos, o que é executado éENTRYPOINT + CMD. - O que você escreve depois de
docker runsubstitui apenasCMDe passa a ser argumento deENTRYPOINT. - Para substituir o próprio
ENTRYPOINT, use--entrypoint.
| CMD | ENTRYPOINT | |
|---|---|---|
| Função | Comando padrão ou argumentos padrão | Executável principal do contêiner |
docker run imagem xxx | xxx substitui CMD | xxx é acrescentado após ENTRYPOINT |
| Como substituir | Escreva o comando diretamente depois do nome da imagem | --entrypoint |
Como escolher:
- Aplicativo comum: escreva apenas
CMD. Assim, quando quiser abrir um shell, o usuário pode executar diretamentedocker run -it myapp sh. - Para usar o contêiner como “um comando” (por exemplo, uma ferramenta de CLI): use
ENTRYPOINTpara especificar o programa e coloque os argumentos padrão emCMD.
Formato exec e formato shell
CMD ["node", "server.js"] # formato exec (array JSON)
CMD node server.js # formato shell
O formato shell é executado como /bin/sh -c "node server.js" e permite a expansão de variáveis de ambiente e sintaxe de shell como &&. Já o formato exec executa o programa diretamente, sem passar pelo shell. Em geral, recomendamos o formato exec, pois seu comportamento é mais explícito.
PID 1 e o problema dos 10 segundos de espera do docker stop
O ciclo de vida do contêiner mencionou que, às vezes, docker stop leva 10 segundos. A seguir, vamos usar o mesmo programa Node.js (server.js da seção anterior, sem tratamento de SIGTERM) para comparar quatro formas de escrever CMD, ver os processos dentro do contêiner com docker top e cronometrar docker stop:
$ docker top shell-npm
PID USER TIME COMMAND
4155 root 0:00 npm start
4182 root 0:00 {MainThread} node server.js
$ time docker stop shell-npm
shell-npm
real 0m 0.66s
user 0m 0.00s
sys 0m 0.00s
$ docker top exec-npm
PID USER TIME COMMAND
4318 root 0:00 npm start
4347 root 0:00 {MainThread} node server.js
$ time docker stop exec-npm
exec-npm
real 0m 0.67s
user 0m 0.00s
sys 0m 0.00s
$ docker top shell-node
PID USER TIME COMMAND
4488 root 0:00 {MainThread} node server.js
$ time docker stop shell-node
shell-node
real 0m 10.14s
user 0m 0.01s
sys 0m 0.01s
$ docker top exec-node
PID USER TIME COMMAND
4660 root 0:00 {MainThread} node server.js
$ time docker stop exec-node
exec-node
real 0m 10.14s
user 0m 0.01s
sys 0m 0.00s
| Forma de escrever CMD | PID 1 dentro do contêiner | docker stop |
|---|---|---|
CMD npm start | npm | 0.66 segundo |
CMD ["npm", "start"] | npm | 0.67 segundo |
CMD node server.js | node | 10.14 segundos |
CMD ["node", "server.js"] | node | 10.14 segundos |
O ponto principal não é o formato shell ou exec, mas quem é o PID 1:
- O Linux tem uma regra especial para o PID 1: se o PID 1 não tratar SIGTERM por conta própria, o sinal será ignorado. Quando node é diretamente o PID 1 e o programa não trata SIGTERM,
docker stopsó consegue aguardar 10 segundos e então encerrar o processo à força com SIGKILL. - Quando iniciado pelo npm, o npm é o PID 1 e encaminha SIGTERM para node. Como node não é o PID 1, ao receber SIGTERM ele termina conforme o comportamento padrão.
- A primeira linha usa o formato shell sem adicionar outra camada
shporque oshdo Alpine substitui a si próprio pornpmquando há apenas um comando.
Soluções (escolha uma):
- Trate SIGTERM no programa e encerre o servidor de forma graciosa (a melhor opção; veja Aplicativo Node.js).
- Adicione
--initao executar (no Compose, useinit: true); assim, um pequeno processo init fica como PID 1 e encaminha os sinais.
Outros comandos
| Comando | Finalidade |
|---|---|
LABEL | Adicionar metadados, por exemplo, LABEL org.opencontainers.image.source="https://github.com/..." |
SHELL | Alterar o shell usado pelo formato shell, por exemplo, usar PowerShell em contêineres Windows |
STOPSIGNAL | Alterar o sinal enviado por docker stop (a imagem nginx define SIGQUIT; veja docker history em Imagens) |
ONBUILD | Executar um comando apenas quando outra imagem usar esta imagem como base (pouco usado) |
Exemplo completo: site em Python
FROM python:3.14-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd --system app
USER app
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]
PYTHONUNBUFFERED=1 faz com que a saída do Python seja gravada imediatamente, para que você possa vê-la em tempo real com docker logs. Veja o processo real de build e execução deste Dockerfile em Implante um aplicativo Python com Docker.
Resumo
RUN,COPYeARGtêm efeito durante o build;CMD,ENTRYPOINT,USEReENVafetam a execução.- Em
docker run imagem xxx,xxxsubstitui CMD ou é acrescentado depois de ENTRYPOINT. - Use o formato exec (array JSON) em
CMDeENTRYPOINT. - Não use
ARGnemENVpara passar senhas.
Exercícios
Exercício 1: A imagem define ENTRYPOINT ["ping"] e CMD ["-c", "3", "localhost"]. O que é executado de fato ao executar docker run imagem -c 1 example.com?
Resposta
ping -c 1 example.com. Os argumentos informados na execução substituem CMD e são acrescentados depois de ENTRYPOINT.
Exercício 2: Por que ENV NODE_ENV production gera um aviso de docker build --check?
Resposta
Essa é a forma antiga; o correto é escrever ENV NODE_ENV=production. O nome do aviso é LegacyKeyValueFormat.
Perguntas frequentes
Quando devo usar COPY ou ADD?
Prefira sempre COPY. ADD descompacta arquivos tar automaticamente e pode baixar URLs. Esse comportamento é menos intuitivo, então use ADD apenas quando precisar desses recursos.
Quando devo usar CMD ou ENTRYPOINT?
A maioria dos aplicativos usa apenas CMD, que é fácil de substituir. Use ENTRYPOINT quando a imagem funcionar como uma ferramenta de comando (sempre executa o mesmo programa, mudando apenas os argumentos).
Como verifico se a sintaxe do Dockerfile está correta?
Execute docker build --check .. O comando lista avisos como JSONArgsRecommended e LegacyKeyValueFormat. Consulte [docker init](/pt/docker/docker-init/).
Atualizado 14 de outubro de 2026