Projeto: Full Cycle 4.0 — Docker e Containers Fase do projeto: Do Dev à Produção: Containerizando uma API Node.js
Neste desafio você vai pegar uma aplicação Node.js + TypeScript funcional, porém sem nenhuma infraestrutura de containers, e entregar a containerização completa dela em dois ambientes: desenvolvimento e produção.
Cenário: você entrou em um time que mantém uma API de feature flags em produção, mas todo o fluxo de trabalho roda direto na máquina de cada desenvolvedor. Sua missão é construir do zero toda a camada de containers: um ambiente de desenvolvimento produtivo, com reload automático e ferramentas de apoio, e uma imagem de produção enxuta, segura e publicada no Docker Hub com metadados de supply chain, pronta para rodar via Docker Compose de produção.
Os dois ambientes têm objetivos diferentes e isso deve se refletir nas suas decisões. O ambiente de desenvolvimento prioriza produtividade: feedback rápido, dependências completas, dados descartáveis. O ambiente de produção prioriza o mínimo: imagem pequena, superfície de ataque reduzida, rastreabilidade do que foi construído.
A entrega é puramente de containerização: o código da aplicação não deve ser alterado. Ele serve de contexto e referência.
Entregar, em um repositório público no GitHub (fork do repositório base), o seguinte pacote:
Dockerfileúnico multi-stage com os estágiosdev,buildeproduction.dockerignorecompose.yamldo ambiente de desenvolvimento, com watch, healthchecks e profilescompose.prod.yamldo ambiente de produção, consumindo a imagem publicada no Docker Hub- Imagem de produção publicada em repositório público no Docker Hub: multi-arch (amd64 e arm64), com SBOM e provenance, nas tags semver e
latest - Relatório de vulnerabilidades do Docker Scout em
reports/ README.mdcom instruções, evidências e comandos de validação
O repositório base contém uma API REST de feature flags em Node.js + TypeScript com persistência em PostgreSQL. Ela expõe um CRUD de flags em /flags, identificadas por uma chave única. Características relevantes para o desafio:
- Porta configurável via variável de ambiente
PORT(padrão 3000) - Conexão com o banco configurável via variáveis de ambiente (host, porta, usuário, senha e nome do database)
- Endpoint
GET /healthque responde 200 quando a aplicação e a conexão com o banco estão saudáveis - Scripts npm:
dev(execução com reload automático),build(compila o TypeScript paradist/),start(executa o código compilado) edb:migrate(aplica as migrações de banco; idempotente) - Tratamento de SIGTERM/SIGINT com encerramento gracioso
A aplicação não possui Dockerfile, compose, dockerignore ou qualquer arquivo de containerização. Esse vácuo é proposital: é exatamente o que você vai construir.
As migrações não rodam sozinhas. A subida de cada ambiente, tanto de desenvolvimento quanto de produção, deve executá-las automaticamente, e a estratégia (script de inicialização, command no compose ou outro mecanismo) é decisão sua. O runner de migrações é compilado junto com a aplicação: após o build, ele está disponível em dist/db/migrate.js.
- Docker Engine ou Docker Desktop em versão recente, com Compose v2 e Buildx
- Buildx com builder capaz de build multi-plataforma (driver
docker-containerou equivalente) - Em Docker Engine no Linux, emulação QEMU/binfmt instalada para o build arm64 (ex:
docker run --privileged --rm tonistiigi/binfmt --install arm64); o Docker Desktop já traz a emulação embutida - Docker Scout (CLI ou via Docker Desktop)
- Conta no Docker Hub com repositório público
É proibido alterar o código da aplicação (src/, package.json, package-lock.json, tsconfig.json e migrações). Arquivos novos de containerização, como scripts de inicialização, são permitidos.
Um único arquivo Dockerfile na raiz do projeto, com no mínimo os estágios nomeados dev, build e production.
Regras que valem para o arquivo inteiro:
- Nenhuma imagem base com tag
latestou sem tag: versões sempre fixadas - Instalações de dependências usando cache de build (
RUN --mount=type=cacheapontando para o diretório de cache do npm) - Ordem das instruções pensada para aproveitamento de cache entre builds (metadados de dependências copiados antes do restante do código)
Estágio dev:
- Instala todas as dependências, incluindo devDependencies
- Usuário não-root e
WORKDIRdefinidos CMDexecuta a aplicação em modo desenvolvimento (scriptdev)
Estágio build:
- Compila o TypeScript (
npm run build), gerandodist/
Estágio production:
- Parte de uma imagem base enxuta (a escolha é sua e deve ser justificada no README)
- Contém apenas o código compilado e as dependências de produção
- Usuário não-root
HEALTHCHECKdeclarado na própria imagem, validando oGET /healthENTRYPOINT/CMDem exec form, garantindo que o processo Node receba sinais do sistema corretamente- Labels OCI:
org.opencontainers.image.title,org.opencontainers.image.description,org.opencontainers.image.versioneorg.opencontainers.image.source
- Serviços
app(build do estágiodev) edb(PostgreSQL com versão fixada), conectados por uma network nomeada declarada no arquivo dbcom healthcheck eappdependendo dele comcondition: service_healthy- Variáveis de ambiente carregadas de um arquivo
.env(viaenv_fileou interpolação). O.env.exampledeve estar versionado com valores funcionais para a subida via compose, e o.envdeve estar no.gitignore - Dados do PostgreSQL em volume nomeado
develop.watchconfigurado com, no mínimo: açãosyncpara mudanças emsrc/e açãorebuildpara mudanças nopackage.json- Serviço
adminerdisponível emhttp://localhost:8081, ativado somente pelo profiletools - Migrações aplicadas automaticamente na subida do ambiente
- Fluxo do avaliador:
cp .env.example .envseguido dedocker compose updeve deixar a API respondendo emhttp://localhost:3000, comGET /flagsretornando 200, sem nenhum passo manual adicional
- Build com
docker buildx builda partir do estágioproduction - Manifest list contendo as plataformas
linux/amd64elinux/arm64 - Build com
--sbom=truee--provenance=true, com as attestations publicadas junto da imagem - Publicada em repositório público no Docker Hub com uma tag semver (ex:
1.0.0) e a taglatest, ambas apontando para o mesmo digest - Tamanho da imagem, reportado por
docker image lsapósdocker pull --platform linux/amd64, menor ou igual a 350 MB
- Executar
docker scout cvescontra a imagem publicada e salvar a saída completa emreports/scout-cves.txt - A imagem não pode conter nenhuma CVE de severidade CRITICAL com correção disponível (verificável com
docker scout cves --only-severity critical --only-fixed) - CVEs de severidade HIGH, e CRITICAL sem correção disponível, se existirem, devem ser listadas no README com justificativa ou plano de mitigação
- Serviço
appsem instruçãobuild: usa a imagem publicada no Docker Hub, referenciada pela tag semver - Serviços
appedbcom restart policy (alwaysouunless-stopped) e limites explícitos de CPU e memória (serviços de execução única, como um eventual serviço de migração, ficam fora desta regra) - Nenhum bind mount de código-fonte; dados do PostgreSQL em volume nomeado
- Mesmo esquema de variáveis de ambiente (
.env.example→.env). Em produção real esses valores viriam de um gerenciador de segredos; aqui o.envexiste para permitir a subida local pelo avaliador - Migrações aplicadas automaticamente na subida do ambiente
- Healthchecks ativos:
docker compose -f compose.prod.yaml psdeve exibirappedbcomohealthy - Fluxo do avaliador:
cp .env.example .envseguido dedocker compose -f compose.prod.yaml up -ddeve deixar a API respondendo emhttp://localhost:3000, comGET /flagsretornando 200
Substitua o conteúdo do README.md do repositório base pela documentação da sua entrega, com as seguintes seções:
- Sobre a entrega: visão geral da solução em 1 a 2 parágrafos
- Imagem no Docker Hub: link do repositório, comando
docker pull, digest do manifest e comparação de tamanho entre a imagemdeve a imagemproduction - Decisões técnicas: justificativa da imagem base de produção com comparação a pelo menos 1 alternativa considerada, e explicação da estratégia de cache de build adotada
- Como rodar (desenvolvimento): passo a passo, incluindo o uso do watch e do profile
tools - Como rodar (produção): passo a passo
- Segurança e supply chain: comandos para verificar usuário não-root, labels, SBOM e provenance; resumo do relatório do Scout e justificativa das CVEs remanescentes (HIGH, e CRITICAL sem correção disponível), se houver
- Validação: mapeamento de cada critério de aceite ao comando que o avaliador executa para verificá-lo
A entrega é avaliada contra os critérios abaixo. Todos são obrigatórios.
Dockerfile e contexto de build
☐ Dockerfile único na raiz com os estágios nomeados dev, build e production
☐ Nenhuma imagem em Dockerfile, compose.yaml ou compose.prod.yaml usa tag latest ou omite a tag
☐ .dockerignore presente, excluindo no mínimo node_modules, dist, .git e .env
☐ Instalações de dependências usam RUN --mount=type=cache
☐ docker build --target dev . e docker build --target production . concluem sem erro
Ambiente de desenvolvimento
☐ cp .env.example .env && docker compose up deixa a API respondendo em http://localhost:3000 e GET /flags retorna 200 (migrações aplicadas), sem passos manuais adicionais
☐ db possui healthcheck e app depende dele com condition: service_healthy
☐ Com docker compose watch (ou up --watch), alteração em arquivo de src/ é refletida sem rebuild e alteração no package.json dispara rebuild
☐ docker compose --profile tools up -d sobe o cliente de banco em http://localhost:8081
☐ docker compose exec app id -u retorna um UID diferente de 0
☐ .env não está versionado e .env.example está, com valores funcionais
Imagem de produção e Docker Hub
☐ docker buildx imagetools inspect da imagem lista linux/amd64 e linux/arm64
☐ docker buildx imagetools inspect exibe as attestations de SBOM e provenance
☐ A tag semver e a tag latest apontam para o mesmo digest
☐ Após docker pull --platform linux/amd64, docker image ls reporta tamanho menor ou igual a 350 MB
☐ docker image inspect mostra User não-root, HEALTHCHECK configurado e as 4 labels OCI exigidas
☐ Com um banco acessível (ex: no ambiente de produção), o container fica healthy pelo HEALTHCHECK da própria imagem
☐ docker stop encerra o container em menos de 10 segundos, sem esperar o timeout do SIGKILL
Docker Scout
☐ reports/scout-cves.txt contém a saída completa do docker scout cves da imagem publicada
☐ Zero CVEs de severidade CRITICAL com correção disponível (docker scout cves --only-severity critical --only-fixed)
☐ CVEs HIGH, e CRITICAL sem correção disponível, se existirem, estão listadas e justificadas no README
Ambiente de produção
☐ compose.prod.yaml não contém instrução build e referencia a imagem do Docker Hub pela tag semver
☐ Os serviços app e db têm restart policy e limites de CPU e memória
☐ Não há bind mount de código-fonte e os dados do PostgreSQL estão em volume nomeado
☐ cp .env.example .env && docker compose -f compose.prod.yaml up -d deixa a API em http://localhost:3000, GET /flags retorna 200 e ps exibe app e db como healthy
README
☐ Contém todas as seções obrigatórias listadas no requisito 6
☐ Inclui o link do repositório no Docker Hub e o comando docker pull
☐ Justifica a escolha da imagem base de produção comparando com pelo menos 1 alternativa
☐ A seção Validação mapeia os critérios de aceite aos comandos de verificação
Consistência geral
☐ O código da aplicação (src/, package.json, package-lock.json, tsconfig.json, migrações) não foi alterado
☐ Nenhuma credencial hardcoded em Dockerfile ou nos arquivos compose (valores sempre vindos do .env); o único arquivo versionado com valores de credenciais é o .env.example, contendo exclusivamente credenciais de desenvolvimento local
.
├── Dockerfile
├── .dockerignore
├── compose.yaml
├── compose.prod.yaml
├── .env.example
├── reports/
│ └── scout-cves.txt
├── src/ (não alterar)
├── package.json (não alterar)
├── package-lock.json (não alterar)
├── tsconfig.json (não alterar)
├── ... (demais arquivos da aplicação, não alterar)
└── README.md (substituído pelo aluno)
Arquivos adicionais de containerização criados por você, como scripts de inicialização, podem ficar na raiz ou em uma pasta docker/.
- Repositório público no GitHub, fork do repositório base, com todo o conteúdo na branch
main - Repositório público no Docker Hub com a imagem de produção, com o link e o comando
docker pullno README
https://github.com/devfullcycle/fc4-desafio-docker-node-api
1. Fork e exploração: leia os scripts do package.json, entenda as variáveis de ambiente esperadas e o funcionamento do db:migrate.
2. Crie o .dockerignore e o estágio dev do Dockerfile.
3. Monte o compose.yaml mínimo (app + db com healthcheck e migrações automáticas) até a API responder em http://localhost:3000.
4. Adicione o develop.watch e o serviço de administração de banco sob o profile tools.
5. Escreva os estágios build e production e valide localmente com docker build --target production ..
6. Crie o builder multi-plataforma no Buildx e faça o build com SBOM e provenance, publicando as duas tags no Docker Hub.
7. Rode o Scout contra a imagem publicada. Se houver CVE CRITICAL com correção disponível, ajuste a imagem base e republique. Salve o relatório em reports/.
8. Monte o compose.prod.yaml consumindo a imagem publicada, incluindo a aplicação automática das migrações.
9. Escreva o README com as evidências e a seção de validação.
10. Revisão final: percorra a checklist de critérios de aceite item por item antes do push final.
A ordem das instruções no Dockerfile determina o aproveitamento de cache. Copiar o package.json e o lockfile antes do restante do código muda drasticamente o tempo de rebuild durante o desenvolvimento.
O tamanho da imagem final é consequência das suas decisões (imagem base, multi-stage, apenas dependências de produção), não de um ajuste cosmético no fim. Se a imagem estourou o limite, revisite as decisões.
O HEALTHCHECK executa dentro da imagem final: bases enxutas nem sempre trazem curl ou wget, mas o próprio node (com o fetch global) resolve.
Se o Scout apontar CVEs CRITICAL com correção disponível, o caminho quase sempre é atualizar ou trocar a imagem base, não conviver com elas.
docker buildx imagetools inspect é a ferramenta para conferir plataformas, digests e attestations de uma imagem publicada sem precisar puxá-la.
Teste o fluxo do avaliador do zero: clone o seu próprio fork em uma pasta limpa, copie o .env.example e execute exatamente os comandos descritos no README. Se qualquer passo extra for necessário, o critério não está atendido.