keycloak-mcp-server

Jib Container Image Guide

Overview

The Keycloak MCP Server now supports building container images using Jib (Java Image Builder), a fast and efficient container image builder that doesn’t require Docker to be installed.

What is Jib?

Jib is a containerization tool from Google that builds optimized Docker and OCI images for Java applications without needing:

Benefits

No Docker Required - Build images without Docker daemon Fast Builds - Only rebuilds changed layers Reproducible - Same source = same image Optimized Layers - Separates dependencies from classes Multi-Platform - Build for linux/amd64 and linux/arm64 CI/CD Friendly - Perfect for pipelines

Configuration

Base Configuration

The project is configured in application.properties:

# Container Image Configuration
quarkus.container-image.build=false
quarkus.container-image.registry=quay.io
quarkus.container-image.group=sshaaf
quarkus.container-image.name=keycloak-mcp-server
quarkus.container-image.tag=@git.commit.id.abbrev@ # Automatic: Git SHA
quarkus.container-image.additional-tags=latest # Automatic: Latest

# Jib specific
quarkus.jib.base-jvm-image=registry.access.redhat.com/ubi9/openjdk-21-runtime:1.20
quarkus.jib.platforms=linux/amd64,linux/arm64

Note: The @git.commit.id.abbrev@ is automatically replaced with the current git commit SHA during the build.

Image Details

Automatic Git SHA Tagging

Images are automatically tagged with the git commit SHA for perfect traceability:

# Every build automatically creates:
quay.io/sshaaf/keycloak-mcp-server:49ff54e # Git commit SHA (primary)
quay.io/sshaaf/keycloak-mcp-server:latest # Latest build

# Semantic versions are manually tagged when ready for release:
docker tag quay.io/sshaaf/keycloak-mcp-server:49ff54e \
 quay.io/sshaaf/keycloak-mcp-server:0.3.0

Benefits:

See Git Commit Tagging Guide for complete details.

Building Container Images

1. Build and Push to Quay.io

# Set Quay.io credentials
export QUAY_USERNAME=your-username
export QUAY_PASSWORD=your-password # or robot token

# Build and push
./mvnw package \
 -Dquarkus.container-image.build=true \
 -Dquarkus.container-image.push=true \
 -Dquarkus.container-image.username=${QUAY_USERNAME} \
 -Dquarkus.container-image.password=${QUAY_PASSWORD}

Result: quay.io/sshaaf/keycloak-mcp-server:0.3.0 and :latest pushed to Quay.io

2. Build and Push to Custom Registry

# For GitHub Container Registry
./mvnw package \
 -Dquarkus.container-image.push=true \
 -Dquarkus.container-image.registry=ghcr.io \
 -Dquarkus.container-image.group=your-username \
 -Dquarkus.container-image.username=${GITHUB_USERNAME} \
 -Dquarkus.container-image.password=${GITHUB_TOKEN}

Result: ghcr.io/your-username/keycloak-mcp-server:0.3.0

3. Build and Load to Docker Daemon

# Build and load into local Docker
./mvnw package -Dquarkus.jib.docker-executable-name=docker

# Verify
docker images | grep keycloak-mcp-server

Result: Image available locally in Docker

4. Build to Tarball (Offline Distribution)

# Build to tar file
./mvnw package \
 -Dquarkus.container-image.build=true \
 -Djib.outputPaths.tar=target/keycloak-mcp-server.tar

# Load into Docker
docker load < target/keycloak-mcp-server.tar

Result: target/keycloak-mcp-server.tar file created

5. Build for Specific Platform

# Build only for ARM64 (Apple Silicon)
./mvnw package -Dquarkus.jib.platforms=linux/arm64

# Build only for AMD64
./mvnw package -Dquarkus.jib.platforms=linux/amd64

# Build for both (default)
./mvnw package -Dquarkus.jib.platforms=linux/amd64,linux/arm64

Running the Container

Using Docker

# Pull and run from Quay.io
docker run -d \
 -p 8080:8080 \
 -e KC_URL=http://host.docker.internal:8180 \
 --name keycloak-mcp \
 quay.io/sshaaf/keycloak-mcp-server:0.3.0

# Check logs
docker logs -f keycloak-mcp

# Test health
curl http://localhost:8080/q/health

Using Podman

# Same commands work with Podman
podman run -d \
 -p 8080:8080 \
 -e KC_URL=http://host.containers.internal:8180 \
 --name keycloak-mcp \
 quay.io/sshaaf/keycloak-mcp-server:0.3.0

Using Docker Compose

version: '3.8'

