Skip to content
Latchkey

CI/CD for a Kotlin + Ktor + Postgres App with GitHub Actions

Build with Gradle and run Ktor tests against a real Postgres database on every push.

This recipe tests a Kotlin Ktor service that talks to Postgres through Exposed or jOOQ. CI runs the Gradle build against a Postgres service container, then builds a fat jar for deployment.

What the pipeline does

  • set up the JDK with Gradle cache
  • start a Postgres service container
  • run the Gradle test task
  • build the fat jar with the shadow plugin
  • upload the jar artifact

The workflow

A Postgres service backs the data layer in tests. The Gradle wrapper runs check, then shadowJar packages a standalone runnable jar.

.github/workflows/ci.yml
name: CI
on:
  push:
    branches: [main]
  pull_request:
jobs:
  build:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: ktor
        ports: ["5432:5432"]
        options: >-
          --health-cmd "pg_isready" --health-interval 10s
          --health-timeout 5s --health-retries 5
    env:
      DATABASE_URL: jdbc:postgresql://localhost:5432/ktor
      DATABASE_USER: postgres
      DATABASE_PASSWORD: postgres
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: "21"
      - uses: gradle/actions/setup-gradle@v4
      - run: ./gradlew check
      - run: ./gradlew shadowJar
      - uses: actions/upload-artifact@v4
        with:
          name: ktor-jar
          path: build/libs/*-all.jar

Caching and speed

gradle/actions/setup-gradle restores the Gradle dependency and build cache and enables the build cache. Kotlin compilation plus Postgres integration tests are slow; cheaper managed runners such as Latchkey (around 69% cheaper than GitHub-hosted) keep the run fast and auto-retry a flaky Postgres image pull.

Deploying

The -all.jar from shadowJar is self-contained. Build a Docker image around it (FROM eclipse-temurin:21-jre) in a deploy job that needs the build job, then push and roll out once check passes on main.

Key takeaways

  • Back Ktor data-layer tests with a Postgres service container.
  • Use gradle/actions/setup-gradle for dependency and build caching.
  • Build a self-contained fat jar with the shadow plugin for deploy.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →