Uma API Node.js que funciona localmente não está automaticamente pronta para um ambiente de nuvem. As implantações de produção apresentam requisitos fáceis de ignorar: portas dinâmicas, verificações de integridade, desligamentos normais, segurança de contêineres, logs estruturados e configuração baseada em ambiente.
Neste tutorial, criaremos uma pequena API Node.js, empacotá-la-emos em um contêiner Docker, testaremos localmente e implantaremos no Google Cloud Run. Os mesmos princípios de design se aplicam a outras plataformas de contêineres gerenciados.
Esta abordagem fornece uma base prática para desenvolvimento de aplicativos em nuvem porque o aplicativo permanece portátil. A plataforma em nuvem executa o contêiner, mas o serviço em si não depende muito do código específico da plataforma.
O que vamos construir
O serviço de exemplo fornecerá três endpoints:
GET /retorna informações básicas da API.GET /healthinforma se o processo está em execução.POST /tasksvalida e aceita uma tarefa.
Também irá:
- Leia a configuração das variáveis de ambiente.
- Ouça na porta atribuída pela plataforma.
- Retorna erros JSON estruturados.
- Lide com sinais de terminação normalmente.
- Execute como um usuário de contêiner sem privilégios.
Você precisará do Node.js 20 ou posterior, do Docker, de um projeto do Google Cloud e da CLI do Google Cloud se quiser concluir a seção de implantação.
Crie o projeto Node.js
Crie um novo diretório e inicialize o projeto:
mkdir cloud-task-api
cd cloud-task-api
npm init -y
npm install express
Atualizar package.json:
{
"name": "cloud-task-api",
"version": "1.0.0",
"description": "A containerized Node.js API",
"type": "module",
"main": "src/server.js",
"scripts": {
"start": "node src/server.js"
},
"engines": {
"node": ">=20"
},
"dependencies": {
"express": "^5.0.0"
}
}
O uso de uma versão explícita do Node.js torna o requisito de tempo de execução visível para desenvolvedores e sistemas de construção.
Construa a API
Crie um src diretório e adicione src/server.js:
import express from "express";
import process from "node:process";
import crypto from "node:crypto";
const app = express();
const config = {
port: Number(process.env.PORT ?? 8080),
environment: process.env.NODE_ENV ?? "development",
serviceName: process.env.SERVICE_NAME ?? "task-api"
};
app.disable("x-powered-by");
app.use(express.json({ limit: "32kb" }));
app.get("/", (request, response) => {
response.json({
service: config.serviceName,
environment: config.environment,
status: "available"
});
});
app.get("/health", (request, response) => {
response.json({
status: "ok",
uptimeSeconds: Math.floor(process.uptime())
});
});
app.post("/tasks", (request, response) => {
const { title, priority = "normal" } = request.body;
if (typeof title !== "string" || title.trim().length < 3) {
return response.status(400).json({
error: {
code: "INVALID_TITLE",
message: "The title must contain at least three characters."
}
});
}
const allowedPriorities = new Set((
"low",
"normal",
"high"
));
if (!allowedPriorities.has(priority)) {
return response.status(400).json({
error: {
code: "INVALID_PRIORITY",
message: "Priority must be low, normal, or high."
}
});
}
const task = {
id: crypto.randomUUID(),
title: title.trim(),
priority,
createdAt: new Date().toISOString()
};
console.log(JSON.stringify({
severity: "INFO",
event: "task_created",
taskId: task.id,
priority: task.priority
}));
return response.status(201).json({ task });
});
app.use((request, response) => {
response.status(404).json({
error: {
code: "NOT_FOUND",
message: "The requested endpoint does not exist."
}
});
});
A API limita os corpos JSON a 32 KB. Os limites de tamanho de solicitação ajudam a proteger um serviço público contra cargas inesperadamente grandes e uso excessivo de memória.
O exemplo também desativa o Express X-Powered-By cabeçalho. A remoção do cabeçalho não é uma medida de segurança completa, mas os aplicativos geralmente não precisam anunciar sua estrutura de servidor.
Inicie o servidor corretamente
Adicione o seguinte código na parte inferior de src/server.js:
const server = app.listen(
config.port,
"0.0.0.0",
() => {
console.log(JSON.stringify({
severity: "INFO",
event: "server_started",
port: config.port,
environment: config.environment
}));
}
);
function shutdown(signal) {
console.log(JSON.stringify({
severity: "INFO",
event: "shutdown_started",
signal
}));
server.close(error => {
if (error) {
console.error(JSON.stringify({
severity: "ERROR",
event: "shutdown_failed",
message: error.message
}));
process.exit(1);
}
console.log(JSON.stringify({
severity: "INFO",
event: "shutdown_complete"
}));
process.exit(0);
});
setTimeout(() => {
console.error(JSON.stringify({
severity: "ERROR",
event: "shutdown_timeout"
}));
process.exit(1);
}, 10_000).unref();
}
process.on("SIGTERM", () => shutdown("SIGTERM"));
process.on("SIGINT", () => shutdown("SIGINT"));
Ouvindo 0.0.0.0 torna o serviço acessível fora de seu contêiner. Leitura PORT do ambiente permite que a plataforma selecione a porta de escuta.
Os manipuladores de desligamento impedem que o servidor aceite novas conexões enquanto dão tempo para as solicitações ativas serem concluídas. O temporizador de fallback evita que o processo aguarde indefinidamente.
Teste o serviço localmente
Inicie a API:
npm start
Abra outro terminal e verifique o endpoint de integridade:
curl http://localhost:8080/health
Crie uma tarefa:
curl -X POST http://localhost:8080/tasks \
-H "Content-Type: application/json" \
-d '{"title":"Review deployment logs","priority":"high"}'
Teste uma solicitação inválida:
curl -X POST http://localhost:8080/tasks \
-H "Content-Type: application/json" \
-d '{"title":"A","priority":"urgent"}'
A solicitação inválida deve retornar um 400 status e um erro JSON previsível.
Crie um contêiner de produção
Crie um Dockerfile:
FROM node:20-alpine
ENV NODE_ENV=production
ENV PORT=8080
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --chown=node:node src ./src
USER node
EXPOSE 8080
CMD ("node", "src/server.js")
A imagem instala apenas dependências de produção e alterna para as dependências sem privilégios existentes node usuário antes de iniciar o serviço.
Criar .dockerignore:
node_modules
npm-debug.log
.git
.gitignore
.env
Dockerfile*
README.md
A exclusão de arquivos locais torna o contexto de construção menor e reduz a chance de copiar credenciais ou artefatos de desenvolvimento na imagem.
Guia do SitePoint para usando Node.js com Docker fornece uma introdução mais detalhada a imagens, contêineres, montagens de ligação e fluxos de trabalho de desenvolvimento baseados em Docker.
Construa o contêiner:
docker build -t cloud-task-api .
Execute-o localmente:
docker run --rm \
-p 8080:8080 \
-e SERVICE_NAME=container-task-api \
cloud-task-api
Visita http://localhost:8080 ou repita o anterior curl comandos.
Adicione uma verificação de integridade do contêiner
O /health endpoint também pode ser usado pelo Docker:
HEALTHCHECK --interval=30s --timeout=3s \
--start-period=5s --retries=3 \
CMD wget -qO- http://127.0.0.1:8080/health || exit 1
Adicione esta instrução antes CMD se você precisar de informações de saúde no nível do Docker.
Mantenha as verificações de integridade rápidas e independentes de serviços externos lentos. Um endpoint básico de atividade deve confirmar que o processo pode responder. Uma verificação de prontidão separada pode testar se as dependências necessárias para servir o tráfego estão disponíveis.
Implantar o serviço no Cloud Run
Autentique a CLI do Google Cloud e selecione seu projeto:
gcloud auth login
gcloud config set project YOUR_PROJECT_ID
Implante o diretório de origem:
gcloud run deploy cloud-task-api \
--source . \
--region us-central1 \
--allow-unauthenticated \
--set-env-vars SERVICE_NAME=cloud-task-api
O tutorial oficial do Google Cloud para criando e implantando um serviço Node.js no Cloud Run explica a configuração necessária do projeto e o fluxo de implantação.
O --allow-unauthenticated opção torna a API de exemplo acessível publicamente. Não o utilize para serviços administrativos privados ou terminais que expõem dados protegidos. Configure o acesso baseado em identidade.
Após a implantação, o comando imprime a URL do serviço. Teste com:
curl https://YOUR_SERVICE_URL/health
Mantenha a configuração fora da imagem
As configurações específicas da implantação devem ser fornecidas por meio de variáveis de ambiente, em vez de gravadas no contêiner.
Para configuração não confidencial, você pode implantar outra revisão com:
gcloud run services update cloud-task-api \
--region us-central1 \
--set-env-vars NODE_ENV=production,SERVICE_NAME=tasks
Não armazene senhas de banco de dados, chaves de API ou segredos de assinatura diretamente em um Dockerfile, repositório de origem ou comando de implantação salvo no histórico do shell. Use um armazenamento secreto gerenciado e conceda ao serviço acesso apenas aos segredos necessários.
Equipes usando Serviços de consultoria do Google Cloud ainda devem manter a propriedade de suas decisões de arquitetura, configuração de implantação, políticas de acesso e documentação operacional. A orientação externa pode ajudar na migração ou no design da plataforma, mas a equipa de aplicação deve compreender como o serviço é protegido, monitorizado e recuperado.
Melhore a observabilidade
O exemplo grava objetos JSON de linha única na saída padrão. Os logs estruturados são mais fáceis de serem analisados por um sistema de log em nuvem do que mensagens inconsistentes legíveis por humanos.
Cada log deve incluir contexto operacional útil, como:
console.log(JSON.stringify({
severity: "INFO",
event: "request_completed",
method: request.method,
path: request.path,
statusCode: response.statusCode,
durationMs
}));
Evite registrar cabeçalhos de autorização, cookies, órgãos de solicitação, informações pessoais ou segredos.
Na produção, as medidas úteis incluem:
- Contagem de solicitações e latência.
- Taxa de erro por endpoint e status.
- Hora de inicialização do contêiner.
- Uso de memória e CPU.
- Contagem de instâncias ativas.
- Falha nas revisões de implantação.
Crie alertas sobre sintomas que afetam os usuários, e não apenas sobre mensagens de log individuais.
Evite armazenar o estado dentro do contêiner
Os recipientes gerenciados devem ser tratados como descartáveis. Uma plataforma pode iniciar ou parar instâncias conforme a demanda muda, e os arquivos locais podem desaparecer com a instância.
Armazene informações duráveis em um banco de dados externo ou serviço de armazenamento de objetos. Não use matrizes na memória ou arquivos JSON locais como armazenamento de dados oficial para sessões, pedidos, tarefas ou registros de usuário.
A API neste tutorial retorna a tarefa criada, mas não a persiste. Uma versão de produção deve validar a solicitação e depois gravar a tarefa em um banco de dados gerenciado por meio de uma camada dedicada de acesso a dados.
Lista de verificação de produção
Antes de lançar um serviço em nuvem, verifique se:
- O aplicativo escuta na porta fornecida pela plataforma.
- Os segredos são fornecidos por meio de um armazenamento secreto gerenciado.
- O contêiner é executado como um usuário sem privilégios.
- Os órgãos de solicitação têm limites de tamanho.
- As entradas são validadas no servidor.
- Os registros excluem valores confidenciais.
- Os sinais de desligamento são tratados.
- O estado durável é armazenado fora do contêiner.
- O acesso público é intencional.
- Os procedimentos de implantação e reversão são documentados.
- Os alertas cobrem latência, disponibilidade e taxas de erro.
Fixe também e atualize regularmente as versões de tempo de execução e dependência. Execute testes automatizados e uma construção de contêiner em integração contínua antes de implantar uma nova revisão.
Considerações finais
A implantação de uma API Node.js em uma plataforma de contêiner gerenciada é simples, mas a prontidão da produção depende das decisões tomadas antes da execução do comando de implantação.
Um contêiner portátil, configuração baseada em ambiente, registro estruturado, validação de entrada, desligamento normal e armazenamento de estado externo criam uma base mais sólida do que atalhos específicos de plataforma. Com essas práticas em vigor, o serviço fica mais fácil de testar localmente, implantar de forma consistente, monitorar em produção e mover se seus requisitos de infraestrutura mudarem.




