Docker: do desenvolvimento à produção

Guia completo de Docker para conteinerizar aplicações. Dockerfile, Docker Compose, builds multi-stage e deploy em produção com exemplos práticos.

Guia Docker do desenvolvimento à produção

Docker revoluciona a forma como aplicações são desenvolvidas, testadas e implantadas. Ao encapsular uma aplicação e suas dependências em um contêiner portátil, Docker elimina o clássico problema do "funciona na minha máquina" e garante consistência em todos os ambientes. Este guia cobre o caminho completo desde o primeiro Dockerfile até o deploy em produção.

Docker em 2026

O Docker Desktop 5.x traz melhorias significativas de desempenho, incluindo suporte nativo a containerd, gerenciamento otimizado de recursos e integração fluida com Kubernetes. Imagens multi-arquitetura (ARM/x86) são agora prática padrão.

Fundamentos da conteinerização

Um contêiner é uma unidade de software leve que empacota código, runtime, bibliotecas do sistema e configurações. Diferente de máquinas virtuais que virtualizam o hardware, contêineres compartilham o kernel do sistema hospedeiro, tornando-os mais rápidos para iniciar e menos intensivos em recursos.

bash
# terminal
# Docker installation on Ubuntu
sudo apt update
sudo apt install -y docker.io

# Add user to docker group (avoids sudo)
sudo usermod -aG docker $USER

# Verify installation
docker --version
# Docker version 26.1.0, build 1234567

# First container: downloads image and runs
docker run hello-world

Esse comando baixa a imagem hello-world do Docker Hub e lança um contêiner que exibe uma mensagem de confirmação.

bash
# terminal
# List running containers
docker ps

# List all containers (including stopped)
docker ps -a

# List downloaded images
docker images

# Remove a container
docker rm <container_id>

# Remove an image
docker rmi <image_name>

Esses comandos básicos gerenciam o ciclo de vida de contêineres e imagens.

Criando o primeiro Dockerfile

Um Dockerfile contém instruções para construir uma imagem Docker. Cada instrução cria uma camada na imagem final, permitindo cache e reutilização.

dockerfile
# Dockerfile
# Base image: Node.js 22 on Alpine Linux (lightweight)
FROM node:22-alpine

# Set working directory in the container
WORKDIR /app

# Copy dependency files first (cache optimization)
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy source code
COPY . .

# Expose port (documentation)
EXPOSE 3000

# Startup command
CMD ["node", "server.js"]

A ordem das instruções é crucial para a otimização do cache. Arquivos que mudam raramente (package.json) devem ser copiados antes do código-fonte.

bash
# terminal
# Build image with a tag
docker build -t my-app:1.0 .

# Run the container
docker run -d -p 3000:3000 --name my-app-container my-app:1.0

# Check logs
docker logs my-app-container

# Access container shell
docker exec -it my-app-container sh

A flag -d executa o contêiner em segundo plano, -p mapeia a porta 3000 do contêiner para a porta 3000 do host.

Alpine vs Debian

Imagens Alpine são significativamente menores (cerca de 5 MB vs 120 MB para Debian). Porém, utilizam musl libc em vez de glibc, o que pode causar incompatibilidades com algumas dependências nativas. Quando surgirem problemas, recomenda-se preferir imagens baseadas em Debian (node:22-slim).

Builds multi-stage para produção

Builds multi-stage criam imagens de produção otimizadas separando o ambiente de build do ambiente de execução. Apenas os artefatos necessários são incluídos na imagem final.

dockerfile
# Dockerfile.production
# ============================================
# Stage 1: Build
# ============================================
FROM node:22-alpine AS builder

WORKDIR /app

# Copy and install dependencies (including devDependencies)
COPY package*.json ./
RUN npm ci

# Copy source code
COPY . .

# Build the application (TypeScript, bundling, etc.)
RUN npm run build

# ============================================
# Stage 2: Production
# ============================================
FROM node:22-alpine AS production

# Non-root user for security
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001

WORKDIR /app

