A CI/CD pipeline is the delivery system for your software. Its quality directly determines how fast your team can ship, how confidently they can deploy, and how quickly they can recover when something goes wrong. A poorly designed pipeline — one that is slow, flaky, or missing quality gates — destroys team velocity just as effectively as bad code.Um pipeline de CI/CD é o sistema de entrega do seu software. Sua qualidade determina diretamente a velocidade com que sua equipe consegue entregar, a confiança no deploy e a velocidade de recuperação quando algo dá errado. Um pipeline mal projetado — lento, instável ou sem gates de qualidade — destrói a velocidade da equipe tão efetivamente quanto código ruim.
This article covers the design of production-grade CI/CD pipelines for backend Java systems: pipeline stages, quality gates, Docker image build, artifact management, GitOps, and deployment strategies.Este artigo cobre o design de pipelines CI/CD de nível produtivo para sistemas Java backend: etapas do pipeline, gates de qualidade, build de imagem Docker, gerenciamento de artefatos, GitOps e estratégias de deploy.
The Pipeline as a Quality FunnelO Pipeline como Funil de Qualidade
Think of a CI/CD pipeline as a funnel that filters out defects as early and cheaply as possible. The earlier a problem is caught, the lower the cost to fix it. A commit that fails a unit test costs minutes; the same defect found in production costs hours of incident response and potential customer impact.Pense em um pipeline CI/CD como um funil que filtra defeitos o mais cedo e barato possível. Quanto mais cedo um problema é detectado, menor o custo de correção. Um commit que falha em um teste unitário custa minutos; o mesmo defeito encontrado em produção custa horas de resposta a incidentes e impacto nos clientes.
Stage 1: Build and Unit TestsEtapa 1: Build e Testes Unitarios
The first stage must be fast — ideally under 3 minutes. This is the feedback loop developers interact with on every commit. Slow feedback leads to context switching, batched commits, and harder debugging.A primeira etapa deve ser rápida — idealmente menos de 3 minutos. Este é o ciclo de feedback com o qual os desenvolvedores interagem em cada commit. Feedback lento leva a troca de contexto, commits em lote e depuração mais difícil.
# Jenkinsfile — Stage 1: Build and unit tests
stage('Build & Unit Tests') {
agent { docker { image 'maven:3.9-eclipse-temurin-21' } }
steps {
sh 'mvn clean verify -Punit-tests --no-transfer-progress'
junit 'target/surefire-reports/**/*.xml'
recordCoverage(tools: [jacoco()])
}
post {
always {
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}
}Speed technique: Separate unit tests (no I/O, no Spring context) from integration tests at the Maven profile level. Unit tests run in Stage 1 in under 2 minutes. Integration tests run in Stage 3 against real infrastructure. Never mix them.Técnica de velocidade: Separe os testes unitários (sem I/O, sem contexto Spring) dos testes de integração no nível do profile Maven. Testes unitários rodam na Etapa 1 em menos de 2 minutos. Testes de integração rodam na Etapa 3 contra infraestrutura real. Nunca os misture.
Stage 2: Static Analysis and Security ScanningEtapa 2: Analise Estatica e Varredura de Seguranca
Static analysis is non-negotiable. It catches code smells, security vulnerabilities, and complexity issues before they reach a human reviewer.Análise estática é inegociável. Ela detecta code smells, vulnerabilidades de segurança e problemas de complexidade antes de chegarem a um revisor humano.
stage('Static Analysis') {
steps {
// SonarQube: code quality, coverage thresholds, duplication
withSonarQubeEnv('sonarqube') {
sh 'mvn sonar:sonar -Dsonar.qualitygate.wait=true'
}
// OWASP Dependency-Check: known CVEs in dependencies
dependencyCheck(
additionalArguments: '--format HTML --format JSON',
odcInstallation: 'OWASP-DC'
)
// Fail pipeline if Quality Gate fails
timeout(time: 5, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}Configure SonarQube quality gates with hard thresholds: coverage must not drop below 80%, no new critical or blocker issues, duplication under 3%. The pipeline fails if any threshold is violated — not warns, fails.Configure os quality gates do SonarQube com limites rígidos: cobertura não pode cair abaixo de 80%, nenhum novo issue crítico ou bloqueador, duplicação abaixo de 3%. O pipeline falha se qualquer limite for violado — não avisa, falha.
Stage 3: Integration Tests with TestcontainersEtapa 3: Testes de Integracao com Testcontainers
Integration tests verify that your application works correctly with real infrastructure: real database, real message broker, real cache. Testcontainers spins up Docker containers as part of the test run — no shared test databases, no environment-specific configuration, fully reproducible.Testes de integração verificam que sua aplicação funciona corretamente com infraestrutura real: banco de dados real, message broker real, cache real. O Testcontainers sobe containers Docker como parte da execução dos testes — sem bancos de teste compartilhados, sem configuração específica de ambiente, totalmente reproduzível.
// Spring Boot integration test with Testcontainers
@SpringBootTest
@Testcontainers
class OrderRepositoryIT {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine");
@Container
static RabbitMQContainer rabbitMQ =
new RabbitMQContainer("rabbitmq:3.13-management");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
registry.add("spring.rabbitmq.host", rabbitMQ::getHost);
registry.add("spring.rabbitmq.port", rabbitMQ::getAmqpPort);
}
@Test
void shouldPersistAndRetrieveOrder() {
// Test against a real PostgreSQL container
}
}Stage 4: Docker Image BuildEtapa 4: Build da Imagem Docker
Build the image only after all quality gates pass. Use multi-stage builds to minimize the final image size and reduce the attack surface.Construa a imagem somente após todos os gates de qualidade passarem. Use builds multi-stage para minimizar o tamanho final da imagem e reduzir a superfície de ataque.
# Multi-stage Dockerfile — builds in CI, runs lean in production
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -q # cache dependencies layer
COPY src ./src
RUN mvn package -DskipTests -q
FROM eclipse-temurin:21-jre-alpine # ~200MB vs ~600MB full JDK
WORKDIR /app
# Non-root user for security
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
COPY --from=builder /build/target/app.jar app.jar
ENV JAVA_OPTS="-XX:MaxRAMPercentage=75 -XX:+UseContainerSupport"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]stage('Build & Push Image') {
steps {
script {
def image = docker.build("registry/order-service:${GIT_COMMIT}")
docker.withRegistry('https://registry.example.com', 'registry-creds') {
image.push()
// Also tag as latest for non-prod environments
image.push('latest')
}
}
}
}Secrets Management in PipelinesGerenciamento de Segredos nos Pipelines
A pipeline that builds Docker images, deploys to Kubernetes, and calls external APIs needs credentials: registry passwords, database URLs, API keys, signing certificates. How those secrets reach the pipeline is one of the most commonly neglected security decisions in CI/CD.Um pipeline que constrói imagens Docker, faz deploy no Kubernetes e chama APIs externas precisa de credenciais: senhas de registry, URLs de banco de dados, chaves de API, certificados de assinatura. Como esses segredos chegam ao pipeline é uma das decisões de segurança mais negligenciadas em CI/CD.
Hard rule: Secrets must never appear in source code, Dockerfiles, build scripts, or commit history — not even in private repositories. Leaked secrets in git history account for a significant share of production security incidents. Use git-secrets or pre-commit hooks to catch accidental commits before they reach the remote.Regra inflexível: Segredos nunca devem aparecer em código-fonte, Dockerfiles, scripts de build ou histórico de commits — nem mesmo em repositórios privados. Segredos vazados no histórico do git são responsáveis por uma parcela significativa de incidentes de segurança em produção. Use git-secrets ou pre-commit hooks para detectar commits acidentais antes que cheguem ao repositório remoto.
CI Platform Encrypted Secrets (Baseline)Segredos Cifrados da Plataforma CI (Linha de Base)
GitHub Actions, GitLab CI, and Jenkins all offer encrypted secret stores. Secrets are injected as environment variables at runtime — never printed in logs, never written to disk. This is the minimum acceptable baseline for any pipeline.GitHub Actions, GitLab CI e Jenkins oferecem armazenamentos de segredos cifrados. Os segredos são injetados como variáveis de ambiente em tempo de execução — nunca impressos em logs, nunca escritos em disco. Este é o mínimo aceitável para qualquer pipeline.
# GitHub Actions — secrets injected at runtime, never visible in logs
jobs:
build:
steps:
- name: Build and push Docker image
env:
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
echo "$REGISTRY_PASSWORD" | docker login registry.example.com -u ci --password-stdin
mvn sonar:sonar -Dsonar.token=$SONAR_TOKENHashiCorp Vault (Production Grade)HashiCorp Vault (Nível de Produção)
For production systems, use a dedicated secrets manager. Vault provides dynamic secrets — database credentials that are generated for a specific pipeline run and expire automatically when the stage finishes. The pipeline authenticates to Vault using a short-lived token (AppRole or Kubernetes auth) and retrieves only the secrets it needs for that run.Para sistemas em produção, use um gerenciador de segredos dedicado. O Vault fornece segredos dinâmicos — credenciais de banco de dados geradas para uma execução específica do pipeline e que expiram automaticamente quando a etapa termina. O pipeline se autentica no Vault usando um token de curta duração (AppRole ou autenticação Kubernetes) e recupera apenas os segredos necessários para aquela execução.
stage('Build') {
steps {
withVault(
vaultSecrets: [[
path: 'secret/ci/order-service',
secretValues: [
[envVar: 'DB_PASSWORD', vaultKey: 'db_password'],
[envVar: 'STRIPE_API_KEY', vaultKey: 'stripe_key']
]
]]
) {
sh 'mvn clean verify'
// DB_PASSWORD and STRIPE_API_KEY available as env vars
// credentials are revoked when the stage completes
}
}
}Kubernetes Secrets at RuntimeKubernetes Secrets em Runtime
Secrets that the running application needs (not the build pipeline) should live as Kubernetes Secrets — ideally as Sealed Secrets (Bitnami) or synced from Vault via the Vault Agent Injector. Mount them as files rather than environment variables: processes that crash dump their environment to logs, which can inadvertently expose secrets.Segredos que a aplicação em execução precisa (não o pipeline de build) devem viver como Kubernetes Secrets — idealmente como Sealed Secrets (Bitnami) ou sincronizados do Vault via Vault Agent Injector. Monte-os como arquivos em vez de variáveis de ambiente: processos que travam fazem dump do ambiente para os logs, o que pode expor segredos inadvertidamente.
Audit trail: Vault logs every secret access with a timestamp, pipeline identity, and the exact secret path. When a credential is compromised, you know immediately which pipeline accessed it and when — without that audit trail, incident response is archaeology.Trilha de auditoria: O Vault registra cada acesso a segredos com timestamp, identidade do pipeline e o caminho exato do segredo. Quando uma credencial é comprometida, você sabe imediatamente qual pipeline a acessou e quando — sem essa trilha de auditoria, a resposta a incidentes vira arqueologia.
GitOps: Separating Application and Infrastructure ReposGitOps: Separando Repositorios de Aplicacao e Infraestrutura
In a GitOps model, the desired state of the infrastructure is stored in a Git repository (the "config repo"), separate from the application code repo. The pipeline's job is to update the config repo with the new image tag — not to apply Kubernetes manifests directly.No modelo GitOps, o estado desejado da infraestrutura é armazenado em um repositório Git (o "config repo"), separado do repositório de código da aplicação. O trabalho do pipeline é atualizar o config repo com a nova tag de imagem — não aplicar os manifests do Kubernetes diretamente.
# Pipeline updates the config repo (GitOps model)
stage('Update Config Repo') {
steps {
withCredentials([sshUserPrivateKey(credentialsId: 'config-repo-key')]) {
sh """
git clone git@github.com:org/k8s-config.git
cd k8s-config
# Update image tag in the staging overlay
sed -i 's|order-service:.*|order-service:${GIT_COMMIT}|' \\
overlays/staging/kustomization.yaml
git add -A
git commit -m "chore: bump order-service to ${GIT_COMMIT}"
git push origin main
"""
}
}
}
# ArgoCD or Flux detects the change and applies it to the clusterArgoCD (or Flux) watches the config repo and applies changes to the cluster automatically. This means the cluster state is always auditable — every deployment is a commit with a timestamp, author, and diff. Rollback is a git revert.O ArgoCD (ou Flux) monitora o config repo e aplica as mudanças ao cluster automaticamente. Isso significa que o estado do cluster é sempre auditável — cada deploy é um commit com timestamp, autor e diff. Rollback é um git revert.
Deployment StrategiesEstrategias de Deploy
Blue/GreenBlue/Green
Run two identical environments. The load balancer points at blue (production). Deploy the new version to green, run smoke tests, then switch the load balancer. Rollback is instant — flip back to blue. Best for high-risk releases where rollback speed is critical.Execute dois ambientes idênticos. O load balancer aponta para o blue (produção). Faça o deploy da nova versão no green, execute smoke tests e então troque o load balancer. O rollback é instantâneo — volta para o blue. Melhor para releases de alto risco onde a velocidade de rollback é crítica.
Rolling UpdateRolling Update
Kubernetes default: gradually replace old pods with new ones. Zero downtime, no extra infrastructure cost. Rollback is slower than blue/green. Requires the new version to be backward-compatible with the database schema during the transition window.Padrão do Kubernetes: substituir gradualmente os pods antigos pelos novos. Zero downtime, sem custo extra de infraestrutura. O rollback é mais lento que o blue/green. Requer que a nova versão seja retrocompatível com o schema do banco durante a janela de transição.
Canary ReleaseCanary Release
Route a small percentage of traffic to the new version. Monitor error rate, latency, and business metrics. Gradually increase the percentage. Abort and roll back if metrics degrade. Best for high-traffic systems where catching regressions on real traffic before full rollout is worth the complexity.Direcione uma pequena porcentagem do tráfego para a nova versão. Monitore taxa de erros, latência e métricas de negócio. Aumente gradualmente a porcentagem. Aborte e faça rollback se as métricas piorarem. Melhor para sistemas de alto tráfego onde detectar regressões em tráfego real antes do rollout completo vale a complexidade.
Rule of thumb: Use rolling updates for routine releases. Use blue/green for major version changes or database migrations. Use canary for critical user-facing services where a regression at 5% traffic is far preferable to a regression at 100%.Regra geral: Use rolling updates para releases rotineiras. Use blue/green para mudanças de versão major ou migrações de banco de dados. Use canary para serviços críticos voltados ao usuário, onde uma regressão em 5% do tráfego é muito preferível a uma regressão em 100%.
Pipeline Health MetricsMetricas de Saude do Pipeline
Treat the pipeline itself as a production system. Track these DORA metrics to measure and improve delivery performance:Trate o pipeline como um sistema de produção. Acompanhe essas métricas DORA para medir e melhorar a performance de entrega:
- Deployment Frequency: How often do you deploy to production? High performers deploy multiple times per day.Frequência de Deploy: Com que frequência você faz deploy em produção? Times de alta performance fazem deploy múltiplas vezes por dia.
- Lead Time for Changes: Time from commit to production. Target: under 1 hour for routine changes.Lead Time para Mudanças: Tempo do commit até produção. Meta: menos de 1 hora para mudanças rotineiras.
- Change Failure Rate: Percentage of deployments that cause incidents. Target: under 5%.Taxa de Falha em Mudanças: Porcentagem de deploys que causam incidentes. Meta: abaixo de 5%.
- Mean Time to Recovery (MTTR): Time to restore service after an incident. Target: under 1 hour.Tempo Médio de Recuperação (MTTR): Tempo para restaurar o serviço após um incidente. Meta: menos de 1 hora.
Anti-pattern: A pipeline that nobody trusts. If developers bypass the pipeline ("I'll just deploy manually this once"), the pipeline has failed at its job — either it's too slow, too flaky, or it's blocking legitimate changes with false positives. Fix the pipeline, not the bypass.Anti-pattern: Um pipeline em que ninguém confia. Se os desenvolvedores contornam o pipeline ("vou fazer o deploy manual só dessa vez"), o pipeline falhou no seu objetivo — está lento, instável ou bloqueando mudanças legítimas com falsos positivos. Corrija o pipeline, não o desvio.
Key TakeawaysPontos Principais
- Design the pipeline as a quality funnel — catch defects as early and cheaply as possible.Projete o pipeline como um funil de qualidade — detecte defeitos o mais cedo e barato possível.
- Keep Stage 1 (build + unit tests) under 3 minutes. Separate unit from integration tests by Maven profile.Mantenha a Etapa 1 (build + testes unitários) abaixo de 3 minutos. Separe testes unitários dos de integração por profile Maven.
- Use Testcontainers for integration tests — real infrastructure, fully reproducible, no shared test databases.Use Testcontainers para testes de integração — infraestrutura real, totalmente reproduzível, sem bancos de teste compartilhados.
- Use multi-stage Docker builds to minimize image size and attack surface.Use builds Docker multi-stage para minimizar o tamanho da imagem e a superfície de ataque.
- Adopt GitOps: the pipeline updates a config repo; a CD tool (ArgoCD/Flux) applies to the cluster. Every deployment is auditable and reversible.Adote GitOps: o pipeline atualiza um config repo; uma ferramenta de CD (ArgoCD/Flux) aplica ao cluster. Todo deploy é auditável e reversível.
- Choose deployment strategy by risk: rolling for routine, blue/green for migrations, canary for critical paths.Escolha a estratégia de deploy pelo risco: rolling para rotina, blue/green para migrações, canary para caminhos críticos.
- Never store secrets in source code or commit history. Use the CI platform's encrypted secret store as a baseline; use Vault with dynamic credentials for production pipelines.Nunca armazene segredos em código-fonte ou histórico de commits. Use o armazenamento de segredos cifrados da plataforma CI como linha de base; use o Vault com credenciais dinâmicas para pipelines em produção.
- Track DORA metrics — they reveal systemic bottlenecks that code reviews alone cannot surface.Acompanhe métricas DORA — elas revelam gargalos sistêmicos que revisões de código sozinhas não conseguem expor.