Tag latest no Docker: por que não é versão
Tag latest no Docker testada com registry local: servidor com cache antigo sobe outra versão, tag de versão sobrescrita, digest fixo e labels OCI no container.
Na madrugada de 31/10/2026, consultando o Docker Hub, a tag postgres:latest do Docker Hub apontava para a mesma imagem que postgres:18.6 (atualizada em 28/10). A tag node:latest apontava para a mesma imagem que node:26.13.0, que também atende por lts, lithium e current. Daqui a alguns meses, os mesmos nomes vão apontar para outras imagens. Ninguém vai mudar uma linha do seu compose.yaml, mas o que sobe no servidor pode ser outra coisa.
A tag latest é só um rótulo, e quem publica pode movê-lo para qualquer imagem. O Docker usa esse nome quando nenhuma tag é informada (docker pull nginx é docker pull nginx:latest), e é só isso que ele tem de especial. Ele não garante que a imagem é a mais nova, nem que é estável, e não diz em que versão você está. A referência do docker image tag trata a tag como um nome opcional que, se omitido, vira latest.
O teste: dois servidores, a mesma linha, imagens diferentes
Para ver o efeito sem depender de ninguém, montamos um registry local (registry:3) e uma imagem mínima de uma API que só imprime a própria versão. Os testes rodaram em 31/10/2026 com Docker Engine 29.1.4, CLI 29.9.2 e Docker Compose 5.6.1. O compose.yaml do servidor tinha uma linha:
services:
api:
image: localhost:5055/loja/api:latest
Na primeira publicação, a versão 1.0.0 saiu com duas tags, 1.0.0 e latest. O servidor subiu e o log mostrou api 1.0.0 no ar. Depois publicamos a 1.1.0, também com latest, e o registry passou a mostrar o mesmo digest nas duas tags:
1.0.0 sha256:cba7b504d9c3... 1.1.0 sha256:89dd07f44a02... latest sha256:89dd07f44a02...
No servidor que já tinha a latest antiga em cache, docker compose up -d --force-recreate recriou o container e subiu a 1.0.0 de novo, sem consultar o registry. Um docker run com a mesma imagem fez igual. Num servidor novo, que nunca baixou nada, essa mesma linha sobe a 1.1.0. Com isso, dois servidores com o mesmo arquivo ficam rodando versões diferentes, e o nome da imagem nos dois é loja/api:latest.

