Amigo do Dev
DevOps
12 min de leitura

Docker na Prática: Containerize sua Aplicação Node.js do Zero ao Deploy em 2026

Aprenda a criar Dockerfiles eficientes, orquestrar serviços com Docker Compose e fazer deploy de aplicações Node.js em produção com segurança e boas práticas.

LinkedIn

1. Introdução

Em 2026, saber trabalhar com Docker deixou de ser diferencial e passou a ser requisito em praticamente qualquer vaga de desenvolvedor back-end ou full-stack. Container virou a linguagem universal do deploy.

Mas ainda existe muita confusão sobre como estruturar corretamente um Dockerfile, usar multi-stage builds, gerenciar variáveis de ambiente com segurança e compor serviços com Docker Compose.

Neste artigo você vai ver tudo isso na prática, partindo do zero com uma aplicação Node.js + TypeScript até um setup pronto para produção.

2. O Que É Docker (e Por Que Você Deveria Usar)

Docker é uma plataforma de containerização: você empacota sua aplicação junto com todas as suas dependências numa imagem isolada. Isso resolve o clássico problema "funciona na minha máquina".

2.1 Conceitos essenciais

  • Imagem — snapshot imutável do ambiente da aplicação
  • Container — instância em execução de uma imagem
  • Dockerfile — receita para construir uma imagem
  • Docker Compose — orquestrador para múltiplos containers locais
  • Registry — repositório de imagens (Docker Hub, GHCR, ECR etc.)

2.2 Vantagens práticas

  • Ambiente idêntico em dev, staging e produção
  • Onboarding de novos devs em minutos
  • Rollback rápido — basta trocar a tag da imagem
  • Isolamento de dependências entre projetos
  • Base para Kubernetes e outras orquestrações

3. O Projeto Base: API Node.js + TypeScript

Vamos containerizar uma API REST simples com Express e TypeScript. Estrutura inicial:

minha-api/
├── src/
│   └── index.ts
├── package.json
├── tsconfig.json
└── .env
// src/index.ts
import express from 'express';

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

app.use(express.json());

