Docker e TypeScript: conteinerização avançada de aplicações
\n\nA conteinerização de aplicações TypeScript com Docker não é apenas empacotar código em uma imagem. Em um nível avançado, é uma arte de otimização, segurança e automação. Neste artigo, vamos abordar como criar imagens Docker eficientes para TypeScript, gerenciar o ambiente e integrar a conteinerização no pipeline CI/CD. Você aprenderá como reduzir o tamanho da imagem em 5 a 10 vezes, acelerar a compilação e melhorar o isolamento de dependências.
\n\n1. Compilação em múltiplas etapas (Multi-stage builds) para TypeScript
\n\nO problema clássico das imagens Docker para TypeScript é incluir na imagem final todas as ferramentas de desenvolvimento: compilador TypeScript, dependências de desenvolvimento, arquivos fonte. Isso resulta em tamanhos enormes (frequentemente > 1 GB). A solução é a compilação em múltiplas etapas.
\n\nA ideia é simples: o primeiro estágio (build) compila TypeScript para JavaScript e instala todas as dependências. O segundo estágio (production) copia apenas o JavaScript pronto e as dependências de produção.
\n\n# Estágio 1: Compilação\nFROM node:20-alpine AS builder\nWORKDIR /app\nCOPY package*.json ./\nRUN npm ci --only=development\nCOPY . .\nRUN npm run build\n\n# Estágio 2: Produção\nFROM node:20-alpine AS production\nWORKDIR /app\nCOPY --from=builder /app/dist ./dist\nCOPY --from=builder /app/package*.json ./\nRUN npm ci --only=production\nEXPOSE 3000\nCMD ["node", "dist/index.js"]\n\nEssa abordagem reduz o tamanho da imagem de ~800 MB para ~150 MB. Observe os --only=development e --only=production — são flags chave para separar as dependências.
2. Otimização do cache de camadas do Docker
\n\nCada instrução no Dockerfile cria uma camada. O Docker armazena em cache as camadas se elas não forem alteradas. A ordem correta das instruções é crítica para a velocidade de compilações repetidas.
\n\nExemplo ruim (quebra o cache):
\nFROM node:20-alpine\nWORKDIR /app\nCOPY . .\nRUN npm ci\nRUN npm run build\nCMD ["node", "dist/index.js"]\n\nAqui, qualquer alteração no código fonte (mesmo no README.md) redefine o cache para npm ci e compilação.
\n\nExemplo bom (preserva o cache):
\nFROM node:20-alpine AS builder\nWORKDIR /app\nCOPY package*.json tsconfig.json ./\nRUN npm ci --only=development\nCOPY src ./src\nRUN npm run build\n\nPor que isso funciona? As dependências mudam raramente, enquanto o código fonte muda frequentemente. Copiando package*.json separadamente, garantimos que a camada com npm ci seja reconstruída apenas quando esses arquivos forem alterados.
3. Trabalho com variáveis de ambiente e segredos
\n\nPara aplicações TypeScript em Node.js, o uso de variáveis de ambiente é padrão. No entanto, no Docker existem nuances.
\n\nErro típico: passar dados sensíveis (chaves de API, senhas) através de ENV no Dockerfile. Isso deixa segredos no histórico de camadas da imagem.
Abordagem correta:
\n- \n
- Use
--env-fileao iniciar o contêiner \n - Para produção, utilize Docker Secrets (no Swarm) ou Kubernetes Secrets \n
- Para desenvolvimento local — arquivo
.envcomdocker-compose\n
Exemplo de docker-compose.yml:
\nversion: '3.8'\nservices:\n app:\n build: .\n ports:\n - "3000:3000"\n env_file:\n - .env.production\n volumes:\n - ./logs:/app/logs\n\nNo código TypeScript, use a biblioteca dotenv para carregar as variáveis:
import dotenv from 'dotenv';\nimport { resolve } from 'path';\n\ndotenv.config({ path: resolve(__dirname, '../.env') });\n\nconst config = {\n port: parseInt(process.env.PORT || '3000', 10),\n dbUrl: process.env.DATABASE_URL,\n apiKey: process.env.API_KEY\n};\n\nexport default config;\n\nImportante: nunca faça commit de arquivos .env reais