services:
 keycloak-mcp-server:
 image: quay.io/sshaaf/keycloak-mcp-server:0.3.0
 ports:
 - "8080:8080"
 environment:
 KC_URL: http://keycloak:8080 depends_on:
 - keycloak
 networks:
 - keycloak-net

 keycloak:
 image: quay.io/keycloak/keycloak:23.0
 ports:
 - "8180:8080"
 environment:
 KEYCLOAK_ADMIN: admin
 KEYCLOAK_ADMIN_PASSWORD: admin
 command: start-dev
 networks:
 - keycloak-net

networks:
 keycloak-net:
 driver: bridge

Run with:

docker compose up -d

Kubernetes/OpenShift Deployment

Kubernetes Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
 name: keycloak-mcp-server
 labels:
 app: keycloak-mcp-server
spec:
 replicas: 1
 selector:
 matchLabels:
 app: keycloak-mcp-server
 template:
 metadata:
 labels:
 app: keycloak-mcp-server
 spec:
 containers:
 - name: server
 image: quay.io/sshaaf/keycloak-mcp-server:0.3.0
 ports:
 - containerPort: 8080
 name: http
 protocol: TCP
 env:
 - name: KC_URL
 value: "http://keycloak:8080" secretKeyRef:
 name: keycloak-admin
 key: password
 livenessProbe:
 httpGet:
 path: /q/health/live
 port: 8080
 initialDelaySeconds: 30
 periodSeconds: 10
 readinessProbe:
 httpGet:
 path: /q/health/ready
 port: 8080
 initialDelaySeconds: 10
 periodSeconds: 5
 resources:
 requests:
 memory: "256Mi"
 cpu: "250m"
 limits:
 memory: "512Mi"
 cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
 name: keycloak-mcp-server
spec:
 type: ClusterIP
 selector:
 app: keycloak-mcp-server
 ports:
 - port: 8080
 targetPort: 8080
 name: http
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
 name: keycloak-mcp-server
 annotations:
 nginx.ingress.kubernetes.io/rewrite-target: /
spec:
 rules:
 - host: mcp.example.com
 http:
 paths:
 - path: /
 pathType: Prefix
 backend:
 service:
 name: keycloak-mcp-server
 port:
 number: 8080

Deploy with:

kubectl apply -f k8s-deployment.yaml

CI/CD Integration

GitHub Actions

name: Build and Push Container

on:
 push:
 branches: [ main ]
 tags: [ 'v*' ]

jobs:
 build:
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v4

 - name: Set up JDK 21
 uses: actions/setup-java@v4
 with:
 java-version: '21'
 distribution: 'temurin'
 cache: maven

 - name: Build and Push Container Image
 run: |
 ./mvnw package \
 -Dquarkus.container-image.push=true \
 -Dquarkus.container-image.registry=ghcr.io \
 -Dquarkus.container-image.group=$ \
 -Dquarkus.container-image.username=$ \
 -Dquarkus.container-image.password=$

GitLab CI

build-container:
 stage: build
 image: maven:3.9-eclipse-temurin-21
 script:
 - ./mvnw package
 -Dquarkus.container-image.push=true
 -Dquarkus.container-image.registry=$CI_REGISTRY
 -Dquarkus.container-image.group=$CI_PROJECT_NAMESPACE
 -Dquarkus.container-image.username=$CI_REGISTRY_USER
 -Dquarkus.container-image.password=$CI_REGISTRY_PASSWORD
 only:
 - main
 - tags

Advanced Configuration

Custom Base Image

# Use a different base image
quarkus.jib.base-jvm-image=eclipse-temurin:21-jre

Additional Ports

# Expose additional ports
quarkus.jib.ports=8080,8443

Custom Labels

# Add custom OCI labels
quarkus.jib.labels."com.example.team"=platform
quarkus.jib.labels."com.example.maintainer"=devops@example.com

Working Directory

# Set working directory in container
quarkus.jib.working-directory=/app

User Configuration

# Run as non-root user
quarkus.jib.user=1001:1001

JVM Arguments

# Customize JVM arguments
quarkus.jib.jvm-arguments=-Xmx512m,-Xms256m,-XX:+UseG1GC

Registry-Specific Configurations

Quay.io (Default)

# Login to Quay.io (optional, for authentication)
docker login quay.io

# Build and push
./mvnw package \
 -Dquarkus.container-image.build=true \
 -Dquarkus.container-image.push=true \
 -Dquarkus.container-image.username=${QUAY_USERNAME} \
 -Dquarkus.container-image.password=${QUAY_PASSWORD}

# Or use robot account
./mvnw package \
 -Dquarkus.container-image.build=true \
 -Dquarkus.container-image.push=true \
 -Dquarkus.container-image.username=sshaaf+robot \
 -Dquarkus.container-image.password=${QUAY_ROBOT_TOKEN}

Repository: https://quay.io/repository/sshaaf/keycloak-mcp-server

Docker Hub

