Introdução: Por que a conteinerização avançada de TypeScript é uma necessidade
A conteinerização de aplicações TypeScript com Docker há muito deixou de ser apenas uma tendência — é o padrão do desenvolvimento moderno. No entanto, Dockerfiles básicos, que simplesmente copiam todo o projeto e executam npm start, estão longe do ideal. Em um nível avançado, enfrentamos desafios como otimização do tamanho das imagens, garantia de segurança, organização de arquitetura de microsserviços e configuração de recuperação automática.
Neste artigo, abordaremos técnicas que transformarão seu pipeline Docker para TypeScript em um padrão industrial: builds multi-estágio (multi-stage builds), minimização de camadas, uso de .dockerignore, Docker Compose para microsserviços, healthchecks e trabalho com variáveis de ambiente. Todos os exemplos são escritos em TypeScript e estão prontos para integração em projetos reais.
1. Build multi-estágio: do desenvolvimento à produção
O principal problema dos Dockerfiles básicos é que eles incluem tudo na imagem final: arquivos fonte, node_modules para desenvolvimento, ferramentas de build. Para TypeScript, isso é crítico, pois tsc e devDependencies não são necessários em produção.
O build multi-estágio (multi-stage build) resolve esse problema: o primeiro estágio compila e constrói o TypeScript, o segundo cria uma imagem mínima apenas com os arquivos necessários.
Exemplo de Dockerfile com dois estágios
# Estágio 1: Build do TypeScript
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build
# Estágio 2: Imagem de produção
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
Pontos-chave:
- Usamos
npm ci— reprodução exata das dependências a partir do arquivo lock. - Copiamos apenas
dist/enode_modules— os arquivos fonte TypeScript não vão para produção. - Imagem Alpine — base mínima (cerca de 5 MB).
Essa imagem pesa de 3 a 5 vezes menos do que um build ingênuo. Para um projeto grande com dezenas de dependências, a diferença pode chegar a centenas de megabytes.
Build de três estágios com dependências de desenvolvimento
Às vezes, é necessário separar dependências de desenvolvimento e produção. Adicionamos um terceiro estágio para instalar apenas as dependências de produção:
# Estágio 1: Instalação de todas as dependências
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
# Estágio 2: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Estágio 3: Produção
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=deps /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
2. Otimização da imagem: .dockerignore, cache e camadas
Cada instrução COPY, RUN e ADD cria uma nova camada no Docker. Quanto menos camadas e mais eficientemente elas forem armazenadas em cache, mais rápida será a construção.
.dockerignore — seu primeiro aliado
Crie um arquivo .dockerignore na raiz do projeto:
node_modules
dist
.git
.gitignore
*.md
.env
.env.*
Dockerfile
.dockerignore
coverage
.vscode
.idea
__tests__
Isso impede a cópia de lixo para o contexto de build e acelera a transferência de arquivos para o daemon Docker.
Ordem correta das instruções
O Docker armazena camadas em cache. Se você copiar primeiro o package.json e depois executar npm ci, essa camada será reutilizada enquanto o package.json não mudar. Em seguida, copie o código fonte
```