CI/CD é o conjunto de práticas que transforma um projeto Kotlin em software entregue com previsibilidade: cada mudança compila, roda testes, passa por análise de qualidade, gera artefatos e chega ao ambiente certo sem depender de uma sequência manual frágil. Em Kotlin, isso vale para Android, backend com Ktor ou Spring Boot, bibliotecas multiplataforma, CLIs e serviços que rodam em Docker ou Kubernetes.
Este guia foi atualizado para 2026 com um foco prático: montar pipelines úteis em GitHub Actions e GitLab CI, configurar Gradle para ambientes de integração contínua, encaixar testes, detekt, ktlint, cache, Docker e deploy sem transformar o pipeline em um labirinto. Se você ainda está escolhendo a stack do projeto, leia também o guia de Kotlin para backend, o artigo de Kotlin com Spring Boot e o material de detekt e ktlint em Kotlin.
O que um pipeline Kotlin precisa fazer
Um pipeline Kotlin saudável costuma ter sete etapas:
- Preparar o ambiente com JDK, Gradle Wrapper e cache.
- Compilar o projeto para detectar erros rápidos.
- Executar testes unitários com JUnit, Kotest ou outra stack.
- Executar testes de integração quando houver banco, fila, API externa ou container.
- Verificar qualidade com detekt, ktlint e, quando fizer sentido, análise de cobertura.
- Gerar artefatos: JAR, imagem Docker, APK, AAB ou pacote multiplataforma.
- Publicar ou fazer deploy apenas quando as etapas anteriores passarem.
O princípio é simples: falhar cedo, explicar bem a falha e evitar que código ruim chegue ao branch principal. O pipeline não precisa ser perfeito no primeiro dia. Ele precisa ser confiável o bastante para o time confiar nele.
GitHub Actions para projetos Kotlin
O GitHub Actions é uma boa escolha para projetos hospedados no GitHub e funciona muito bem com Gradle. Um pipeline inicial pode compilar, testar, rodar detekt e ktlint em pull requests e pushes para main.
# .github/workflows/ci.yml
name: Kotlin CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: kotlin-ci-${{ github.ref }}
cancel-in-progress: true
env:
GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.jvmargs=-Xmx2g
jobs:
build-test-quality:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: app_test
POSTGRES_USER: app
POSTGRES_PASSWORD: app
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
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Configurar Gradle
uses: gradle/actions/setup-gradle@v4
- name: Permitir Gradle Wrapper
run: chmod +x ./gradlew
- name: Compilar
run: ./gradlew assemble
- name: Testes unitários
run: ./gradlew test
- name: Testes de integração
run: ./gradlew integrationTest
env:
DATABASE_URL: jdbc:postgresql://localhost:5432/app_test
DATABASE_USER: app
DATABASE_PASSWORD: app
- name: Qualidade de código
run: ./gradlew ktlintCheck detekt
- name: Publicar relatórios de teste
if: always()
uses: actions/upload-artifact@v4
with:
name: test-reports
path: |
**/build/reports/tests/
**/build/test-results/
**/build/reports/detekt/
Para Android, a estrutura é parecida, mas você normalmente usa JDK 17 ou 21, configura o Android Gradle Plugin e roda tarefas como ./gradlew testDebugUnitTest, ./gradlew lintDebug e, em pipelines mais completos, testes instrumentados em emulador. Para bibliotecas Kotlin Multiplatform, separe jobs por alvo: JVM em Linux, iOS em macOS e JS/Wasm quando aplicável.
Deploy com GitHub Actions
O deploy não deve acontecer em qualquer push. Uma regra comum é publicar apenas quando uma tag v* é criada ou quando o branch main passa por revisão.
# .github/workflows/deploy.yml
name: Deploy
on:
push:
tags:
- 'v*'
jobs:
docker-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- uses: gradle/actions/setup-gradle@v4
- name: Build do JAR
run: ./gradlew clean build
- name: Login no registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build e push da imagem
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/sua-org/seu-app:${{ github.ref_name }}
Se o destino for Kubernetes, a etapa seguinte pode atualizar um manifesto, acionar Argo CD, chamar um webhook interno ou aplicar um chart Helm. Para entender melhor esse caminho, veja também Kotlin com Kubernetes.
GitLab CI para Kotlin
GitLab CI é forte quando o repositório, o registry, os ambientes e as regras de deploy vivem dentro do GitLab. Para Kotlin, a diferença principal é a sintaxe do .gitlab-ci.yml, mas a lógica do pipeline continua a mesma.
# .gitlab-ci.yml
stages:
- build
- test
- quality
- package
- deploy
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs=-Xmx2g"
GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .gradle/wrapper
- .gradle/caches
build:
stage: build
image: eclipse-temurin:21-jdk
script:
- chmod +x ./gradlew
- ./gradlew assemble
artifacts:
paths:
- build/libs/*.jar
expire_in: 1 week
unit-tests:
stage: test
image: eclipse-temurin:21-jdk
script:
- ./gradlew test
artifacts:
when: always
reports:
junit: build/test-results/test/*.xml
paths:
- build/reports/tests/
integration-tests:
stage: test
image: eclipse-temurin:21-jdk
services:
- name: postgres:16
alias: postgres
variables:
POSTGRES_DB: app_test
POSTGRES_USER: app
POSTGRES_PASSWORD: app
DATABASE_URL: jdbc:postgresql://postgres:5432/app_test
script:
- ./gradlew integrationTest
quality:
stage: quality
image: eclipse-temurin:21-jdk
script:
- ./gradlew ktlintCheck detekt
artifacts:
when: always
paths:
- build/reports/detekt/
- build/reports/ktlint/
docker-build:
stage: package
image: docker:27
services:
- docker:27-dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
deploy-production:
stage: deploy
image: alpine:3.20
script:
- echo "Acione aqui Helm, Argo CD, SSH ou API interna de deploy"
rules:
- if: '$CI_COMMIT_TAG'
when: manual
GitHub Actions ou GitLab CI: qual escolher para Kotlin?
Para a consulta “GitLab CI vs Kotlin”, a resposta correta é que não existe uma disputa entre a ferramenta de CI e a linguagem. GitLab CI e GitHub Actions são orquestradores de pipeline; Kotlin é o projeto que será compilado, testado e entregue por eles. A decisão real é entre GitLab CI para um repositório Kotlin e GitHub Actions para um repositório Kotlin.
Escolha GitHub Actions quando:
- o código já está no GitHub;
- você quer marketplace grande de actions prontas;
- o time usa pull requests e branch protection do GitHub;
- o deploy é feito para GitHub Packages, GHCR, cloud providers ou serviços externos.
Escolha GitLab CI quando:
- o código está no GitLab;
- você quer registry, environments, approvals e runners no mesmo produto;
- o time precisa de pipelines complexos com regras internas;
- a empresa já opera GitLab Runner próprio.
Para Kotlin em si, ambos resolvem. O ponto crítico é manter o Gradle Wrapper versionado, cachear dependências, separar testes rápidos de testes lentos e rodar qualidade de código antes do deploy. Se você quer exemplos focados no GitHub, veja Kotlin com GitHub Actions.
Configuração do Gradle para CI
O build.gradle.kts deve facilitar o trabalho do pipeline. Não dependa de tarefas locais misteriosas ou scripts que só funcionam na máquina de uma pessoa.
tasks.test {
useJUnitPlatform()
reports {
junitXml.required.set(true)
html.required.set(true)
}
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
}
val integrationTest by tasks.registering(Test::class) {
description = "Executa testes de integração"
group = "verification"
testClassesDirs = sourceSets["integrationTest"].output.classesDirs
classpath = sourceSets["integrationTest"].runtimeClasspath
useJUnitPlatform()
shouldRunAfter(tasks.test)
}
Também vale padronizar versões com version catalog, travar a versão do JDK e evitar latest em imagens Docker. Builds reproduzíveis são mais fáceis de depurar e mais seguros para deploy.
detekt e ktlint no pipeline
Qualidade de código não deve depender de revisão manual. detekt encontra complexidade, problemas de estilo e padrões perigosos; ktlint padroniza formatação. Uma configuração inicial pode ser simples:
plugins {
id("io.gitlab.arturbosch.detekt") version "1.23.8"
id("org.jlleitschuh.gradle.ktlint") version "12.1.1"
}
detekt {
buildUponDefaultConfig = true
allRules = false
config.setFrom("$rootDir/config/detekt/detekt.yml")
}
ktlint {
android.set(false)
outputToConsole.set(true)
}
Em times grandes, evite bloquear tudo no primeiro dia. Crie um baseline, rode as ferramentas no pipeline e vá reduzindo violações por módulo. O artigo Detekt e ktlint em Kotlin: qualidade de código em 2026 mostra esse rollout em mais detalhes.
Secrets, variáveis e segurança
Nunca coloque credenciais no repositório. Use secrets do GitHub, variáveis protegidas do GitLab, vault interno ou secret manager da sua nuvem. No código Kotlin, leia valores do ambiente e mantenha defaults apenas para desenvolvimento local.
val databaseUrl = System.getenv("DATABASE_URL")
?: "jdbc:postgresql://localhost:5432/devdb"
val databaseUser = System.getenv("DATABASE_USER") ?: "devuser"
val databasePassword = System.getenv("DATABASE_PASSWORD") ?: "devpass"
Também cuide dos logs. Um pipeline pode vazar tokens quando imprime comandos com variáveis, executa scripts com set -x ou mostra payloads de deploy. Secrets devem ser mascarados e acessíveis apenas nos jobs que realmente precisam deles.
Boas práticas de CI/CD para Kotlin
- Fail fast: rode compilação e testes unitários antes de etapas caras.
- Cache de Gradle: reduza tempo de build sem esconder problemas de dependência.
- Gradle Wrapper versionado: não dependa do Gradle instalado no runner.
- JDK fixo: use uma versão explícita, como 17 ou 21, alinhada ao projeto.
- Relatórios sempre publicados: quando falhar, o time precisa ver o motivo.
- Separação por ambiente: staging, homologação e produção têm regras diferentes.
- Deploy manual onde existe risco: produção pode exigir aprovação, tag ou janela.
- Observabilidade: publique versão, commit e ambiente para facilitar rollback.
- Qualidade incremental: detekt e ktlint funcionam melhor quando viram hábito, não punição.
Erros comuns
O erro mais frequente é começar pelo deploy e esquecer a base: testes instáveis, cache mal configurado, secrets expostos e tarefas lentas demais. Outro problema é tratar CI/CD como arquivo esquecido: o pipeline precisa evoluir junto com o projeto. Se o projeto ganhou banco, fila ou cache, o pipeline precisa testar esse caminho. Se o projeto virou monólito modular, talvez seja hora de paralelizar módulos.
Também evite transformar o pipeline em uma cerimônia impossível de manter. Um bom pipeline Kotlin é explícito, rápido o suficiente para rodar em pull requests e rígido o suficiente para proteger main.
FAQ rápido
Preciso de GitHub Actions para usar Kotlin?
Não. Você pode usar GitHub Actions, GitLab CI, Jenkins, Buildkite, CircleCI ou qualquer runner que tenha JDK e consiga executar o Gradle Wrapper.
GitLab CI é melhor que GitHub Actions para Kotlin?
Não por causa da linguagem. GitLab CI costuma ser melhor quando a empresa já usa GitLab como plataforma completa. GitHub Actions costuma ser mais natural quando o repositório vive no GitHub e o time quer um ecossistema grande de actions prontas.
Devo rodar detekt e ktlint antes ou depois dos testes?
Em projetos pequenos, pode rodar tudo junto. Em projetos maiores, rode compilação e testes unitários primeiro para feedback rápido, depois qualidade e integração.
CI/CD para Android Kotlin é diferente?
A ideia é a mesma, mas as tarefas mudam: testDebugUnitTest, lintDebug, build de APK/AAB e, quando necessário, testes instrumentados com emulador ou device farm.
Conclusão
CI/CD para Kotlin em 2026 não é apenas um YAML bonito no repositório. É uma rede de segurança para entregar software com menos medo: Gradle previsível, testes confiáveis, qualidade automatizada, secrets protegidos e deploy controlado. Comece pequeno com build, teste e qualidade. Depois adicione Docker, ambientes, approvals e observabilidade. O importante é que cada mudança no projeto Kotlin passe por um caminho repetível antes de virar produção.
Pipelines semelhantes podem ser configurados para projetos Go, Python e Rust, mas a disciplina é a mesma: automatizar o caminho crítico e deixar o time livre para escrever código melhor.