A especificação do Compose diz que, na política padrão missing, a tag latest é sempre baixada. Não foi isso que vimos no Compose 5.6.1: o up não fez pull nenhum. Por isso é melhor não depender desse detalhe e deixar o pull explícito. Com docker compose pull antes do up, com pull_policy: always no serviço ou com docker run --pull always, o servidor velho passou a subir a 1.1.0.
Mesmo assim, o pull explícito só faz todos rodarem a mesma coisa. Ele não diz qual é essa coisa, e um rollback continua difícil: você sabe que precisa voltar, mas a tag que havia em produção ontem já aponta para outra imagem.
Tag de versão: um nome por build
A saída é publicar cada build com uma tag que identifica aquele build e nunca é reaproveitada. Uma combinação prática é a versão mais o commit:
VERSAO=1.1.0 COMMIT=$(git rev-parse --short HEAD) docker build \ --build-arg APP_VERSION=$VERSAO \ --build-arg GIT_COMMIT=$COMMIT \ -t ghcr.io/sua-empresa/loja-api:$VERSAO \ -t ghcr.io/sua-empresa/loja-api:$VERSAO-$COMMIT . docker push ghcr.io/sua-empresa/loja-api:$VERSAO docker push ghcr.io/sua-empresa/loja-api:$VERSAO-$COMMIT
No servidor, o arquivo passa a dizer a versão, e quem faz deploy precisa mudar essa linha (ou a variável que a preenche). Isso deixa um rastro no Git de quando cada versão entrou:
services:
api:
image: ghcr.io/sua-empresa/loja-api:${API_VERSAO:?defina API_VERSAO}
O :? faz o Compose parar com erro se a variável estiver vazia, em vez de cair silenciosamente em outra tag. Se você ainda quiser oferecer latest para quem testa localmente, tudo bem. O que não pode é a produção depender dela.
Nem a tag de versão é imutável
Uma tag com número de versão é uma convenção, e o registry aceita sobrescrevê-la. No nosso teste, um novo push com a tag 1.0.0 funcionou sem aviso, e a tag passou de sha256:cba7b504... para sha256:dc079da3.... Quem roda 1.0.0 e fizer pull amanhã recebe outra imagem.
Para ter garantia de verdade, use o digest. A documentação do docker pull chama o digest de identificador imutável: ele é o hash do manifest e muda se qualquer byte da imagem mudar. Você pode escrever a tag e o digest juntos. A tag fica lá para pessoas lerem, e o digest é o que vale:
services:
api:
image: localhost:5055/loja/api:1.0.0@sha256:cba7b504d9c35aa14016fded9ccb4d4349b24c7fc234d153846460571c94da2f
Testamos isso depois de a tag 1.0.0 ter sido sobrescrita, apagando antes as imagens locais e usando --pull always. O Compose baixou a imagem do digest e o log mostrou api 1.0.0 no ar, a versão original. Com a imagem fixada pelo digest, sobrescrever a tag não muda o que roda no servidor.
Para descobrir o digest de uma tag sem baixar a imagem, faça uma consulta HEAD no manifest. Num registry compatível com a API de distribuição, o header Docker-Content-Digest traz o valor:
curl -sI \ -H 'Accept: application/vnd.oci.image.index.v1+json, application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.v2+json' \ http://localhost:5055/v2/loja/api/manifests/1.0.0 | grep -i docker-content-digest
Se a imagem já está no servidor, docker image inspect --format '{{index .RepoDigests 0}}' loja/api:1.0.0 mostra o digest de onde ela veio.
A imagem base tem o mesmo problema
Um FROM node:latest ou FROM postgres tem o mesmo problema dentro do build. Cada build pode pegar uma base diferente, e às vezes a mudança quebra coisas. O caso do Postgres é bom de lembrar. A página oficial da imagem avisa que, a partir da versão 18, o PGDATA passou a ser /var/lib/postgresql/18/docker e o volume declarado mudou para /var/lib/postgresql (mudança feita no repositório da imagem). Um postgres:latest que saiu do 17 para o 18 com o volume montado no caminho antigo não encontra os dados do 17 onde espera. Além disso, o 18 não abre arquivos de dados de outra versão major sem um pg_upgrade.
Para a base, use pelo menos a versão major (node:26, postgres:18). Assim você recebe correções de segurança sem trocar de major sem perceber. Para builds reprodutíveis, fixe também o digest, como recomenda o guia de boas práticas de build do Docker, e atualize de propósito, num commit que alguém revisa.
O que está rodando agora
Sem latest, a pergunta "qual versão está em produção?" passa a ter resposta, desde que a imagem carregue essa informação. As anotações padrão da OCI têm chaves para isso, como org.opencontainers.image.version e org.opencontainers.image.revision. No Dockerfile do teste:
FROM alpine:3.22
ARG APP_VERSION=dev
ARG GIT_COMMIT=desconhecido
LABEL org.opencontainers.image.version=$APP_VERSION \
org.opencontainers.image.revision=$GIT_COMMIT
ENV APP_VERSION=$APP_VERSION
Com isso, a resposta sai do container em execução, e não do que alguém lembra ter feito:
docker inspect --format '{{.Config.Image}} {{index .Config.Labels "org.opencontainers.image.version"}} {{index .Config.Labels "org.opencontainers.image.revision"}}' loja-api-1
No servidor que tinha subido pela latest, o comando mostrou localhost:5055/loja/api:latest 1.1.0 7c21d04. O nome da imagem não dizia nada, mas o label disse a versão e o commit. Expor APP_VERSION num endpoint de saúde também ajuda o monitoramento a mostrar a versão em cada máquina.
Num painel de containers como o Dockyard, a coluna da imagem só serve se a tag disser alguma coisa. Com api:1.1.0 na tela, dá para comparar servidores de relance. Com api:latest nos três, não dá. A comparação de painéis Docker mostra como cada um exibe imagem e tag.
Rollback de verdade
Com tags que não se repetem, voltar uma versão é trocar API_VERSAO=1.1.0 por API_VERSAO=1.0.0 e rodar docker compose up -d. Isso só funciona se a imagem antiga ainda estiver no registry: cuidado com a política de limpeza que apaga tags antigas sem olhar o que ainda está em uso. O passo a passo para ensaiar a volta antes de precisar dela está em rollback ensaiado. Se o servidor ainda está nos primeiros containers, comece por Docker no servidor: o primeiro passo.
Na prática, ficam três regras. A produção usa uma tag de versão que nunca é reaproveitada, com o digest quando a garantia importa. A imagem base fixa ao menos a versão major. E cada imagem carrega a versão e o commit nos labels. A latest pode continuar existindo para testes locais, desde que nada em produção dependa dela.
Referência: https://docs.docker.com/develop/publish/publish/