From 0fbe0255cd0a40fcefdb252a39701f32dc963b4a Mon Sep 17 00:00:00 2001 From: viacheslav_kolesnyk Date: Thu, 26 Feb 2026 18:37:15 +0100 Subject: [PATCH 1/2] chore: Improve docker compose for local development - add compose file for app - add compose file for ingrastructure - add env and pgadmin servers files - add readme file for docker compose testing Implements: MODNOTES-280 --- docker/.env | 24 ++ docker/README.md | 363 ++++++++++++++++++++++++ docker/app-docker-compose.yml | 43 +++ docker/infra-docker-compose.yml | 55 ++++ docker/pgadmin-servers.json | 14 + pom.xml | 14 + src/main/resources/application-dev.yaml | 123 ++++++++ 7 files changed, 636 insertions(+) create mode 100644 docker/.env create mode 100644 docker/README.md create mode 100644 docker/app-docker-compose.yml create mode 100644 docker/infra-docker-compose.yml create mode 100644 docker/pgadmin-servers.json create mode 100644 src/main/resources/application-dev.yaml diff --git a/docker/.env b/docker/.env new file mode 100644 index 00000000..bc38ca40 --- /dev/null +++ b/docker/.env @@ -0,0 +1,24 @@ +# Project name for Docker Compose +COMPOSE_PROJECT_NAME=folio-mod-notes + +# Module configuration +MODULE_PORT=8081 +MODULE_REPLICAS=1 +DEBUG_PORT=5005 + +# PostgreSQL configuration +DB_HOST=postgres +DB_PORT=5432 +DB_DATABASE=modules +DB_USERNAME=folio_admin +DB_PASSWORD=folio_admin + +# PgAdmin configuration +PGADMIN_PORT=5050 +PGADMIN_DEFAULT_EMAIL=user@domain.com +PGADMIN_DEFAULT_PASSWORD=admin + +# WireMock (Okapi mock) configuration +WIREMOCK_PORT=9130 +OKAPI_URL=http://wiremock:8080 + diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..c784caa7 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,363 @@ +# 🐳 Docker Compose Setup for mod-notes + +Local development environment for mod-notes using Docker Compose. + +## 📋 Prerequisites + +- Docker and Docker Compose V2+ +- Java 21+ (for local development mode) +- Maven 3.8+ (for building the module) + +## 🏗️ Architecture + +Two compose files provide flexible development workflows: + +- **`infra-docker-compose.yml`**: Infrastructure services only (PostgreSQL, pgAdmin, WireMock) +- **`app-docker-compose.yml`**: Full stack including the module (uses `include` to incorporate infra services) + +## ⚙️ Configuration + +Configuration is managed via the `.env` file in this directory. + +### Key Environment Variables + +| Variable | Description | Default | +|---------------------------|-------------------------------|------------------------| +| `MODULE_REPLICAS` | Number of module instances | `1` | +| `MODULE_PORT` | Module host port | `8081` | +| `DEBUG_PORT` | Remote debugging port | `5005` | +| `DB_HOST` | PostgreSQL hostname | `postgres` | +| `DB_PORT` | PostgreSQL port | `5432` | +| `DB_DATABASE` | Database name | `modules` | +| `DB_USERNAME` | Database user | `folio_admin` | +| `DB_PASSWORD` | Database password | `folio_admin` | +| `PGADMIN_PORT` | PgAdmin port | `5050` | +| `WIREMOCK_PORT` | WireMock (Okapi mock) port | `9130` | +| `OKAPI_URL` | Okapi URL for the module | `http://wiremock:8080` | + +## 🚀 Services + +### PostgreSQL +- **Purpose**: Primary database for module data +- **Version**: PostgreSQL 16 Alpine +- **Access**: localhost:5432 (configurable via `DB_PORT`) +- **Credentials**: See `DB_USERNAME` and `DB_PASSWORD` in `.env` +- **Database**: See `DB_DATABASE` in `.env` + +### pgAdmin +- **Purpose**: Database administration interface +- **Access**: http://localhost:5050 (configurable via `PGADMIN_PORT`) +- **Login**: Use `PGADMIN_DEFAULT_EMAIL` and `PGADMIN_DEFAULT_PASSWORD` from `.env` + +### WireMock +- **Purpose**: Mock Okapi and other FOLIO modules for local testing +- **Access**: http://localhost:9130 (configurable via `WIREMOCK_PORT`) +- **Mappings**: Located in `src/test/resources/mappings` + +## 📖 Usage + +> **Note**: All commands in this guide assume you are in the `docker/` directory. If you're at the project root, run `cd docker` first. + +### Starting the Environment + +```bash +# Build the module first +mvn -f ../pom.xml clean package -DskipTests + +# Start all services (infrastructure + module) +docker compose -f app-docker-compose.yml up -d +``` + +```bash +# Start only infrastructure services (for local development) +docker compose -f infra-docker-compose.yml up -d +``` + +```bash +# Start with build (if module code changed) +docker compose -f app-docker-compose.yml up -d --build +``` + +```bash +# Start specific service +docker compose -f infra-docker-compose.yml up -d postgres +``` + +### Stopping the Environment + +```bash +# Stop all services +docker compose -f app-docker-compose.yml down +``` + +```bash +# Stop infra services only +docker compose -f infra-docker-compose.yml down +``` + +```bash +# Stop and remove volumes (clean slate) +docker compose -f app-docker-compose.yml down -v +``` + +### Viewing Logs + +```bash +# All services +docker compose -f app-docker-compose.yml logs +``` + +```bash +# Specific service +docker compose -f app-docker-compose.yml logs mod-notes +``` + +```bash +# Follow logs in real-time +docker compose -f app-docker-compose.yml logs -f mod-notes +``` + +```bash +# Last 100 lines +docker compose -f app-docker-compose.yml logs --tail=100 mod-notes +``` + +### Scaling the Module + +The module is configured with resource limits and deployment policies for production-like scaling: + +- **CPU Limits**: 1.0 CPU (max), 0.5 CPU (reserved) +- **Memory Limits**: 512M (max), 256M (reserved) +- **Restart Policy**: Automatic restart on failure +- **Update Strategy**: Rolling updates with 1 instance at a time, 10s delay + +```bash +# Scale to 3 instances +docker compose -f app-docker-compose.yml up -d --scale mod-notes=3 +``` + +```bash +# Or modify MODULE_REPLICAS in .env and restart +echo "MODULE_REPLICAS=3" >> .env +docker compose -f app-docker-compose.yml up -d +``` + +### Cleanup and Reset + +```bash +# Complete cleanup (stops containers, removes volumes) +docker compose -f app-docker-compose.yml down -v +``` + +```bash +# Remove all Docker resources +docker compose -f app-docker-compose.yml down -v +docker volume prune -f +docker network prune -f +``` + +## 🔧 Development Workflows + +### Workflow 1: Full Docker Stack +Run everything in Docker, including the module. + +```bash +# Build the module +mvn -f ../pom.xml clean package -DskipTests + +# Start all services +docker compose -f app-docker-compose.yml up -d + +# View logs +docker compose -f app-docker-compose.yml logs -f mod-notes +``` + +**Use Case**: Testing the full deployment, simulating production environment, scaling tests. + +### Workflow 2: Infrastructure Only + IDE +Run infrastructure in Docker, develop the module in your IDE. + +```bash +# Start infrastructure +docker compose -f infra-docker-compose.yml up -d + +# Run module from IDE or command line +mvn -f ../pom.xml spring-boot:run +``` + +**Use Case**: Active development with hot reload, debugging in IDE, faster iteration cycles. + +### Workflow 3: Spring Boot Docker Compose Integration +Let Spring Boot manage Docker Compose automatically (recommended for rapid development). + +```bash +# Run with dev profile (starts infrastructure automatically) +mvn -f ../pom.xml spring-boot:run -Dspring-boot.run.profiles=dev +``` + +The `dev` profile is configured to: +- Start services from `docker/infra-docker-compose.yml` automatically +- Connect to services via localhost ports (PostgreSQL: 5432, WireMock: 9130) +- Keep containers running after the application stops for faster subsequent startups + +**Use Case**: Quickest way to start development — no manual Docker commands needed. + +### Workflow 4: Spring Boot DevTools +For rapid development with automatic restart on code changes. + +```bash +# Start infrastructure +docker compose -f infra-docker-compose.yml up -d + +# Run with devtools (automatic restart on code changes) +mvn -f ../pom.xml spring-boot:run + +# Make code changes — application will automatically restart +``` + +**Use Case**: Continuous development with automatic reload, live code updates, rapid feedback loop. + +## 🛠️ Common Tasks + +### Building the Module + +```bash +# Clean build (skip tests) +mvn -f ../pom.xml clean package -DskipTests +``` + +```bash +# Build with tests +mvn -f ../pom.xml clean package +``` + +### Tenant Setup + +After starting the module, register a tenant by calling the `/_/tenant` API: + +```bash +curl -X POST http://localhost:8081/_/tenant \ + -H "Content-Type: application/json" \ + -H "X-Okapi-Tenant: diku" \ + -H "X-Okapi-Url: http://localhost:9130" \ + -d '{"module_to": "mod-notes-8.0.0-SNAPSHOT", "parameters": [{"key": "loadReference", "value": "true"}]}' +``` + +> **Note**: Adjust `module_to` version to match the currently running module version. + +### Accessing Services + +```bash +# PostgreSQL CLI +docker compose -f infra-docker-compose.yml exec postgres psql -U folio_admin -d modules +``` + +```bash +# View database tables +docker compose -f infra-docker-compose.yml exec postgres psql -U folio_admin -d modules -c "\dt" +``` + +```bash +# Check PostgreSQL health +docker compose -f infra-docker-compose.yml exec postgres pg_isready -U folio_admin +``` + +### Rebuilding the Module + +```bash +# Rebuild and restart the module +mvn -f ../pom.xml clean package -DskipTests +docker compose -f app-docker-compose.yml up -d --build mod-notes +``` + +```bash +# Force rebuild without cache +docker compose -f app-docker-compose.yml build --no-cache mod-notes +docker compose -f app-docker-compose.yml up -d mod-notes +``` + +## 🐛 Troubleshooting + +### Port Conflicts + +If you encounter port conflicts, modify the ports in `.env`: + +```bash +# Example: Change module port to 8082 +MODULE_PORT=8082 +``` + +Then restart the services: + +```bash +docker compose -f app-docker-compose.yml up -d +``` + +### Container Health Issues + +```bash +# Check container status +docker compose -f app-docker-compose.yml ps + +# Check specific container logs +docker compose -f app-docker-compose.yml logs mod-notes + +# Restart a specific service +docker compose -f app-docker-compose.yml restart mod-notes +``` + +### Database Connection Issues + +```bash +# Verify PostgreSQL is running +docker compose -f infra-docker-compose.yml ps postgres + +# Check PostgreSQL logs +docker compose -f infra-docker-compose.yml logs postgres + +# Test database connection +docker compose -f infra-docker-compose.yml exec postgres psql -U folio_admin -d modules -c "SELECT 1" +``` + +**`FATAL: database "modules" does not exist`** — PostgreSQL only creates the database defined in `POSTGRES_DB` on the very first startup with an empty data directory. If the `postgres-data` volume already existed from a previous run (with different settings), the init is skipped. Fix by recreating the volume: + +```bash +docker compose -f infra-docker-compose.yml stop postgres pgadmin +docker compose -f infra-docker-compose.yml rm -f postgres pgadmin +docker volume rm folio-mod-notes_postgres-data +docker compose -f infra-docker-compose.yml up -d postgres pgadmin +``` + +### Clean Start + +If you need to completely reset the environment: + +```bash +# Stop and remove everything +docker compose -f app-docker-compose.yml down -v + +# Remove any orphaned containers +docker container prune -f + +# Remove unused networks +docker network prune -f + +# Start fresh +mvn -f ../pom.xml clean package -DskipTests +docker compose -f app-docker-compose.yml up -d --build +``` + +## 📚 Additional Resources + +- [Docker Compose Documentation](https://docs.docker.com/compose/) +- [Spring Boot Docker Compose Support](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.docker-compose) +- [mod-notes Documentation](../README.md) + +## 💡 Tips + +- Use **Workflow 3** (Spring Boot Docker Compose) for the fastest development experience +- Keep infrastructure running between development sessions to save startup time +- Use **Workflow 1** (Full Docker Stack) when testing deployment or scaling scenarios +- Use `docker compose -f infra-docker-compose.yml logs -f` to monitor all infrastructure services +- pgAdmin provides a helpful web interface for inspecting the database at http://localhost:5050 + diff --git a/docker/app-docker-compose.yml b/docker/app-docker-compose.yml new file mode 100644 index 00000000..3b9d6d60 --- /dev/null +++ b/docker/app-docker-compose.yml @@ -0,0 +1,43 @@ +# Full application stack for mod-notes (infrastructure + module) +# This includes all infrastructure services and the mod-notes module itself + +include: + - infra-docker-compose.yml + +services: + mod-notes: + image: dev.folio/mod-notes:latest + build: + context: ../ + dockerfile: Dockerfile + ports: + - "${MODULE_PORT}:8081" + - "${DEBUG_PORT}:5005" + environment: + DB_HOST: ${DB_HOST} + DB_PORT: ${DB_PORT} + DB_DATABASE: ${DB_DATABASE} + DB_USERNAME: ${DB_USERNAME} + DB_PASSWORD: ${DB_PASSWORD} + OKAPI_URL: ${OKAPI_URL} + JAVA_OPTIONS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" + networks: + - mod-notes-local + depends_on: + - postgres + - wiremock + deploy: + replicas: ${MODULE_REPLICAS:-1} + restart_policy: + condition: on-failure + update_config: + parallelism: 1 + delay: 10s + resources: + limits: + cpus: "1.0" + memory: "512M" + reservations: + cpus: "0.5" + memory: "256M" + diff --git a/docker/infra-docker-compose.yml b/docker/infra-docker-compose.yml new file mode 100644 index 00000000..005e7f10 --- /dev/null +++ b/docker/infra-docker-compose.yml @@ -0,0 +1,55 @@ +# Infrastructure services for mod-notes +# This file contains all infrastructure dependencies (database, pgAdmin, WireMock/Okapi mock) +# It can be used standalone for local development or included in app-docker-compose.yml + +services: + postgres: + image: postgres:16-alpine + ports: + - ${DB_PORT}:5432 + environment: + POSTGRES_USER: ${DB_USERNAME} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DB: ${DB_DATABASE} + volumes: + - postgres-data:/var/lib/postgresql/data + networks: + - mod-notes-local + + pgadmin: + image: dpage/pgadmin4:latest + ports: + - ${PGADMIN_PORT}:80 + environment: + PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL} + PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD} + PGADMIN_CONFIG_SERVER_MODE: 'False' + PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False' + PGADMIN_SERVER_JSON_FILE: '/pgadmin4/servers.json' + PGADMIN_REPLACE_SERVERS_ON_STARTUP: 'True' + volumes: + - pgadmin-data:/var/lib/pgadmin + - ./pgadmin-servers.json:/pgadmin4/servers.json:ro + depends_on: + - postgres + networks: + - mod-notes-local + + wiremock: + image: wiremock/wiremock:latest + ports: + - ${WIREMOCK_PORT}:8080 + volumes: + - ../src/test/resources/mappings:/home/wiremock/mappings + entrypoint: ["/docker-entrypoint.sh", "--global-response-templating", "--disable-gzip", "--verbose"] + networks: + - mod-notes-local + +networks: + mod-notes-local: + driver: bridge + +volumes: + postgres-data: + pgadmin-data: + diff --git a/docker/pgadmin-servers.json b/docker/pgadmin-servers.json new file mode 100644 index 00000000..b49fc3da --- /dev/null +++ b/docker/pgadmin-servers.json @@ -0,0 +1,14 @@ +{ + "Servers": { + "1": { + "Name": "mod-notes (local)", + "Group": "Servers", + "Host": "postgres", + "Port": 5432, + "MaintenanceDB": "modules", + "Username": "folio_admin", + "SSLMode": "prefer" + } + } +} + diff --git a/pom.xml b/pom.xml index f5529986..2af16b2c 100644 --- a/pom.xml +++ b/pom.xml @@ -110,6 +110,20 @@ ${jsoup.version} + + org.springframework.boot + spring-boot-docker-compose + runtime + true + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + org.springframework.boot diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml new file mode 100644 index 00000000..afb80b41 --- /dev/null +++ b/src/main/resources/application-dev.yaml @@ -0,0 +1,123 @@ +# Development profile for local development with Spring Boot Docker Compose support +# Activate this profile with: --spring.profiles.active=dev or SPRING_PROFILES_ACTIVE=dev + +spring: + application: + name: mod-notes + profiles: + default: "dev" + threads: + virtual: + enabled: true + datasource: + url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_DATABASE:modules} + username: ${DB_USERNAME:folio_admin} + password: ${DB_PASSWORD:folio_admin} + hikari: + connectionTimeout: ${DB_CONNECTION_TIMEOUT:30000} + idleTimeout: ${DB_IDLE_TIMEOUT:600000} + keepaliveTime: ${DB_KEEPALIVE_TIME:0} + maxLifetime: ${DB_MAX_LIFETIME:1800000} + validationTimeout: ${DB_VALIDATION_TIMEOUT:5000} + maximumPoolSize: ${DB_MAXPOOLSIZE:10} + minimumIdle: ${DB_MINIMUM_IDLE:10} + initializationFailTimeout: ${DB_INITIALIZATION_FAIL_TIMEOUT:30000} + leakDetectionThreshold: ${DB_LEAK_DETECTION_THRESHOLD:60000} + liquibase: + changeLog: classpath:db/changelog/changelog-master.xml + enabled: true + jpa: + open-in-view: true + hibernate: + ddl-auto: none + properties: + hibernate: + dialect: org.hibernate.dialect.PostgreSQLDialect + format_sql: true + show-sql: false + jackson: + default-property-inclusion: NON_NULL + deserialization: + fail-on-unknown-properties: false + accept-single-value-as-array: true + cache: + type: caffeine + sql: + init: + continue-on-error: true + docker: + compose: + enabled: true + file: ./docker/infra-docker-compose.yml + +folio: + okapi-url: ${OKAPI_URL:http://localhost:9130} + exchange: + enabled: true + tenant: + validation: + enabled: true + logging: + request: + enabled: true + level: basic + exchange: + enabled: true + level: basic + notes: + cache: + configs: + users-by-id: + initialCapacity: 20 + maximumSize: 100 + expireAfterWrite: 60 + types: + defaults: + name: General note + limit: ${NOTES_TYPES_DEFAULTS_LIMIT:25} + content: + allowed: + tags: + - p + - strong + - em + - a + - u + - ol + - ul + - li + - h1 + - h2 + - h3 + - br + attributes: + all: + - class + a: + - href + - rel + - target + response: + limit: ${MAX_RECORDS_COUNT:1000} + +management: + endpoints: + web: + exposure: + include: info,health,env,httptrace,loggers + base-path: /admin + health: + defaults: + enabled: false + readinessstate: + enabled: true + endpoint: + loggers: + access: unrestricted + +server: + port: 8081 + +logging: + level: + org.folio.spring.filter.IncomingRequestLoggingFilter: DEBUG From 9ce5faaa030d698f497145dcda328f19400772cc Mon Sep 17 00:00:00 2001 From: viacheslav_kolesnyk Date: Fri, 27 Feb 2026 12:02:59 +0100 Subject: [PATCH 2/2] Fix application-dev --- src/main/resources/application-dev.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index afb80b41..fa49cadd 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -4,8 +4,6 @@ spring: application: name: mod-notes - profiles: - default: "dev" threads: virtual: enabled: true