Computação em nuvem
Cloud-native e Twelve-Factor App: o que cada fator significa para um contêiner
Intermediário11 min de leituraAtualizado
Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês
A metodologia twelve-factor continua sendo a descrição útil mais curta de uma aplicação que um orquestrador consegue executar. Este guia traduz cada fator em decisões concretas para um serviço Spring Boot em contêiner, mostra o código de configuração e de ciclo de vida que as implementa e depois trata das quatro áreas — secrets, observabilidade, health e resiliência — em que um checklist de 2011 já não basta.
O que você vai aprender
- Explicar cada um dos doze fatores: o que ele proíbe, além do que exige
- Externalizar corretamente a configuração e os secrets de um serviço em contêiner
- Tornar um processo stateless e descartável, que pode ser morto a qualquer momento sem risco
- Tratar logs como um fluxo de eventos e parar de gravar em arquivos dentro de contêineres
- Identificar onde o modelo twelve-factor é incompleto e o que acrescentar a ele
A metodologia twelve-factor foi publicada em 2011 por engenheiros de uma empresa de platform-as-a-service que tinham visto milhares de aplicações prosperarem ou caírem na infraestrutura deles. É uma lista das propriedades de que uma aplicação precisa para ser iniciada, parada, replicada e movida por algo que não seja um ser humano. Quinze anos depois, as plataformas mudaram completamente e a lista quase não envelheceu, porque a pergunta de fundo continua a mesma: um orquestrador consegue rodar mais cópias disto, matar uma ao acaso e movê-las para outra máquina sem que ninguém perceba?
Este guia pega cada fator e pergunta o que ele significa na prática para um serviço em contêiner — os exemplos assumem uma aplicação Spring Boot rodando em Kubernetes, mas nada aqui é específico de framework. O último terço trata do que o documento original não cobre, porque um serviço que atende aos doze fatores e a nada mais não está pronto para produção em 2026.
Codebase, dependências, configuração
I. Codebase — uma única codebase versionada, muitos deploys. Uma aplicação corresponde a um repositório. O mesmo commit vai para staging e para produção; o que muda entre eles é a configuração, nunca o código. Duas aplicações compartilhando uma codebase são um monólito distribuído em potencial; uma aplicação espalhada por dois repositórios não pode ser liberada de forma atômica. Código compartilhado pertence a uma biblioteca com número de versão próprio.
II. Dependências — declare e isole. Toda dependência é listada explicitamente com versão, e nada se presume existir no host. Na prática, isso significa um arquivo de build que fixa versões, um lock file ou metadados de verificação de dependências commitados junto com ele, e uma imagem de contêiner que traz o runtime em vez de esperar encontrá-lo. A parte do isolamento importa tanto quanto a declaração: um build que só passa porque uma ferramenta por acaso está instalada na máquina do desenvolvedor vai falhar no pipeline, e uma imagem que herda latest de uma imagem base não é reprodutível.
III. Config — armazene a configuração no ambiente. O teste proposto pelo documento original continua sendo o melhor: você conseguiria abrir o código do repositório agora mesmo sem vazar nenhuma credencial? Configuração é tudo o que varia entre deploys — URLs de banco de dados, credenciais, feature flags, o endereço de um serviço upstream, níveis de log. Ela não inclui o que é igual em todo lugar, como tabelas de roteamento internas, que são código.
Para um serviço Spring Boot, isso significa confiar na ordem padrão de resolução de propriedades e deixar a plataforma fornecer os valores:
# application.yaml — defaults only, no environment values, no secrets
spring:
application:
name: orders-api
datasource:
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: none
server:
shutdown: graceful
management:
endpoints:
web:
exposure:
include: health,info,prometheus
Evite a armadilha de codificar ambientes no artefato. Um profile chamado prod dentro da imagem significa que a imagem agora sabe em que ambiente está, e a propriedade “um build, muitos deploys” se perde. Profiles são aceitáveis para variantes de comportamento (kubernetes, local); valores específicos de ambiente ficam do lado de fora.
Serviços de apoio, build/release/run, processos
IV. Serviços de apoio — trate-os como recursos anexados. Um banco de dados, um cache, um message broker, um relay SMTP e uma API de terceiros são todos a mesma coisa: algo acessível numa URL com credenciais. Trocar um PostgreSQL administrado localmente por um gerenciado deve ser uma mudança de configuração e nada mais. O corolário é que seu código não deve tratar de forma especial uma dependência só porque ela é local, nem embutir suposições como um caminho de sistema de arquivos compartilhado com o host do banco.
V. Build, release, run — separe as etapas rigorosamente. O build transforma o código-fonte num artefato imutável (uma imagem de contêiner, identificada por digest). A release combina esse artefato com um conjunto específico de configuração e recebe um identificador próprio. O run executa uma release. Daí saem duas regras: releases são imutáveis — você nunca aplica patch num contêiner em execução, você cria uma nova release — e cada release é endereçável individualmente, o que faz do rollback uma operação de uma linha. Editar um arquivo dentro de um pod em execução quebra isso, e a alteração some no próximo restart de qualquer jeito.
VI. Processos — execute a aplicação como um ou mais processos stateless e share-nothing. Tudo o que um processo mantém em memória ou em disco local deve ser tratado como um cache que pode sumir a qualquer momento. Os suspeitos de sempre são o estado de sessão HTTP, contadores de rate limiting em memória, arquivos de upload gravados num diretório local e jobs agendados que presumem que exatamente uma instância está rodando.
As correções são bem conhecidas: coloque o estado de sessão no Redis ou transforme a sessão num token assinado, use um armazenamento compartilhado para os contadores, grave os uploads em object storage e coordene o trabalho agendado com um lock distribuído ou um agendador que garanta execução única. Sticky sessions são um paliativo, não uma solução — elas impedem uma distribuição de carga uniforme e transformam o despejo de um pod num logout visível para o usuário.
Port binding, concorrência, descartabilidade
VII. Port binding — exponha serviços via port binding. A aplicação é autocontida e escuta na porta que lhe for indicada; ela não é implantada dentro de um servidor de aplicação externo que forneça o runtime HTTP. O servidor embutido do Spring Boot faz exatamente isso, e é por isso que o modelo de JAR executável substituiu o deploy de WAR para cargas na nuvem. Um processo twelve-factor também pode ser um serviço de apoio para outro processo, acessível num host e numa porta.
VIII. Concorrência — escale horizontalmente pelo modelo de processos. Escale rodando mais processos, não aumentando um só. Tipos diferentes de carga viram tipos de processo diferentes, com escalonamento independente: um tipo web dimensionado pela taxa de requisições, um tipo worker dimensionado pela profundidade da fila, e nunca os dois no mesmo deployment compartilhando um pool de threads. O escalonamento vertical tem um teto e não melhora a disponibilidade; o horizontal melhora as duas coisas, desde que o fator VI seja respeitado.
IX. Descartabilidade — maximize a robustez com inicialização rápida e desligamento gracioso. É o fator mais violado na prática, e ele trata inteiramente do comportamento de ciclo de vida.
A inicialização rápida importa porque limita a velocidade com que a plataforma consegue reagir à carga ou reagendar após a falha de um nó. Inicialização lenta do contexto, aquecimento antecipado do pool de conexões e varredura do classpath aumentam esse tempo; class-data sharing, compilação ahead-of-time e o enxugamento da autoconfiguração são as alavancas usuais.
O desligamento gracioso importa ainda mais. Quando um contêiner é parado, a plataforma envia SIGTERM, espera um período de carência e então envia SIGKILL. A aplicação precisa parar de aceitar trabalho novo, terminar o que está em andamento, fechar conexões e sair antes que o período de carência expire. No Kubernetes há uma sutileza de ordem que causa muita confusão: a exclusão do pod e a remoção dos endpoints acontecem ao mesmo tempo, então um pod pode receber requisições por um curto intervalo depois do SIGTERM. Um sleep breve no pre-stop dá tempo para a camada de roteamento se atualizar.
spec:
terminationGracePeriodSeconds: 45
containers:
- name: orders-api
image: registry.example.com/orders-api@sha256:9f2c...
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
readinessProbe:
httpGet: { path: /actuator/health/readiness, port: 8080 }
periodSeconds: 5
livenessProbe:
httpGet: { path: /actuator/health/liveness, port: 8080 }
periodSeconds: 10
failureThreshold: 3
Este fator também exige que os workers resistam a uma morte súbita. Se um job retirado de uma fila se perde quando o processo morre, é preciso configurar o visibility timeout e a semântica de reentrega da fila, e o handler precisa ser idempotente, porque a reentrega vai acontecer.
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY target/orders-api.jar app.jar
USER 1000
EXPOSE 8080
# exec form: the JVM becomes PID 1 and receives SIGTERM directly
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-jar", "app.jar"]
A forma shell do ENTRYPOINT envolve o processo em /bin/sh, que não repassa sinais; a JVM então nunca recebe o SIGTERM e é morta abruptamente ao fim do período de carência. É um erro de uma palavra que desativa silenciosamente o desligamento gracioso, e é extremamente comum. Mais detalhes sobre o empacotamento de contêineres estão em fundamentos de Docker.
Paridade, logs, processos administrativos
X. Paridade dev/prod — mantenha desenvolvimento, staging e produção parecidos. O original aponta três lacunas: tempo (quanto tempo entre escrever o código e rodá-lo em produção), pessoas (se quem escreve também faz o deploy) e ferramentas (se os serviços de apoio são diferentes). Trocar o banco de produção por um banco em memória é a lacuna de ferramentas clássica, e ela esconde tudo o que interessa — diferenças de dialeto, comportamento dos níveis de isolamento, uso de índices. Os contêineres resolveram isso em grande parte: rode o banco real localmente e use a mesma imagem em todo lugar.
XI. Logs — trate logs como fluxos de eventos. A aplicação escreve na saída padrão, sem buffer, e não sabe nada de arquivos, rotação ou destinos. A plataforma coleta o fluxo e o encaminha para um sistema de agregação de logs. Gravar num arquivo dentro de um contêiner significa que os logs somem junto com o contêiner e ocupam a camada gravável até enchê-la.
Emita JSON estruturado com identificadores de correlação, para que o agregador possa indexar campos em vez de fazer parsing de texto:
{"timestamp":"2026-09-07T09:14:22.118Z","level":"WARN","service":"orders-api",
"traceId":"4bf92f3577b34da6a3ce929d0e0e4736","spanId":"00f067aa0ba902b7",
"message":"payment authorisation retried","orderId":"A-91245","attempt":2}
XII. Processos administrativos — execute tarefas pontuais como processos pontuais. Migrações de banco, correções de dados e geração de relatórios rodam contra a mesma release, a partir da mesma codebase, com a mesma configuração — como um job na plataforma, e não como uma sessão SSH num host. Uma ferramenta de migração de schema invocada na inicialização ou como init container atende a isso; um documento com comandos SQL que alguém executa à mão, não, porque não é versionado, revisado nem repetível.
O que os doze fatores não cobrem
Atender aos doze faz com que uma aplicação seja orquestrada de forma confiável. Isso não a torna operável. Quatro lacunas importam o bastante para serem tratadas como fatores adicionais.
Secrets não são apenas config. O fator III coloca no mesmo saco uma feature flag e uma senha de banco de dados, e isso não envelheceu bem. Secrets precisam de rotação sem redeploy, trilha de auditoria de acesso, criptografia em repouso com uma chave que você controla e um raio de impacto limitado a uma única carga de trabalho. O padrão que atende a tudo isso: a carga de trabalho se autentica na plataforma com uma identidade que ela não precisou armazenar — uma IAM role ou uma managed identity — e busca os secrets num cofre gerenciado na inicialização, atualizando-os periodicamente. Nada sensível fica na imagem, no manifesto, no repositório ou no bloco de variáveis de ambiente.
Observabilidade não é só log. O fator XI cobre um dos três sinais. Um serviço operável também emite métricas (taxa, erros, duração, saturação, além de contadores de negócio) e traces distribuídos com contexto propagado entre serviços. OpenTelemetry é o padrão neutro em relação a fornecedores para os três e é a escolha padrão na prática; a configuração no lado da aplicação é tratada em Actuator e observabilidade.
Health não é um único booleano. A plataforma precisa de pelo menos três respostas distintas: o processo está vivo e vale a pena mantê-lo (liveness), ele consegue atender tráfego agora (readiness), e ele terminou sua inicialização lenta (startup)? Confundir liveness com readiness é a origem de um modo de falha bem conhecido, em que uma dependência temporariamente indisponível faz todas as réplicas serem mortas e reiniciadas em loop, transformando uma degradação parcial numa indisponibilidade total. A liveness deve testar apenas o próprio processo.
Resiliência e contratos de API. Timeouts, retries com jitter, circuit breakers, backpressure e rate limits são propriedades de arquitetura que a metodologia nunca menciona, assim como a evolução de APIs com retrocompatibilidade e as migrações de schema seguras de rodar enquanto a release antiga e a nova estão no ar ao mesmo tempo. O padrão de migração expand-then-contract — adicionar uma coluna nullable, fazer deploy de código que escreve nas duas, fazer o backfill, fazer deploy de código que lê a nova coluna, remover a antiga — é o que torna realmente seguro o rollback instantâneo do fator V.
A definição de cloud-native da CNCF captura a ambição mais ampla: sistemas fracamente acoplados, resilientes, gerenciáveis e observáveis, combinados com uma automação que permite aos engenheiros fazer mudanças de alto impacto com frequência e de forma previsível. Os doze fatores são a maneira como um processo individual conquista seu lugar num sistema assim; eles são o requisito de entrada, não o design completo, e é por isso que convivem com as preocupações mais amplas da arquitetura de microsserviços.
Uma auditoria rápida para um serviço existente
Faça estas perguntas sobre um serviço que você já opera. Cada “não” é um ticket concreto.
- O repositório poderia se tornar público hoje sem vazar nenhuma credencial?
- O artefato implantado é identificado por um digest imutável, e o mesmo digest está em staging e em produção?
- Se duas réplicas estão rodando e uma é morta no meio de uma requisição, algum usuário perde dados ou a sessão?
- O processo termina de forma limpa dentro do período de carência após o
SIGTERM, concluindo as requisições em andamento? - O contêiner grava em disco local algo que não seja um cache de verdade?
- Os logs vão para a saída padrão, estruturados e com um identificador de trace?
- Liveness e readiness verificam coisas realmente diferentes?
- Dá para rotacionar um secret sem reconstruir a imagem?
- As migrações rodam como um job versionado contra a mesma release, e são compatíveis com a release anterior?
- A inicialização é rápida o bastante para que o autoscaling reaja antes de o pico de tráfego passar?
Principais conclusões
- Os doze fatores descrevem o contrato entre uma aplicação e um orquestrador: uma única codebase, dependências declaradas, configuração externalizada, serviços de apoio anexados e separação rigorosa entre build, release e run.
- Ausência de estado e descartabilidade carregam a maior parte do peso prático — nada de estado de sessão local, nada de arquivos locais, inicialização rápida e um desligamento gracioso que realmente recebe o
SIGTERM. - A configuração fica fora do artefato para que uma única imagem imutável rode sem alterações em todos os ambientes; profiles de build com nome de ambiente quebram essa propriedade sem alarde.
- Logs são um fluxo estruturado e sem buffer para a saída padrão, encaminhado pela plataforma, nunca arquivos dentro de um contêiner.
- A metodologia é uma base, não uma definição completa: acrescente secrets gerenciados com identidade de plataforma, observabilidade completa, sinais de health distintos e estratégias explícitas de resiliência e migração.
Fontes e leituras complementares
Perguntas frequentes
- A metodologia twelve-factor está ultrapassada?
- Os fatores em si envelheceram bem porque descrevem o contrato entre uma aplicação e um orquestrador, e esse contrato não mudou. O que mudou foi o escopo: a metodologia quase não fala de gestão de secrets, tracing distribuído, health probes, retries, service discovery ou autoscaling, que hoje são o mínimo esperado. Trate-a como uma base necessária, não como uma definição completa de cloud-native.
- Como os secrets se encaixam no fator de configuração se variáveis de ambiente são arriscadas?
- O que o fator exige de fato é que a configuração fique fora do código e varie por deploy, não que ela precise ser uma variável de ambiente. Variáveis de ambiente vazam em crash dumps, listagens de processos, processos filhos e linhas de log, e não podem ser rotacionadas sem reinício. Busque os secrets na inicialização em um cofre gerenciado usando a própria identidade de plataforma da carga de trabalho, ou deixe a plataforma projetá-los em um arquivo montado, e mantenha-os em cache com um intervalo de atualização.
- Um banco de dados ou um message broker pode ser twelve-factor?
- Não, e nem precisa. A metodologia descreve processos de aplicação sem estado; sistemas com estado violam deliberadamente vários fatores, em especial a descartabilidade e a concorrência share-nothing. Numa arquitetura twelve-factor, esses sistemas são serviços de apoio anexados por URL. Rodá-los você mesmo num orquestrador é possível com operators e volume claims, mas aí você está operando um banco de dados, e não escrevendo uma aplicação twelve-factor.
- O que significa paridade dev/prod quando a produção tem quarenta serviços?
- Significa fechar as lacunas que importam: mesmas tecnologias de serviços de apoio e mesmas versões major, mesma imagem de contêiner, mesmo mecanismo de configuração, e horas em vez de semanas entre escrever o código e rodá-lo em produção. Não significa que cada desenvolvedor rode todo o parque localmente, o que deixa de ser viável depois de alguns poucos serviços. A resposta usual: contêineres para as dependências imediatas mais um ambiente de integração compartilhado, barato e efêmero.