Construindo pipelines DevOps com Docker e entrega contínua

Construindo pipelines DevOps com Docker e entrega contínua
Nota: Este artigo foca em estratégias e ferramentas que permitem transformar o ciclo de vida de software em um fluxo automatizado, confiável e repetível. Não é necessário conhecimento avançado de orquestração de clusters; o objetivo é mostrar como montar a base do pipeline usando Docker e integração contínua.
Introdução
Nos últimos anos, a cultura DevOps se consolidou como o caminho mais eficiente para reduzir o tempo entre a escrita de código e a entrega de valor ao cliente. Dois pilares fundamentais desse movimento são:
Quando combinados, esses elementos criam um fluxo de trabalho onde cada commit pode ser validado, testado e, se aprovado, promovido automaticamente para o próximo estágio. O resultado é maior velocidade, menor risco e maior confiança nas mudanças.
Neste artigo, você vai descobrir:
Como estruturar um Dockerfile enxuto e seguro. Como definir um pipeline de CI/CD usando GitHub Actions (ou outra plataforma similar). Como automatizar o provisionamento de infraestrutura com scripts de linha de comando. Boas práticas para versionamento, controle de qualidade e monitoramento do pipeline.
Vamos colocar a mão na massa!
1. Preparando a base: Dockerfile otimizado
Um Dockerfile bem escrito reduz o tempo de build, diminui a superfície de ataque e facilita a manutenção. A seguir, um exemplo de Dockerfile para uma aplicação Node.js que usa a estratégia de multistage build.
# ---------- Etapa 1: Build ----------
FROM node:20-alpine AS builder
WORKDIR /app
Copiar apenas o package.json e o lock para aproveitar o cache
COPY package.json ./
RUN npm ci --only=production
Copiar o código-fonte e compilar (se houver)
COPY . .
RUN npm run build
---------- Etapa 2: Runtime ----------
FROM node:20-alpine
WORKDIR /app
Copiar apenas os artefatos necessários da etapa anterior
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
Definir variáveis de ambiente seguras
ENV NODE_ENV=production
ENV PORT=3000
Expor a porta da aplicação
EXPOSE 3000
Usuário não-root para segurança
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
Comando de inicialização
CMD ["node", "dist/index.js"]
Por que esse Dockerfile funciona bem?
| Boa prática | Benefício |
|---|---|
| Multistage | Reduz o tamanho final da imagem ao descartar arquivos de compilação. |
| Cache de dependências | package.json é copiado antes do código-fonte, permitindo que o npm ci seja reutilizado entre builds. |
| Usuário não-root | Minimiza riscos caso a aplicação seja comprometida. |
| Variáveis de ambiente | Facilita a configuração em diferentes ambientes (dev, staging, prod). |
2. Automatizando a integração: workflow de CI/CD
A maioria das equipes usa plataformas como GitHub Actions, GitLab CI ou Azure Pipelines. Vamos criar um workflow simples que:
Atenção: Substitua os placeholders (
,, etc.) pelos valores reais do seu projeto.
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- name: Checkout do código
uses: actions/checkout@v3
- name: Configurar Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
- name: Instalar dependências
run: npm ci
- name: Executar testes
run: npm test
- name: Build da aplicação
run: npm run build
docker-build:
needs: build-test
runs-on: ubuntu-latest
steps:
- name: Checkout do código
uses: actions/checkout@v3
- name: Login no registro de containers
uses: docker/login-action@v2
with:
registry: <YOUR_REGISTRY>
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASS }}
- name: Build e push da imagem
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: <YOUR_REGISTRY>/my-app:${{ github.sha }}
provision-test:
needs: docker-build
runs-on: ubuntu-latest
env:
AWS_REGION: us-east-1
steps:
- name: Checkout do código
uses: actions/checkout@v3
- name: Instalar AWS CLI
run: |
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
- name: Deploy no ambiente de teste
run: |
chmod +x scripts/deploy-test.sh
./scripts/deploy-test.sh ${{ github.sha }}
Explicação das etapas
| Job | Função |
|---|---|
| build-test | Compila o código e garante que a base está estável. |
| docker-build | Constrói a imagem Docker e a envia para o registro, versionada pelo SHA do commit. |
| provision-test | Executa um script que cria (ou atualiza) recursos de teste na nuvem, usando a imagem recém‑publicada. |
3. Provisionamento automático com script de linha de comando
Em vez de usar ferramentas declarativas que contenham os termos proibidos, podemos aproveitar a CLI da nuvem (AWS, Azure ou GCP) para criar recursos de forma programática. O exemplo abaixo demonstra como lançar um cluster de containers e implantar a imagem recém‑gerada usando a AWS CLI.
Pré‑requisitos:
- Credenciais configuradas (
aws configure).- Permissões para criar/gerenciar ECS, ALB e VPC.
#!/usr/bin/env bash
scripts/deploy-test.sh
Uso: ./deploy-test.sh <image-tag>
set -euo pipefail
IMAGE_TAG=$1
CLUSTER_NAME="devops-test-cluster"
SERVICE_NAME="my-app-service"
TASK_FAMILY="my-app-task"
REGION="us-east-1"
1️⃣ Criar (ou garantir) o cluster ECS
aws ecs describe-clusters --clusters $CLUSTER_NAME --region $REGION \
|| aws ecs create-cluster --cluster-name $CLUSTER_NAME --region $REGION
2️⃣ Registrar definição de tarefa
TASK_DEF=$(cat <<EOF
{
"family": "$TASK_FAMILY",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"containerDefinitions": [
{
"name": "my-app",
"image": "<YOUR_REGISTRY>/my-app:$IMAGE_TAG",
"portMappings": [{ "containerPort": 3000, "protocol": "tcp" }],
"essential": true,
"environment": [
{ "name": "NODE_ENV", "value": "production" }
]
}
]
}
EOF
)
aws ecs register-task-definition --cli-input-json "$TASK_DEF" --region $REGION
3️⃣ Criar ou atualizar o serviço
aws ecs describe-services --cluster $CLUSTER_NAME --services $SERVICE_NAME --region $REGION \
|| aws ecs create-service \
--cluster $CLUSTER_NAME \
--service-name $SERVICE_NAME \
--task-definition $TASK_FAMILY \
--desired-count 1 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[subnet-xxxxxx],securityGroups=[sg-xxxxxx],assignPublicIp=ENABLED}" \
--region $REGION
4️⃣ Forçar nova implantação
aws ecs update-service \
--cluster $CLUSTER_NAME \
--service $SERVICE_NAME \
--force-new-deployment \
--region $REGION
echo "✅ Deploy concluído! A aplicação está rodando com a imagem $IMAGE_TAG"
Dicas de segurança
Credenciais – Use secrets da plataforma CI/CD; nunca exponha chaves no código. Políticas de IAM – Conceda apenas as permissões necessárias (princípio do menor privilégio). Variáveis de ambiente – Mantenha segredos (tokens, chaves) fora da imagem, usando o mecanismo de injeção da nuvem.
4. Boas práticas para um pipeline resiliente
| Área | Recomendações |
|---|---|
| Versionamento | Tagueie imagens com o SHA do commit e, opcionalmente, com semver (v1.2.3). |
| Testes |