./mvnw package \
 -Dquarkus.container-image.build=true \
 -Dquarkus.container-image.push=true \
 -Dquarkus.container-image.registry=docker.io \
 -Dquarkus.container-image.username=${DOCKER_USERNAME} \
 -Dquarkus.container-image.password=${DOCKER_PASSWORD}

GitHub Container Registry (ghcr.io)

./mvnw package \
 -Dquarkus.container-image.push=true \
 -Dquarkus.container-image.registry=ghcr.io \
 -Dquarkus.container-image.username=${GITHUB_USERNAME} \
 -Dquarkus.container-image.password=${GITHUB_TOKEN}

AWS ECR

# Login to ECR first
aws ecr get-login-password --region us-east-1 | \
 docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com

# Build and push
./mvnw package \
 -Dquarkus.container-image.push=true \
 -Dquarkus.container-image.registry=123456789012.dkr.ecr.us-east-1.amazonaws.com \
 -Dquarkus.container-image.username=AWS \
 -Dquarkus.container-image.password=$(aws ecr get-login-password --region us-east-1)

Azure Container Registry (ACR)

# Login to ACR first
az acr login --name myregistry

# Build and push
./mvnw package \
 -Dquarkus.container-image.push=true \
 -Dquarkus.container-image.registry=myregistry.azurecr.io \
 -Dquarkus.container-image.username=${ACR_USERNAME} \
 -Dquarkus.container-image.password=${ACR_PASSWORD}

Google Container Registry (GCR)

# Configure Docker to use gcloud as credential helper
gcloud auth configure-docker

# Build and push
./mvnw package \
 -Dquarkus.container-image.push=true \
 -Dquarkus.container-image.registry=gcr.io \
 -Dquarkus.container-image.group=${GCP_PROJECT_ID}

Troubleshooting

Issue: “unauthorized: authentication required”

Solution: Ensure you are logged in to the registry:

docker login
# or
echo $GITHUB_TOKEN | docker login ghcr.io -u $GITHUB_USERNAME --password-stdin

Issue: “platform not supported”

Solution: Build for specific platform:

./mvnw package -Dquarkus.jib.platforms=linux/amd64

Issue: Build is slow

Solution: Jib caches layers. First build is slow, subsequent builds are fast. To disable cache:

./mvnw package -Dquarkus.jib.use-current-timestamp=true

Issue: Base image pull fails

Solution: Check network connectivity or use alternative base image:

quarkus.jib.base-jvm-image=eclipse-temurin:21-jre

Comparison: Jib vs Docker

Feature Jib Docker Build
Docker Daemon Required No Yes
Dockerfile Required No Yes
Build Speed (incremental) Very Fast Slower
Layer Optimization Automatic Manual
Multi-Platform Native buildx required
CI/CD Friendly Excellent Good
Reproducibility Perfect Good
Maven/Gradle Integration Native External

Best Practices

1. Use Specific Base Image Versions

# Good - pinned version
quarkus.jib.base-jvm-image=registry.access.redhat.com/ubi9/openjdk-21-runtime:1.20

# Avoid - floating tag
quarkus.jib.base-jvm-image=openjdk:21

2. Tag with Version and Commit SHA

./mvnw package \
 -Dquarkus.container-image.tag=0.3.0-${GIT_COMMIT_SHA}

3. Use Non-Root User

quarkus.jib.user=1001:1001

4. Optimize for Size

5. Security Scanning

# Scan with Trivy
trivy image quay.io/sshaaf/keycloak-mcp-server:0.3.0

# Scan with Snyk
snyk container test quay.io/sshaaf/keycloak-mcp-server:0.3.0

# Quay.io also provides built-in security scanning
# View results at: https://quay.io/repository/sshaaf/keycloak-mcp-server?tab=tags

Image Information

Default Built Image

Port Configuration Strategy

The application uses smart port assignment:

Deployment Mode Port Reason
Local JAR/Native 0 (random) Avoids port conflicts in development
Container 8080 (fixed) Reliable networking for Docker/Kubernetes

The container automatically overrides port 0 to 8080 via the QUARKUS_HTTP_PORT environment variable.

Labels

All images include OCI-compliant labels:

Summary

Jib Extension Added: quarkus-container-image-jib Configuration Complete: Ready to build images Multi-Platform Support: AMD64 & ARM64 CI/CD Ready: No Docker daemon required Optimized Layers: Fast incremental builds


Quick Start:

# Pull and run from Quay.io
docker pull quay.io/sshaaf/keycloak-mcp-server:latest

# Run
docker run -d -p 8080:8080 \
 -e KC_URL=http://host.docker.internal:8180 \
 quay.io/sshaaf/keycloak-mcp-server:0.3.0

# Or build locally and load to Docker
./mvnw package \
 -Dquarkus.container-image.build=true \
 -Dquarkus.jib.docker-executable-name=docker

For more information, see: