Como implantar uma aplicação Node.js com Docker (e quando usar Cloudflare Workers)


Como implantar uma aplicação Node.js com Docker (e quando usar Cloudflare Workers)

Quer colocar sua aplicação Node.js no ar de forma rápida, previsível e com menos surpresas? Dois caminhos populares hoje são: empacotar e executar com Docker em um servidor Linux (ex.: Ubuntu 24.04) e, em alguns cenários, ir direto para a borda usando Cloudflare Workers, que recentemente ampliou a compatibilidade com Node.js e até com Express.js. Neste guia, eu uno o melhor dos dois mundos: um passo a passo para você publicar com Docker e Docker Compose, além de um panorama de quando faz sentido migrar (ou complementar) com Workers. Ao longo do texto, também trago boas práticas de containerização para imagens menores, seguras e fáceis de manter.

Pré-requisitos essenciais

  • Aplicação Node.js funcional (pode ser uma API simples em Express ou um servidor HTTP nativo).
  • Docker instalado na sua máquina local para construir a imagem.
  • Uma conta em um registro de contêiner (como Docker Hub) para publicar a imagem.
  • Um servidor Linux (preferencialmente Ubuntu 24.04) para executar os contêineres com Docker Compose.

Parte 1 — Empacotando a aplicação Node.js com Docker

O primeiro passo é descrever como sua aplicação deve ser “empacotada” dentro de uma imagem. Isso é feito com um arquivo Dockerfile. O fluxo clássico e eficiente para Node.js segue esta ordem:

  • Base confiável e fixada: escolha uma imagem oficial (por exemplo, node:20.x). Fixar a versão (em vez de usar “latest”) evita que builds quebrem de surpresa.
  • Variáveis de ambiente: defina NODE_ENV=production para instalar somente dependências de produção e habilitar otimizações em bibliotecas.
  • WORKDIR: defina um diretório de trabalho, por exemplo “/app”.
  • Camadas com cache inteligente: copie apenas “package.json” e “package-lock.json” (ou “pnpm-lock.yaml”, “yarn.lock”) e rode “npm ci” (ou “pnpm i –frozen-lockfile”, “yarn install –frozen-lockfile”). Assim, enquanto o lockfile não muda, o Docker reaproveita a camada de dependências.
  • Copie o restante do projeto: só depois das dependências, copie o código. Isso acelera rebuilds quando você só altera o código-fonte.
  • Exponha a porta interna: documente a porta (ex.: 8080). “EXPOSE” é apenas documentação de intenção, a publicação real é feita no “docker run” ou no “docker-compose”.
  • Comando de entrada: defina o comando para startar sua app, por exemplo executar “node src/index.js”.

Crie também um .dockerignore para evitar enviar lixo para o build (o que torna a imagem maior e os builds mais lentos). Exemplos úteis: “.git”, “node_modules”, pastas de build locais e arquivos temporários.

Construindo e versionando a imagem

Com o Dockerfile pronto, construa a imagem localmente e atribua um nome e tag. Nomear com “usuario/imagem:tag” facilita publicar no registro e manter histórico. Exemplos de tags úteis: “latest” (a versão mais recente) e uma tag imutável por data ou semântica (ex.: “20240905” ou “1.2.3”).

  • Build com tag “latest”: docker build -t seuusuario/minhaapi:latest .
  • Tags adicionais: docker tag suaimagem seuusuario/minhaapi:20240905
  • Login e push: docker login, depois docker push seuusuario/minhaapi

Dica de troubleshooting: se “npm ci” falhar por alguma inconsistência, teste “npm install” para confirmar o problema. Em pipelines, prefira corrigir o lockfile para manter reprodutibilidade.

Instalando Docker e Docker Compose no Ubuntu 24.04

No servidor, instale os pré-requisitos, configure o repositório oficial e instale os pacotes do Docker. Em seguida, adicione seu usuário ao grupo “docker” para não precisar de “sudo” a cada comando. Valide a instalação executando “docker –version”, “docker ps” e “docker compose version”. Se não houver erros, está tudo pronto.

Orquestrando com Docker Compose

O Docker Compose simplifica o ciclo de vida do contêiner. Um arquivo básico define um serviço “minhaapi” que usa a imagem publicada. Boas opções:

  • image: “seuusuario/minhaapi:latest” ou uma tag específica.
  • container_name: para facilitar logs e inspeção.
  • restart: unless-stopped: recomeça automaticamente se o processo cair.
  • ports: mapeie “80:8080” para expor a app na porta 80 do servidor.
  • environment: variáveis de ambiente (sem segredos versionados).

Suba em modo interativo (para ver logs e validar) com “docker compose up”, e depois em segundo plano com “docker compose up -d”. Em atualizações, faça “docker compose pull” para baixar a nova imagem e “docker compose up -d –force-recreate” para recriar o contêiner com a versão nova.

Boas práticas de Dockerfile para Node.js (imagens menores, seguras e rápidas)

  • Use multi-stage builds quando compilar artefatos: se sua app transpila (TypeScript, Babel, Next.js com build para produção), faça um estágio “builder” (instala dependências de desenvolvimento, gera “dist”) e outro “runtime” leve (ex.: node:20-alpine), copiando só o necessário para rodar.
  • Evite rodar como root: use o usuário “node” presente nas imagens oficiais ou crie um usuário sem privilégios. Troque para “USER node” antes do CMD/ENTRYPOINT.
  • Pin de versões: fixe a base (node:20.17, por exemplo) para builds determinísticos.
  • Instalação determinística: “npm ci” com package-lock.json; em Yarn/PNPM, use as flags de lockfile imutável.
  • .dockerignore robusto: exclua “node_modules”, “.git”, diretórios de artefatos e arquivos secretos. Isso acelera e protege.
  • Healthcheck (opcional): em produção, um HEALTHCHECK que bata em “/health” ajuda orquestradores a detectar falhas cedo.
  • Segredos: nunca copie chaves/credenciais para dentro da imagem. Injete via variáveis de ambiente, Docker secrets ou cofre externo.
  • Camadas pequenas: agrupe comandos RUN e limpe caches temporários. Em builds nativos (ex.: dependências C/C++), mantenha toolchains só no estágio “builder”.
  • Portas e sinalização: documente a porta interna e garanta que a app finalize corretamente ao receber sinais (SIGTERM) para shutdown gracioso.

Parte 2 — Quando (e como) rodar Express.js no Cloudflare Workers

Cloudflare Workers evoluiu bastante em compatibilidade com Node.js e agora oferece suporte ao “http.createServer” por meio de flags de compatibilidade e a API “httpServerHandler” (exposta via “cloudflare:node”). Isso permite que apps Express.js rodem diretamente no ambiente de Workers (em desenvolvimento local já funciona; a disponibilidade total em produção está sendo ampliada).

Como começar com Workers + Express

  • Atualize o CLI: use uma versão recente do wrangler (por exemplo, linha 4.28.x).
  • Compatibilidade: defina no seu arquivo de configuração a “compatibility_date” atual e ative flags como “nodejs_compat”, “enable_nodejs_http_modules” e “enable_nodejs_http_server_modules”.
  • App Express: crie sua app normalmente (rotas, middlewares). Em vez de iniciar um servidor tradicional, exponha o “httpServerHandler” configurado com a porta que você definir (ex.: 8080). Você pode, inclusive, aproveitar bindings como KV para persistência simples.
  • Dev e deploy: rode “wrangler dev” para testar localmente (ex.: acessar “/hello”) e faça o deploy com “wrangler deploy” quando a feature estiver habilitada no seu ambiente.

Workers vs Docker: o que considerar

  • Latência e distribuição: Workers brilham ao executar na borda, reduzindo latência global sem você gerir servidores.
  • Compatibilidade de runtime: bibliotecas que dependem de APIs de sistema, binários nativos ou file system podem ser melhores em Docker. Para APIs HTTP puras, Workers são excelentes.
  • Estado e armazenamento: em Workers, use KV, D1, R2, Durable Objects. Em Docker, você controla volumes, bancos e rede como preferir.
  • Custos e operação: Workers simplifica operação (sem patch de SO, sem orquestração), enquanto Docker dá controle total (rede privada, sidecars, jobs).
  • Escalabilidade: Workers escalam horizontalmente por padrão. Em Docker, você escala via Compose/Swarm/Kubernetes ou automatiza com sua infraestrutura.

Fluxo de deploy contínuo sem drama

  • Versione suas imagens: publique “latest” e uma tag imutável (data ou semântica). Em produção, aponte o Compose para a tag imutável; em staging, “latest” pode ser aceitável.
  • Atualizações previsíveis: no servidor, “docker compose pull” e depois “docker compose up -d –force-recreate”. Automatize no seu CI/CD (ex.: GitHub Actions, GitLab CI).
  • Rollback fácil: como você mantém tags por data/versão, voltar para uma imagem anterior é só ajustar a tag e recriar o serviço.
  • Observabilidade: exponha métricas e healthchecks. Em Compose, agregue logs e, quando precisar, “docker logs -f minhaapi”.
  • Segredos e configs: use variáveis de ambiente e arquivos externos; evite commits de chaves. Em Workers, configure variables e secrets pelo wrangler/console.

Checklist final de produção

  • Base Node fixada (ex.: node:20.x), NODE_ENV=production e lockfile presente.
  • .dockerignore abrangente e multi-stage build quando houver etapa de build.
  • Usuário não-root no runtime e superfície mínima (sem ferramentas de build na imagem final).
  • Tagueamento consistente, push para o registro e Compose com “restart: unless-stopped”.
  • Mapeamento de portas correto (ex.: 80:8080) e shutdown gracioso.
  • Healthcheck configurado (opcional, mas recomendado) e logs acessíveis.
  • Automação de deploy e rollback via CI/CD.
  • Se optar por Workers: flags de compatibilidade ativas, rotas testadas e bindings (KV/D1) configurados.

Conclusão

Você agora tem um roteiro completo para implantar uma aplicação Node.js com Docker, desde a construção da imagem até a execução com Docker Compose em um servidor Ubuntu 24.04, incluindo um caminho seguro para atualizações. De quebra, viu quando faz sentido ir para a borda com Cloudflare Workers e rodar Express.js diretamente no runtime deles. O “como” é importante — mas o “como com qualidade” é o que fará seu ciclo de desenvolvimento ficar mais rápido, seguro e estável: imagens enxutas, não-root, cache bem aproveitado e deploys reproduzíveis.

E você, pretende seguir com Docker no servidor, experimentar Workers para reduzir latência global, ou combinar os dois modelos conforme o caso? Compartilhe seu cenário e suas dúvidas nos comentários!

Categories