Docker e TypeScript: conteinerização de aplicações do zero

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: por que containerizar aplicações TypeScript?

O desenvolvimento moderno de software é impensável sem containerização. Docker se tornou o padrão de facto para empacotar, distribuir e executar aplicações em ambientes isolados. Em combinação com TypeScript — um superconjunto estritamente tipado de JavaScript — o Docker garante previsibilidade, portabilidade e simplifica os processos de CI/CD.

Neste artigo, vamos abordar como containerizar corretamente uma aplicação TypeScript: desde um Dockerfile básico até uma compilação pronta para produção usando docker-compose e compilação em múltiplos estágios. Você aprenderá como evitar erros comuns, reduzir o tamanho da imagem e acelerar a implantação.

Fundamentos: TypeScript e Node.js no Docker

TypeScript é uma linguagem que compila para JavaScript. Isso significa que, para executar uma aplicação TypeScript em um contêiner, primeiro precisamos compilar o código-fonte e depois executar o JavaScript resultante com Node.js. Docker é ideal para automatizar esse processo.

Criando uma aplicação TypeScript simples

Para começar, vamos criar uma aplicação mínima em TypeScript. Estrutura do projeto:

my-ts-app/
├── src/
│   └── index.ts
├── package.json
├── tsconfig.json
└── Dockerfile

src/index.ts:

import express, { Request, Response } from 'express';

const app = express();
const PORT = process.env.PORT || 3000;

app.get('/', (req: Request, res: Response) => {
  res.json({ message: 'Hello from TypeScript + Docker!' });
});

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

package.json:

{
  "name": "my-ts-app",
  "version": "1.0.0",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "ts-node src/index.ts"
  },
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "@types/express": "^4.17.21",
    "@types/node": "^20.11.0",
    "typescript": "^5.3.3",
    "ts-node": "^10.9.2"
  }
}

tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

Dockerfile básico

A primeira versão, mais simples, do Dockerfile:

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

RUN npm run build

EXPOSE 3000

CMD ["npm", "start"]

Este Dockerfile copia todo o projeto, instala as dependências, compila o TypeScript e executa a aplicação. No entanto, essa abordagem tem desvantagens: o tamanho da imagem é grande e o cache de camadas não é eficiente.

Otimização: compilação em múltiplos estágios e redução de tamanho

Para ambientes de produção, é recomendado usar a compilação em múltiplos estágios (multi-stage build). Isso permite separar as etapas de compilação e execução, mantendo apenas os arquivos necessários na imagem final.

Dockerfile de produção com compilação em múltiplos estágios

# Stage 1: Build
FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

# Stage 2: Production
FROM node:20-alpine

WORKDIR /app

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

EXPOSE 3000

USER node

CMD ["node", "dist/index.js"]

Vantagens dessa abordagem:

  • Menor tamanho da imagem — a camada final contém apenas o código compilado e as dependências de produção.
  • Segurança — o código-fonte TypeScript não permanece no contêiner, dev

Blogs

Book Recommendations