Tech Blog

Ktor and Exposed

JetBrains 公式 Kotlin LSP の alpha がリリースされたので、 Kotlin, Ktor, and Exposed な開発環境を Dev Container で構築してみる

今回の環境

TL;DR

1. ソースコードの取得

gh repo clone ktorio/ktor-documentation
Cloning into 'ktor-documentation'...
remote: Enumerating objects: 27093, done.
remote: Counting objects: 100% (1417/1417), done.
remote: Compressing objects: 100% (429/429), done.
remote: Total 27093 (delta 1201), reused 997 (delta 982), pack-reused 25676 (from 2)
Receiving objects: 100% (27093/27093), 91.09 MiB | 55.00 MiB/s, done.
Resolving deltas: 100% (15385/15385), done.
code ktor-documentation/codeSnippets/snippets/tutorial-server-db-integration

2. .devcontainer の追加

mkdir -p .devcontainer
  // .devcontainer/devcontainer.json
+ // For format details, see https://aka.ms/devcontainer.json. For config options, see the
+ // README at: https://github.com/devcontainers/templates/tree/main/src/java-postgres
+ {
+ 	"name": "tutorial-server-db-integration",
+ 	"dockerComposeFile": "compose.yaml",
+ 	"service": "app",
+ 	"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
+
+ 	// Features to add to the dev container. More info: https://containers.dev/features.
+ 	"features": {
+ 		"ghcr.io/devcontainers/features/java:1": {
+ 			"version": "21",
+ 			"jdkDistro": "jbr"
+ 		}
+ 	}
+
+ 	// Use 'forwardPorts' to make a list of ports inside the container available locally.
+ 	// This can be used to network with other containers or with the host.
+ 	// "forwardPorts": [5432],
+
+ 	// Use 'postCreateCommand' to run commands after the container is created.
+ 	// "postCreateCommand": "java -version",
+ 
+ 	// Configure tool-specific properties.
+ 	// "customizations": {}
+
+ 	// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+ 	// "remoteUser": "root"
+ }
+
  // .devcontainer/devcontainer.env
+ POSTGRES_PASSWORD=postgres
+ POSTGRES_USER=postgres
+ POSTGRES_DB=postgres
+ POSTGRES_HOSTNAME=postgresdb
+
  # .devcontainer/compose.yaml
+ services:
+   app:
+     container_name: javadev
+     image: mcr.microsoft.com/devcontainers/base:debian
+     env_file: devcontainer.env
+     # NOTE: POSTGRES_DB/USER/PASSWORD should match values in db container
+
+     volumes:
+       - ../..:/workspaces:cached
+
+     # Overrides default command so things don't shut down after the process ends.
+     command: sleep infinity
+
+   db:
+     container_name: postgresdb
+     image: postgres:latest
+     restart: unless-stopped
+     volumes:
+       - postgres-data:/var/lib/postgresql/data
+     env_file: devcontainer.env
+     # NOTE: POSTGRES_DB/USER/PASSWORD should match values in app container
+
+     # Runs db on the same network as the app container, allows "forwardPorts" in devcontainer.json function.
+     network_mode: service:app
+
+     # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. 
+     # (Adding the "ports" property to this file will not forward from a Codespace.)
+
+ volumes:
+   postgres-data:
+

3. kotlin-lsp のインストール

mkdir -p .devcontainer/extensions
curl -sSLO --output-dir .devcontainer/extensions https://download-cdn.jetbrains.com/kotlin-lsp/0.252.17811/kotlin-0.252.17811.vsix
  // .devcontainer/devcontainer.json
  ...
  	// Features to add to the dev container. More info: https://containers.dev/features.
  	"features": {
  		"ghcr.io/devcontainers/features/java:1": {
  			"version": "21",
  			"jdkDistro": "jbr"
  		}
- 	}
+.  },

  ...
+ 	// Configure tool-specific properties.
- 	// "customizations": {}
+ 	"customizations": {
+ 		"vscode": {
+ 			"extensions": [
+ 				"${containerWorkspaceFolder}/.devcontainer/extensions/kotlin-0.252.17811.vsix"
+ 			]
+ 		}
+ 	}

  ...

4. postgres の設定

mkdir -p .devcontainer/postgres/initdb.d
  -- /Users/developer/workspaces/ktor-documentation/codeSnippets/snippets/tutorial-server-db-integration/.devcontainer/postgres/initdb.d/task.sql
+ DROP TABLE IF EXISTS task;
+ CREATE TABLE task(id SERIAL PRIMARY KEY, name VARCHAR(50), description VARCHAR(50), priority VARCHAR(50));
+
+ INSERT INTO task (name, description, priority) VALUES ('cleaning', 'Clean the house', 'Low');
+ INSERT INTO task (name, description, priority) VALUES ('gardening', 'Mow the lawn', 'Medium');
+ INSERT INTO task (name, description, priority) VALUES ('shopping', 'Buy the groceries', 'High');
+ INSERT INTO task (name, description, priority) VALUES ('painting', 'Paint the fence', 'Medium');
+ INSERT INTO task (name, description, priority) VALUES ('exercising', 'Walk the dog', 'Medium');
+ INSERT INTO task (name, description, priority) VALUES ('meditating', 'Contemplate the infinite', 'High');
+
  # .devcontainer/compose.yaml
  ...
    db:
      container_name: postgresdb
      image: postgres:latest
      restart: unless-stopped
      volumes:
+       - ./postgres/initdb.d:/docker-entrypoint-initdb.d:ro
        - postgres-data:/var/lib/postgresql/data
  ...
  // .devcontainer/devcontainer.env
- POSTGRES_PASSWORD=password
- POSTGRES_USER=postgres
- POSTGRES_DB=postgres
+ POSTGRES_PASSWORD=password
+ POSTGRES_USER=postgresql
+ POSTGRES_DB=ktor_tutorial_db
+ POSTGRES_HOSTNAME=postgresdb
+

5. Dev Container で実行する

```bash[class='command-line'][data-user='takeharak'][data-host='macbook-pro'][data-filter-continuation='(con)'][data-filter-output='(out)']
devcontainer open .
./gradlew run
Downloading https://services.gradle.org/distributions/gradle-8.4-bin.zip
............10%............20%.............30%............40%.............50%............60%.............70%............80%.............90%............100%

Welcome to Gradle 8.4!

Here are the highlights of this release:
 - Compiling and testing with Java 21
 - Faster Java compilation on Windows
 - Role focused dependency configurations creation

For more details see https://docs.gradle.org/8.4/release-notes.html

Starting a Gradle Daemon (subsequent builds will be faster)

> Task :run
2025-06-26 20:42:39.664 [main] INFO  Application - Autoreload is disabled because the development mode is off.
2025-06-26 20:42:39.851 [main] INFO  Application - Application started in 0.387 seconds.
2025-06-26 20:42:39.979 [DefaultDispatcher-worker-1] INFO  Application - Responding at http://0.0.0.0:8080

6. 確認

open http://localhost:8080/static/index.html

A Simple SPA For Tasks

参考にしたページ