Docker Compose Integration

Spring Boot 3.1+ includes built-in Docker Compose support that can automatically start and configure services defined in a compose.yaml file. This makes it easy to run Memory Service and its dependencies during development.

Overview

Spring Boot’s Docker Compose integration provides:

  • Automatic service discovery - Spring Boot detects running containers
  • Service connection - Automatic configuration via ConnectionDetails
  • Health checks - Waits for services to be ready before starting the app
  • Clean shutdown - Stops containers when the app shuts down (optional)

Setup

Add the Docker Compose dependency to your pom.xml:

<dependency>
  <groupId>io.github.chirino.memory-service</groupId>
  <artifactId>memory-service-spring-boot-docker-compose-starter</artifactId>
  <version>999-SNAPSHOT</version>
</dependency>

Place a compose.yaml file in your project root (Spring Boot auto-detects it). The chat-spring example ships this one:

compose.yaml
services:
  postgres:
    image: pgvector/pgvector:pg18
    environment:
      POSTGRES_DB: memory_service
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    ports:
      - "55432:5432"
    volumes:
      # Init script mount (read-only); data lives on tmpfs below so it disappears with the container
      - ../../../../deploy/keycloak/postgres-init-keycloak.sql:/docker-entrypoint-initdb.d/postgres-init-keycloak.sql:ro
    tmpfs:
      # Ephemeral Postgres data dir; removed automatically when the container is deleted
      # PG 18+ uses version-specific subdirs under /var/lib/postgresql
      - /var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 10

  keycloak:
    image: quay.io/keycloak/keycloak:26.7.4
    command: ["start-dev", "--import-realm"]
    environment:
      KC_BOOTSTRAP_ADMIN_USERNAME: admin
      KC_BOOTSTRAP_ADMIN_PASSWORD: admin
      KC_DB: postgres
      KC_DB_URL_HOST: postgres
      KC_DB_URL_DATABASE: keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: keycloak
      KC_HTTP_RELATIVE_PATH: /
      KC_HEALTH_ENABLED: "true"
      # External hostname for browser redirects (configurable via env var)
      KC_HOSTNAME: http://${KEYCLOAK_HOSTNAME:-localhost}:${KEYCLOAK_HOSTNAME_PORT:-8081}
      # Allow internal (Docker network) backchannel communication
      KC_HOSTNAME_BACKCHANNEL_DYNAMIC: "true"
    volumes:
      - ../../../../deploy/keycloak/memory-service-realm.json:/opt/keycloak/data/import/memory-service-realm.json:ro
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "8081:8080"
    healthcheck:
      test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/9000 && printf 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3 && cat <&3 | grep -q 'UP'"]
      interval: 10s
      timeout: 5s
      retries: 15
      start_period: 30s

  memory-service:
    image: ghcr.io/chirino/memory-service:latest
    build:
      context: ../../
      dockerfile: Dockerfile
    environment:
      # Trusted agent API keys (first value is surfaced to the Spring app via the service connection)
      MEMORY_SERVICE_API_KEYS_AGENT: agent-api-key-1,agent-api-key-2
      # Browser-visible local developer-console credential. Never use this key in production.
      # Local-only admin client for /developer: do not add it to trusted user-ID clients or send
      # X-User-ID from the console.
      MEMORY_SERVICE_API_KEYS_DEVELOPER_FRONTEND: developer-frontend-api-key
      # Docker port publishing cannot reach a loopback-only listener, so bind 0.0.0.0 over
      # plaintext HTTP and acknowledge it (local development only).
      MEMORY_SERVICE_HOST: 0.0.0.0
      MEMORY_SERVICE_PLAIN_TEXT: "true"
      MEMORY_SERVICE_TLS: "false"
      MEMORY_SERVICE_ALLOW_NON_LOOPBACK_PLAINTEXT: "true"

      # ── Self-contained storage ─────
      MEMORY_SERVICE_DB_KIND: sqlite
      MEMORY_SERVICE_DB_URL: file:/tmp/memory-service-dev/memory-service.db
      MEMORY_SERVICE_DB_MIGRATE_AT_START: "true"
      MEMORY_SERVICE_CACHE_KIND: local
      # Hardened startup otherwise requires a dedicated management listener.
      MEMORY_SERVICE_MANAGEMENT_ON_MAIN_LISTENER: "true"
      MEMORY_SERVICE_VECTOR_KIND: sqlite
      MEMORY_SERVICE_EMBEDDING_KIND: local
      MEMORY_SERVICE_ATTACHMENTS_KIND: fs
      MEMORY_SERVICE_ATTACHMENTS_FS_DIR: /tmp/memory-service-dev/attachments
      MEMORY_SERVICE_TEMP_DIR: /tmp/memory-service-dev/tmp

      # ── Authentication & authorization ─────────────────────────
      MEMORY_SERVICE_OIDC_ISSUER: http://${KEYCLOAK_HOSTNAME:-localhost}:${KEYCLOAK_HOSTNAME_PORT:-8081}/realms/memory-service
      MEMORY_SERVICE_OIDC_DISCOVERY_URL: http://keycloak:8080/realms/memory-service
      # Development-only issuer validation; configure allowed audiences in production.
      MEMORY_SERVICE_OIDC_ALLOWED_AUDIENCES: memory-service
      MEMORY_SERVICE_OIDC_USER_ID_CLAIM: /preferred_username
      MEMORY_SERVICE_ROLES_ADMIN_OIDC_ROLE: admin
      MEMORY_SERVICE_ROLES_AUDITOR_OIDC_ROLE: auditor
      MEMORY_SERVICE_ROLES_ADMIN_CLIENTS: developer_frontend
      MEMORY_SERVICE_ROLES_INDEXER_CLIENTS: agent

      # ── Developer Frontend ─────────────────────────────────────
      # Enable the integrated developer frontend at http://localhost:8082/developer
      MEMORY_SERVICE_DEVELOPER_FRONTEND_ENABLED: "true"
      MEMORY_SERVICE_DEVELOPER_FRONTEND_CLIENT_ID: developer_frontend
      MEMORY_SERVICE_DEVELOPER_FRONTEND_AUTH_MODE: api-key
      MEMORY_SERVICE_DEVELOPER_FRONTEND_API_KEY: developer-frontend-api-key
      MEMORY_SERVICE_BASE_URL: "http://localhost:8082"
      MEMORY_SERVICE_CORS_ENABLED: "true"
      MEMORY_SERVICE_CORS_ORIGINS: "http://localhost:3000"

      # ── Encryption ─────────────────────────────────────────────
      # Well-known development key; replace with a secure key in production.
      MEMORY_SERVICE_ENCRYPTION_KIND: dek
      MEMORY_SERVICE_ENCRYPTION_DEK_KEY: ${MEMORY_SERVICE_ENCRYPTION_DEK_KEY:-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef}
    depends_on:
      keycloak:
        condition: service_healthy
    ports:
      - "8082:8080"
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8080/ready || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 12

# Spring Boot 3.1+ will auto-detect this compose file when running from this directory.

The relative volume paths and the build section refer to this repository’s layout. When you copy the file into your own project, point the volumes at your own Keycloak realm and database init script, and remove the build section so Compose uses the published image.

This configuration uses self-contained storage for the memory service (SQLite, local cache, filesystem attachments). The integrated developer console at http://localhost:8082/developer skips OAuth and uses its dedicated API-key admin client. Keycloak remains available for the Spring chat application’s end-user login flow.

The developer console key is deliberately exposed through /developer/config.json, so anyone who can reach port 8082 receives local admin access. This is appropriate only for isolated local development and must never be enabled in production. The console does not send X-User-ID; that header selects user context for normal user APIs and is not a credential for Admin APIs.

Key features:

  • SQLite for memory service database storage (ephemeral)
  • Local cache for in-memory caching
  • Filesystem for attachment storage
  • Local embeddings for vector operations
  • Keycloak for the chat application’s OIDC authentication (uses Postgres)
  • Integrated developer frontend at http://localhost:8082/developer

Access points:

  • Memory service API: http://localhost:8082
  • Developer frontend: http://localhost:8082/developer
  • Keycloak admin console: http://localhost:8081 (admin/admin)
  • API key: agent-api-key-1

Service Connection

Spring Boot automatically detects the memory-service container and provides connection details via MemoryServiceConnectionDetails. The starter uses these connection details to configure the REST and gRPC clients.

Make sure you comment out the memory-service.client properties in your application.properties file to pickup the connection details from the Docker Compose file.

# These will override Docker Compose connection details if set
#memory-service.client.url=http://localhost:8082
#memory-service.client.api-key=agent-api-key-1

Running the Application

When you start your Spring Boot application:

mvn spring-boot:run

Spring Boot will:

  1. Detect the compose.yaml file
  2. Start all services defined in the file
  3. Wait for health checks to pass
  4. Provide connection details to your application
  5. Start your application

Disabling Docker Compose

To disable Docker Compose integration (e.g., when running against an external service):

spring.docker.compose.enabled=false

Or set the environment variable:

export SPRING_DOCKER_COMPOSE_ENABLED=false

Custom Compose File Location

If your compose.yaml is in a different location:

spring.docker.compose.file=./docker/compose.yaml