CI/CD (Continuous Integration e Continuous Delivery) e a pratica de automatizar a integracao, teste e entrega de software. Para projetos Kotlin, um pipeline de CI/CD bem configurado garante que cada mudanca de codigo seja compilada, testada e, quando aprovada, entregue automaticamente ao ambiente de producao. Neste guia, vamos configurar pipelines completos usando GitHub Actions, GitLab CI e Jenkins, cobrindo desde testes unitarios ate deploy em ambientes de producao com Docker e Kubernetes.

Fundamentos de CI/CD

O ciclo basico de CI/CD para um projeto Kotlin envolve: compilar o codigo, executar testes unitarios e de integracao, analisar qualidade de codigo, gerar artefatos (JAR, Docker image) e fazer deploy. Cada etapa e automatizada e executada em sequencia, com falhas bloqueando etapas subsequentes.

Um bom pipeline segue o principio de fail fast: testes rapidos rodam primeiro para dar feedback imediato, enquanto testes mais demorados e deploy ficam em etapas posteriores.

GitHub Actions para Projetos Kotlin

O GitHub Actions e a solucao de CI/CD integrada ao GitHub. Crie o arquivo .github/workflows/ci.yml:

// .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

env:
  GRADLE_OPTS: -Dorg.gradle.daemon=false

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_DB: testdb
          POSTGRES_USER: testuser
          POSTGRES_PASSWORD: testpass
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - name: Configurar JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Cache Gradle
        uses: actions/cache@v4
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: gradle-${{ hashFiles('**/*.gradle.kts') }}
          restore-keys: gradle-

      - name: Dar permissao ao Gradle Wrapper
        run: chmod +x gradlew

      - name: Compilar
        run: ./gradlew build -x test

      - name: Executar testes unitarios
        run: ./gradlew test

      - name: Executar testes de integracao
        run: ./gradlew integrationTest
        env:
          DATABASE_URL: jdbc:postgresql://localhost:5432/testdb
          DATABASE_USER: testuser
          DATABASE_PASSWORD: testpass

      - name: Publicar relatorio de testes
        uses: dorny/test-reporter@v1
        if: always()
        with:
          name: Relatorio de Testes
          path: '**/build/test-results/test/*.xml'
          reporter: java-junit

      - name: Upload artefatos
        uses: actions/upload-artifact@v4
        with:
          name: build-artifacts
          path: build/libs/*.jar

Pipeline de Deploy

// .github/workflows/deploy.yml
name: Deploy Pipeline

on:
  push:
    tags:
      - 'v*'

jobs:
  deploy:
    runs-on: ubuntu-latest
    needs: build-and-test

    steps:
      - uses: actions/checkout@v4

      - name: Configurar JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Build do projeto
        run: ./gradlew build

      - name: Login no Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build e push da imagem Docker
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            meuusuario/meu-app:${{ github.ref_name }}
            meuusuario/meu-app:latest

      - name: Deploy para producao
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            docker pull meuusuario/meu-app:latest
            docker-compose up -d --force-recreate app

GitLab CI para Projetos Kotlin

O GitLab CI usa o arquivo .gitlab-ci.yml na raiz do projeto:

// .gitlab-ci.yml
stages:
  - build
  - test
  - quality
  - package
  - deploy

variables:
  GRADLE_OPTS: "-Dorg.gradle.daemon=false"
  GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"

cache:
  key: "$CI_COMMIT_REF_SLUG"
  paths:
    - .gradle/wrapper
    - .gradle/caches

build:
  stage: build
  image: eclipse-temurin:17-jdk
  script:
    - chmod +x gradlew
    - ./gradlew assemble
  artifacts:
    paths:
      - build/libs/*.jar

unit-tests:
  stage: test
  image: eclipse-temurin:17-jdk
  script:
    - ./gradlew test
  artifacts:
    when: always
    reports:
      junit: build/test-results/test/*.xml

integration-tests:
  stage: test
  image: eclipse-temurin:17-jdk
  services:
    - postgres:16
  variables:
    POSTGRES_DB: testdb
    POSTGRES_USER: testuser
    POSTGRES_PASSWORD: testpass
    DATABASE_URL: "jdbc:postgresql://postgres:5432/testdb"
  script:
    - ./gradlew integrationTest

code-quality:
  stage: quality
  image: eclipse-temurin:17-jdk
  script:
    - ./gradlew detekt
  artifacts:
    paths:
      - build/reports/detekt/

docker-build:
  stage: package
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
  only:
    - tags

deploy-production:
  stage: deploy
  image: alpine
  script:
    - apk add --no-cache openssh-client
    - ssh $SERVER_USER@$SERVER_HOST "docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG && docker-compose up -d"
  only:
    - tags
  when: manual

Configuracao do Gradle para CI

Otimize o build do Gradle para ambientes de CI:

// build.gradle.kts
tasks.test {
    useJUnitPlatform()

    // Relatorios para CI
    reports {
        junitXml.required.set(true)
        html.required.set(true)
    }

    // Testes em paralelo
    maxParallelForks = Runtime.getRuntime().availableProcessors() / 2
}

// Task separada para testes de integracao
val integrationTest by tasks.registering(Test::class) {
    description = "Executa testes de integracao"
    group = "verificacao"

    testClassesDirs = sourceSets["integrationTest"].output.classesDirs
    classpath = sourceSets["integrationTest"].runtimeClasspath

    useJUnitPlatform()
    shouldRunAfter(tasks.test)
}

// Analise de codigo com Detekt
plugins {
    id("io.gitlab.arturbosch.detekt") version "1.23.4"
}

detekt {
    config.setFrom("$rootDir/config/detekt/detekt.yml")
    buildUponDefaultConfig = true
    allRules = false
}

Analise de Qualidade de Codigo

Integre ferramentas de analise estatica no pipeline:

// Detekt - Analise estatica para Kotlin
// config/detekt/detekt.yml
complexity:
  LongMethod:
    threshold: 30
  ComplexCondition:
    threshold: 4
  TooManyFunctions:
    thresholdInFiles: 15

style:
  MagicNumber:
    ignoreNumbers:
      - '-1'
      - '0'
      - '1'
      - '2'
  MaxLineLength:
    maxLineLength: 120

// Ktlint - Formatacao de codigo
plugins {
    id("org.jlleitschuh.gradle.ktlint") version "12.0.3"
}

ktlint {
    android.set(true)
    outputToConsole.set(true)
    reporters {
        reporter(ReporterType.CHECKSTYLE)
    }
}

Seguranca no Pipeline

Proteja secrets e credenciais no pipeline:

// Nunca hardcode credenciais. Use variaveis de ambiente:
// GitHub: Settings > Secrets and variables > Actions
// GitLab: Settings > CI/CD > Variables

// No codigo Kotlin, leia do ambiente:
val databaseUrl = System.getenv("DATABASE_URL")
    ?: "jdbc:postgresql://localhost:5432/devdb"

val databaseUser = System.getenv("DATABASE_USER")
    ?: "devuser"

Boas Praticas de CI/CD para Kotlin

  • Fail fast: coloque testes unitarios antes de testes de integracao no pipeline. Feedback rapido e essencial.
  • Cache de dependencias: configure cache do Gradle para evitar download de dependencias a cada build.
  • Builds reproduziveis: use versoes fixas de JDK, Gradle e dependencias. Evite latest em imagens Docker.
  • Testes em paralelo: configure maxParallelForks no Gradle para aproveitar multiplos cores.
  • Branch protection: exija que o pipeline passe antes de permitir merge em branches protegidas.
  • Deploy gradual: use estrategias como blue-green ou canary para deploys seguros.
  • Monitore o pipeline: acompanhe metricas como tempo de build, taxa de falha e cobertura de testes.

Erros Comuns e Armadilhas

  • Pipeline lento sem cache: sem cache de Gradle, cada build baixa todas as dependencias novamente, adicionando minutos ao pipeline.
  • Testes flakey: testes que falham intermitentemente corroem a confianca no pipeline. Investigue e corrija testes instáveis imediatamente.
  • Secrets expostos em logs: cuidado com comandos que imprimem variaveis de ambiente. Use mascaramento de secrets.
  • Falta de testes de integracao: confiar apenas em testes unitarios nao garante que componentes funcionem juntos.
  • Deploy manual como unica opcao: se o deploy depende de um processo manual, nao e CD verdadeiro. Automatize o maximo possivel.
  • Ignorar falhas do pipeline: nunca faça merge com pipeline falhando. Corrija antes de prosseguir.

Conclusao e Proximos Passos

Um pipeline de CI/CD robusto e o alicerce de entregas confiaveis e frequentes. Com GitHub Actions ou GitLab CI, voce pode automatizar todo o ciclo de vida do seu projeto Kotlin, desde a compilacao ate o deploy em producao. Para aprofundar, explore nossos guias sobre Docker para containerizacao, microservicos para arquitetura distribuida e testes para maximizar a cobertura do seu pipeline. A automacao bem feita libera tempo para o que realmente importa: escrever codigo de qualidade.