# Copy only necessary files from builder stage
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

# Switch to non-root user
USER nodejs

# Environment variables
ENV NODE_ENV=production
ENV PORT=3000

EXPOSE 3000

# Startup command
CMD ["node", "dist/server.js"]

Essa abordagem reduz significativamente o tamanho da imagem final ao excluir ferramentas de build, devDependencies e arquivos-fonte.

bash
# terminal
# Build with specific file
docker build -f Dockerfile.production -t my-app:production .

# Compare image sizes
docker images | grep my-app
# my-app    production    abc123    150MB
# my-app    1.0           def456    450MB

A redução de tamanho pode chegar a 60-70% dependendo do projeto, melhorando os tempos de deploy e reduzindo a superfície de ataque.

Docker Compose para orquestração local

Docker Compose simplifica o gerenciamento de aplicações multi-contêiner. Um arquivo YAML declara todos os serviços, suas configurações e dependências.

yaml
# docker-compose.yml
version: "3.9"

services:
  # Main application
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://postgres:secret@db:5432/myapp
      - REDIS_URL=redis://cache:6379
    volumes:
      # Mount source code for hot-reload
      - ./src:/app/src
      - ./package.json:/app/package.json
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    networks:
      - app-network

  # PostgreSQL database
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: myapp
    volumes:
      # Data persistence
      - postgres_data:/var/lib/postgresql/data
      # Initialization script
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - app-network

  # Redis cache
  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes
    networks:
      - app-network

# Named volumes for persistence
volumes:
  postgres_data:
  redis_data:

# Dedicated network for isolation
networks:
  app-network:
    driver: bridge

Os serviços se comunicam usando seus nomes (db, cache) através da rede interna do Docker. Os healthchecks garantem que as dependências estejam prontas antes da aplicação iniciar.

bash
# terminal
# Start all services
docker compose up -d

# View logs from all services
docker compose logs -f

# Logs from a specific service
docker compose logs -f app

# Stop and remove containers
docker compose down

# Removal including volumes (caution: data loss)
docker compose down -v

# Rebuild after Dockerfile changes
docker compose up -d --build

Pronto para mandar bem nas entrevistas de DevOps?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Gerenciamento de segredos e variáveis de ambiente

O gerenciamento seguro de segredos é crucial em produção. Docker oferece várias abordagens dependendo do contexto.

yaml
# docker-compose.override.yml (development only)
version: "3.9"

services:
  app:
    env_file:
      - .env.development
    environment:
      - DEBUG=true

Para produção, Docker secrets oferece maior segurança.

yaml
# docker-compose.production.yml
version: "3.9"

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.production
    secrets:
      - db_password
      - api_key
    environment:
      - NODE_ENV=production
      - DATABASE_PASSWORD_FILE=/run/secrets/db_password
      - API_KEY_FILE=/run/secrets/api_key

secrets:
  db_password:
    file: ./secrets/db_password.txt
  api_key:
    file: ./secrets/api_key.txt

O código da aplicação lê os segredos a partir de arquivos montados.

config/secrets.jsjavascript
const fs = require('fs');
const path = require('path');

// Utility function to read Docker secrets
function readSecret(secretName) {
  const secretPath = `/run/secrets/${secretName}`;

  // Check if secret file exists
  if (fs.existsSync(secretPath)) {
    return fs.readFileSync(secretPath, 'utf8').trim();
  }

  // Fallback to classic environment variables
  const envVar = secretName.toUpperCase();
  return process.env[envVar];
}

module.exports = {
  databasePassword: readSecret('db_password'),
  apiKey: readSecret('api_key'),
};

Essa abordagem evita expor segredos em variáveis de ambiente ou imagens Docker.

Otimização de imagens Docker

Várias técnicas reduzem o tamanho da imagem e melhoram o desempenho.

dockerfile
# Dockerfile.optimized
FROM node:22-alpine AS base