app.get('/health', (_req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

app.get('/api/hello', (_req, res) => {
  const name = process.env.APP_NAME ?? 'World';
  res.json({ message: `Hello, ${name}!` });
});

app.listen(PORT, () => {
  console.log(`🚀 API rodando na porta ${PORT}`);
});

4. Seu Primeiro Dockerfile

Um Dockerfile ruim gera imagens enormes, lentas e com problemas de segurança. Veja um exemplo ingênuo e seus problemas:

# ❌ Dockerfile ingênuo — NÃO use em produção
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
CMD ["node", "dist/index.js"]

Problemas dessa abordagem:

  • Imagem base pesada (~1 GB)
  • Dependências de desenvolvimento incluídas
  • Código-fonte exposto na imagem final
  • Cache do Docker mal aproveitado

5. Multi-Stage Build: A Forma Certa

Com multi-stage builds, você usa um container para compilar e outro, menor, para rodar:

# ✅ Dockerfile otimizado com multi-stage build

# ── Stage 1: Build ──────────────────────────────────────────────
FROM node:20-alpine AS builder
WORKDIR /build

# Copiar manifests primeiro para aproveitar cache
COPY package*.json ./
RUN npm ci --include=dev

# Compilar TypeScript
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build


# ── Stage 2: Produção ───────────────────────────────────────────
FROM node:20-alpine AS production
WORKDIR /app

# Criar usuário não-root por segurança
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# Apenas dependências de produção
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force

# Copiar apenas o build final, não o código-fonte
COPY --from=builder /build/dist ./dist

# Usar usuário não-root
USER appuser

# Expor porta e definir variáveis padrão
EXPOSE 3000
ENV NODE_ENV=production

# Healthcheck
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
  CMD wget -qO- http://localhost:3000/health || exit 1

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

Resultado: imagem de ~180 MB em vez de ~1 GB, sem código-fonte exposto e com usuário não-root.

.dockerignore: Tão Importante Quanto .gitignore

Sem um .dockerignore, o Docker envia tudo para o build context — incluindo node_modules locais e arquivos sensíveis:

# .dockerignore
node_modules/
dist/
.git/
.gitignore
*.log
.env
.env.*
coverage/
.nyc_output/
README.md
docker-compose*.yml

6. Docker Compose: Orquestrando Múltiplos Serviços

Sua API provavelmente precisa de banco de dados, cache e talvez um serviço de fila. O Docker Compose resolve isso localmente:

# docker-compose.yml
version: "3.9"

services:
  api:
    build:
      context: .
      target: production
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:password@postgres:5432/mydb
      - REDIS_URL=redis://redis:6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped
    networks:
      - app-net

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-net

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    networks:
      - app-net

volumes:
  postgres_data:
  redis_data:

networks:
  app-net:
    driver: bridge

Comandos do dia a dia:

# Subir todos os serviços em background
docker compose up -d

# Ver logs em tempo real
docker compose logs -f api

# Recriar apenas a API após uma mudança
docker compose up -d --build api

# Derrubar tudo (preserva volumes)
docker compose down

# Derrubar tudo e apagar volumes (cuidado em produção!)
docker compose down -v

7. Variáveis de Ambiente com Segurança

Nunca comite segredos no Dockerfile ou no docker-compose.yml. Use arquivos .env:

# .env (adicione ao .gitignore!)
DATABASE_URL=postgresql://user:senha_secreta@postgres:5432/mydb
JWT_SECRET=minha_chave_super_secreta_gerada_com_o_amigo_do_dev
REDIS_URL=redis://redis:6379
# docker-compose.yml — referenciar .env
services:
  api:
    env_file:
      - .env

💡 Dica: Use o Gerador de JWT Secret do Amigo do Dev para criar chaves criptograficamente seguras para o seu JWT_SECRET.

8. Boas Práticas Resumidas

8.1 Segurança

  • ✅ Sempre use usuário não-root (USER appuser)
  • ✅ Imagens alpine ou distroless reduzem superfície de ataque
  • ✅ Não embuta segredos na imagem — use variáveis de ambiente ou secrets
  • ✅ Escaneie vulnerabilidades com docker scout ou Trivy
  • ✅ Defina --read-only no filesystem quando possível

8.2 Performance

  • ✅ Ordene camadas do Dockerfile do que muda menos para o que muda mais
  • ✅ Use npm ci em vez de npm install para builds reproduzíveis
  • ✅ Multi-stage build sempre em produção
  • ✅ Adicione --no-cache no CI para evitar builds stale

8.3 Observabilidade

  • ✅ Implemente HEALTHCHECK no Dockerfile
  • ✅ Exponha métricas em /metrics para Prometheus
  • ✅ Use logs em formato JSON para facilitar ingestão no Loki/Datadog

9. Integração com CI/CD (GitHub Actions)

Automatize o build e push da imagem no merge para main:

# .github/workflows/docker.yml
name: Build & Push Docker Image

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          target: production
          push: true
          tags: |
            ghcr.io/${{ github.repository }}:latest
            ghcr.io/${{ github.repository }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

10. Conclusão

Você agora tem um setup de Docker profissional, com:

  • Multi-stage build para imagens pequenas e seguras
  • .dockerignore bem configurado
  • Docker Compose com banco de dados e cache
  • Variáveis de ambiente gerenciadas corretamente
  • CI/CD automatizado com GitHub Actions

Docker é apenas o começo da jornada DevOps. O próximo passo natural é Kubernetes para orquestração em escala. Mas com o que vimos aqui, você já está pronto para subir aplicações em qualquer VPS, Railway, Render ou serviço de cloud.

Não se esqueça de explorar as ferramentas do Amigo do Dev que podem ajudar no seu dia a dia como dev!

Tags

DockerNode.jsDevOpsContainerDocker ComposeDeployCI/CDInfraestrutura

© 2026 Amigo do Dev — Ferramentas gratuitas para desenvolvedores