# Install necessary tools in a single layer
RUN apk add --no-cache \
    dumb-init \
    && rm -rf /var/cache/apk/*

# ============================================
# Stage: Dependencies
# ============================================
FROM base AS deps

WORKDIR /app

# Copy only lock files for caching
COPY package.json package-lock.json ./

# Install with mounted npm cache (BuildKit)
RUN --mount=type=cache,target=/root/.npm \
    npm ci --only=production

# ============================================
# Stage: Builder
# ============================================
FROM base AS builder

WORKDIR /app

COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci

COPY . .
RUN npm run build

# ============================================
# Stage: Production
# ============================================
FROM base AS production

# Image metadata
LABEL maintainer="team@example.com"
LABEL version="1.0"
LABEL description="Production-ready Node.js application"

# Non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001

WORKDIR /app

# Copy production dependencies
COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules

# Copy build output
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/package.json ./

USER nodejs

ENV NODE_ENV=production

# dumb-init as PID 1 for signal handling
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/server.js"]

O uso do dumb-init garante o tratamento correto de sinais Unix, permitindo o desligamento gracioso do contêiner.

bash
# terminal
# Enable BuildKit for advanced features
export DOCKER_BUILDKIT=1

# Build with cache and detailed output
docker build --progress=plain -t my-app:optimized .

# Analyze image layers
docker history my-app:optimized

# Detailed image inspection
docker inspect my-app:optimized
Segurança de imagens

Recomenda-se escanear regularmente as imagens em busca de vulnerabilidades usando ferramentas como Trivy ou Snyk. As imagens base devem ser atualizadas periodicamente para incluir patches de segurança.

Redes avançadas no Docker

Docker oferece vários drivers de rede para diferentes casos de uso.

yaml
# docker-compose.networking.yml
version: "3.9"

services:
  # Publicly accessible frontend
  frontend:
    build: ./frontend
    ports:
      - "80:80"
    networks:
      - frontend-network
      - backend-network

  # API accessible only from frontend
  api:
    build: ./api
    networks:
      - backend-network
      - database-network
    # No external ports exposed

  # Isolated database
  database:
    image: postgres:16-alpine
    networks:
      - database-network
    # Accessible only by API

networks:
  frontend-network:
    driver: bridge
  backend-network:
    driver: bridge
    internal: true  # No internet access
  database-network:
    driver: bridge
    internal: true

Essa configuração isola os serviços seguindo o princípio do menor privilégio. O banco de dados é acessível apenas pela API.

bash
# terminal
# Inspect Docker networks
docker network ls

# Details of a specific network
docker network inspect app-network

# Create a custom network
docker network create --driver bridge --subnet 172.28.0.0/16 custom-network

# Connect a container to an existing network
docker network connect custom-network my-container

Volumes e persistência de dados

Volumes Docker preservam dados além do ciclo de vida do contêiner.

yaml
# docker-compose.volumes.yml
version: "3.9"

services:
  app:
    image: my-app:latest
    volumes:
      # Named volume for persistent data
      - app_data:/app/data
      # Bind mount for development
      - ./uploads:/app/uploads:rw
      # Read-only mount for configuration
      - ./config:/app/config:ro

  backup:
    image: alpine
    volumes:
      # Access same volume for backups
      - app_data:/data:ro
      - ./backups:/backups
    command: |
      sh -c "tar czf /backups/backup-$$(date +%Y%m%d).tar.gz /data"

volumes:
  app_data:
    driver: local
    driver_opts:
      type: none
      device: /path/to/host/data
      o: bind

A distinção entre volumes nomeados e bind mounts é importante: volumes são gerenciados pelo Docker enquanto bind mounts usam diretamente o sistema de arquivos do host.

bash
# terminal
# List volumes
docker volume ls

# Inspect a volume
docker volume inspect app_data

# Remove orphaned volumes
docker volume prune

# Backup a volume
docker run --rm -v app_data:/data -v $(pwd):/backup alpine \
  tar czf /backup/volume-backup.tar.gz /data

Deploy em produção

Um fluxo de deploy robusto inclui build, testes e push para um registro.

bash
# deploy.sh
#!/bin/bash
set -e

# Variables
REGISTRY="registry.example.com"
IMAGE_NAME="my-app"
VERSION=$(git describe --tags --always)

echo "Building version: $VERSION"

# Build production image
docker build \
  -f Dockerfile.production \
  -t $REGISTRY/$IMAGE_NAME:$VERSION \
  -t $REGISTRY/$IMAGE_NAME:latest \
  --build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
  --build-arg VERSION=$VERSION \
  .

# Security scan
echo "Running security scan..."
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy image $REGISTRY/$IMAGE_NAME:$VERSION

# Push to registry
echo "Pushing to registry..."
docker push $REGISTRY/$IMAGE_NAME:$VERSION
docker push $REGISTRY/$IMAGE_NAME:latest

echo "Deployment complete: $REGISTRY/$IMAGE_NAME:$VERSION"

Para deploys em servidor, um arquivo compose de produção separado adapta a configuração.

yaml
# docker-compose.prod.yml
version: "3.9"

services:
  app:
    image: registry.example.com/my-app:latest
    restart: always
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: "0.5"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 256M
      update_config:
        parallelism: 1
        delay: 10s
        failure_action: rollback
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Essa configuração define alocação de recursos, estratégia de atualização e healthchecks para um deploy confiável.

bash
# terminal
# Production deployment with Docker Compose
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# Zero-downtime update (rolling update)
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps app

# Rollback if issues occur
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps \
  --scale app=0 && \
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps app

Monitoramento e depuração de contêineres

O monitoramento de contêineres é essencial em produção.

bash
# terminal
# Real-time statistics for all containers
docker stats

# Statistics for a specific container with custom format
docker stats my-app --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

# Inspect processes in a container
docker top my-app

# Real-time Docker events
docker events --filter container=my-app

# Copy files from/to a container
docker cp my-app:/app/logs/error.log ./error.log

Para depuração avançada, várias técnicas estão disponíveis.

bash
# terminal
# Interactive shell in a running container
docker exec -it my-app sh

# Execute a single command
docker exec my-app cat /app/config/settings.json

# Start a container in debug mode
docker run -it --rm --entrypoint sh my-app:latest

# Inspect environment variables
docker exec my-app printenv

# Analyze logs with filters
docker logs my-app --since 1h --tail 100 | grep ERROR
yaml
# docker-compose.monitoring.yml
version: "3.9"

services:
  app:
    # ... existing configuration
    labels:
      - "prometheus.scrape=true"
      - "prometheus.port=3000"
      - "prometheus.path=/metrics"

  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=15d'

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3001:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=secret

volumes:
  prometheus_data:
  grafana_data:

Esse stack de monitoramento permite coletar e visualizar métricas de contêineres.

Conclusão

Docker transforma o ciclo de desenvolvimento ao garantir consistência em todos os ambientes. A conteinerização traz portabilidade, isolamento e reprodutibilidade—qualidades essenciais para aplicações modernas.

Checklist Docker para produção

  • ✅ Builds multi-stage para imagens otimizadas
  • ✅ Usuário não-root nos contêineres
  • ✅ Healthchecks configurados para todos os serviços
  • ✅ Segredos gerenciados via Docker secrets ou variáveis de ambiente seguras
  • ✅ Limites de recursos (CPU, memória) definidos
  • ✅ Volumes para persistência de dados críticos
  • ✅ Logging centralizado com rotação de arquivos
  • ✅ Escaneamento de segurança de imagens antes do deploy
  • ✅ Estratégia de atualização sem downtime
  • ✅ Rede isolada entre serviços

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Dominar Docker é uma habilidade fundamental para todo desenvolvedor moderno. Do ambiente local ao deploy em produção, Docker padroniza fluxos de trabalho e simplifica operações. Os conceitos apresentados aqui formam uma base sólida para explorar Kubernetes e a orquestração de contêineres em larga escala.

Tags

#docker
#containerization
#devops
#docker compose
#deployment

Compartilhar

Artigos relacionados