SAML SSO Docker Deployment Guide
| Parameter | Value |
|---|---|
Application version |
Spring Boot 3.5.6 / Java 25 |
Identity Provider |
Keycloak 26.0 (Docker) |
Standard |
SAML 2.0, HTTP POST Binding |
Networking |
Docker Compose with shared bridge network |
1. Overview
This guide covers deploying Keycloak (as a test Identity Provider) in Docker alongside the OneIoT backend, with full SAML SSO configuration. Both services run in separate Docker Compose stacks connected via a shared Docker network, allowing container-to-container communication by hostname.
The deployment consists of two independent Compose stacks:
-
Keycloak stack — Keycloak IdP + PostgreSQL database for Keycloak metadata storage
-
OneIoT stack — UI Backend (Spring Boot) + UI Portals (Nginx), using the existing
compose.yml
A shared Docker bridge network (ft-shared-network) connects the two stacks, enabling the backend to reach Keycloak for SAML metadata exchange and assertion verification.
2. Prerequisites
-
Docker Engine 24+ and Docker Compose v2+ installed on the deployment host.
-
OneIoT backend Docker image built and available locally or in a registry.
-
Prepare and build the all in one compose file from:
FT_DISK → prerelease → ONE IOT UI → docker → all in one → saml
docker compose up -d
-
Basic familiarity with SAML SSO concepts. See SAML SSO Integration Guide for a full description of the authentication flow, attribute mapping, and architecture.
-
SAML SP key pair generated (see Generating the SP Key Pair).
3. Keycloak Docker deployment
3.1. Docker Compose for Keycloak
Run compose-saml.yml with the following content:
version: "3.9"
networks:
ft-network:
name: ft-shared-network
driver: bridge
services:
keycloak:
image: quay.io/keycloak/keycloak:latest
command: start --hostname=https://<DOCKER_HOST>:8500
volumes:
- ./<YOUR.crt>.crt:/opt/keycloak/conf/server.crt
- ./<YOUR.key>.key:/opt/keycloak/conf/server.key
environment:
KC_LOG_LEVEL: "INFO,org.keycloak.protocol.saml:DEBUG,org.keycloak.saml:DEBUG,org.keycloak.events:DEBUG"
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
KC_HTTPS_CERTIFICATE_FILE: /opt/keycloak/conf/server.crt
KC_HTTPS_CERTIFICATE_KEY_FILE: /opt/keycloak/conf/server.key
ports:
- "8500:8443"
networks:
- ft-network
| <DOCKER_HOST> should be replaced with valid hostname or IP |
The start-dev command runs Keycloak in development mode — no TLS, no hostname verification, relaxed security. In production, use the start command with proper TLS certificates and a production database. See the Keycloak production configuration guide.
|
The ft-network is a named bridge network (ft-shared-network) that will be shared with the OneIoT backend stack. Keycloak is attached to both keycloak-internal (for PostgreSQL access) and ft-network (for backend communication).
|
The Keycloak Admin Console will be available at http://<DOCKER_HOST>:<KEYCLOAK_PORT>/admin with the credentials defined in KEYCLOAK_ADMIN / KEYCLOAK_ADMIN_PASSWORD.
|
3.2. Starting Keycloak
# Start the Keycloak stack in detached mode
docker compose -f compose-saml.yml up -d
# Verify container are running and healthy
docker compose -f compose-saml.yml ps
# Check Keycloak startup logs
docker logs keycloak --tail 50
Open the Admin Console in your browser:
https://localhost:8500/admin
Log in with admin / admin (default credentials from the compose file).
3.3. Keycloak configuration
| For detailed step-by-step instructions on configuring Keycloak as a SAML IdP, see SAML SSO Setup — Keycloak setup (test IdP). |
The following configuration steps must be completed in the Keycloak Admin Console:
-
Create realm
oneiotin Realm settings -
Create SAML clients:
-
oneiot-sp-sc— Support Center portal use SAML client type -
oneiot-sp-mc— Management Console portal use SAML client type
-
-
Disable Client signature required on the Keys tab for each client
-
Export IdP signing certificate to your
idp.crt(see Exporting IdP Signing Certificate) -
Configure User Profile attributes — add
domain,user-group, andportal-typeat Realm Settings → User profile -
Create test users with the custom attributes filled in at Users → Create user
-
Create Protocol Mappers for each client —
username,email,domain,user-group,portal-typeat Clients → oneiot-sp-sc/mc → Client scopes → oneiot-sp-sc/mc-dedicated → Configure new mapper → User Attribute
When setting the
Master SAML Processing URL and for example https://<UI_HOST>:8843/iot-webservice/login/saml2/sso/saml-support-portal or saml-management-portal
Valid Redirect URIs for example https://<UI_HOST>:8843/iot-webservice/*
in Keycloak, use the
externally accessible
URL of the UI (the same value as SAML_REDIRECT_HOST=https://<UI_HOST>:8843/),
not the Docker-internal hostname.
|
4. Backend deployment with SAML
4.1. Connecting to the shared network
The existing OneIoT backend compose.yml (located at iot-web-app-build/docker/compose.yml) must be connected to the same Docker network as Keycloak so the backend container can reach Keycloak by its container name.
4.1.1. Option A: Modify compose.yml (recommended)
Add the external network declaration and attach the ui-backend service to it:
# Add to the top-level networks section of compose.yml:
networks:
ft-network:
external: true
name: ft-shared-network
# Add network membership to ui-backend service:
services:
ui-backend:
networks:
- ft-network
- default
# ... rest of the service definition ...
4.2. SAML environment variables
The following environment variables control SAML SSO behavior. They are passed to the backend via the .env.saml file.
| Environment variable | YAML path | Default value | Description |
|---|---|---|---|
|
|
|
Enables/disables SAML SSO. When |
|
|
— (required) |
Base application URL used to construct redirect links and the ACS URL. Example: |
|
|
— (required) |
Identity Provider Entity ID. Example: |
|
|
— (required) |
IdP Single Sign-On URL. Example: |
|
|
|
Service Provider Entity ID for the Support Center portal. |
|
|
|
Service Provider Entity ID for the Management Console portal. |
|
|
|
Name of the SAML attribute containing the username. |
|
|
|
Name of the SAML attribute containing the user email. |
|
|
|
Name of the SAML attribute determining the portal type ( |
|
|
|
Name of the SAML attribute containing the user domain. The value must match the domain name in the database. |
|
|
|
Name of the SAML attribute containing the user group. The value must match the group name in the database. |
|
|
|
Name of the SAML attribute with the account expiration date. Formats: ISO-8601 ( |
4.3. Post-authentication redirect URLs
After successful SAML authentication, the backend redirects the browser to the frontend with a JWT token and session hash as query parameters.
| Portal | URL | Configuration |
|---|---|---|
SC |
|
|
MC |
|
|
The frontend receives the JWT token and session hash as query parameters:
https://portal.example.com/support-portal/login/auth-callback?token=eyJhbG...&sessionHash=BASE64...
4.4. .env.saml configuration file
The project includes a template SAML environment file at iot-web-app-build/docker/.env.saml. Copy this file and fill in the values for your deployment.
Below is a complete filled-in example for a Docker deployment where Keycloak runs in the same Docker host:
# ==========================================================
# SAML Configuration for Docker Deployment
# ==========================================================
SAML_ENABLED=true
# --- SC (Support Center) SP registration ---
SAML_SC_SP_ENTITY_ID=oneiot-sp-sc
SAML_SUCCESS_REDIRECT=https://<DOCKER_HOST>:8843/support-portal/login/auth-callback
# --- MC (Management Console) SP registration ---
SAML_MC_SP_ENTITY_ID=oneiot-sp-mc
SAML_MC_SUCCESS_REDIRECT=https://<DOCKER_HOST>:8843/management-portal/login/auth-callback
# --- IdP (Keycloak running in Docker) ---
# Use the Docker container name as hostname for server-to-server communication
SAML_IDP_ENTITY_ID=https://<DOCKER_HOST>:8500/realms/oneiot
SAML_IDP_SSO_URL=https://<DOCKER_HOST>:8500/realms/oneiot/protocol/saml
# --- Attribute mapping (defaults shown) ---
SAML_ATTR_USERNAME=username
SAML_ATTR_EMAIL=email
SAML_ATTR_CLIENT_TYPE=portal-type
SAML_ATTR_DOMAIN=domain
SAML_ATTR_USER_GROUP=user-group
SAML_ATTR_EXPIRE_DATE=expire-date
4.5. Certificate mount
SAML certificates must be mounted into the backend container.
Place the certificate files in the ui-backend-conf/ directory on the host:
| File | Purpose | Format | Required |
|---|---|---|---|
|
Identity Provider certificate (exported from Keycloak) |
X.509 PEM |
true |
|
Service Provider private key |
PKCS#8 PEM |
false |
|
Service Provider certificate |
X.509 PEM |
false |
The idp.crt file must be in X.509 PEM format:
-----BEGIN CERTIFICATE-----
MIICnTCCAYUCBgGE... (base64-encoded certificate data)
...
-----END CERTIFICATE-----
The certificate MUST include the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- header/footer lines. See Exporting IdP Signing Certificate for how to obtain it from Keycloak.
|
4.6. Starting the backend with SAML
Follow this sequence to start the full deployment:
# 1. Ensure Keycloak is running and healthy
docker compose -f compose-saml.yml ps
# Expected: keycloak (healthy), if exist: keycloak-db (healthy)
# 2. Verify the shared network exists
docker network ls | grep ft-shared-network
# 3. Start the backend stack with both env files
cd iot-web-app-build/docker
docker compose --env-file .env.mysql --env-file .env.saml up -d
# 4. Verify the backend can reach Keycloak via the shared network
docker exec <backend-container> curl -s http://keycloak:8080/realms/oneiot/.well-known/openid-configuration | head -5
# 5. Verify SAML metadata is published by the backend
curl http://localhost:8880/iot-webservice/saml2/metadata/saml-support-portal
If step 4 fails with "Could not resolve host: keycloak", the backend container is not connected to ft-shared-network. See Connecting to the shared network or run docker network connect ft-shared-network <backend-container>.
|
5. Shared Docker network setup
5.1. How it works
When two Docker Compose stacks need to communicate, they must share a common Docker network. Each Compose stack creates its own isolated default network, so containers from different stacks cannot reach each other unless explicitly connected.
In this deployment:
-
The Keycloak stack creates the
ft-shared-networkbridge network (defined asft-networkwithname: ft-shared-network). -
The OneIoT backend stack joins this network as an
externalnetwork. -
Containers on the shared network can reach each other by container name — Docker’s embedded DNS server resolves container names to IP addresses within the same network.
5.2. Network topology diagram
| Network | Scope | Connected containers |
|---|---|---|
|
Cross-stack communication |
|
|
Keycloak stack internal |
|
|
OneIoT stack internal |
|
5.3. Verifying network connectivity
# Check that the shared network exists
docker network inspect saml-shared-network
# List containers connected to the shared network
docker network inspect saml-shared-network --format '{{range .Containers}}{{.Name}} {{end}}'
# Test DNS resolution from the backend container
docker exec <backend-container> ping -c 3 keycloak
# Test Keycloak SAML descriptor endpoint from the backend container
docker exec <backend-container> curl -sf http://keycloak:8080/realms/oneiot/protocol/saml/descriptor | head -5
5.4. Troubleshooting network issues
5.4.1. "SAML signature validation failed"
Cause: The idp.crt file mounted into the backend container does not match Keycloak’s current signing key.
Solution:
-
Re-export the IdP signing certificate from Keycloak (see Exporting IdP Signing Certificate).
-
Replace the
idp.crtfile on the host inui-backend-conf/. -
Restart the backend container:
docker compose restart ui-backend
5.4.2. "Invalid redirect URI" in Keycloak
Cause: The SAML_REDIRECT_HOST value does not match the Valid Redirect URIs configured in the Keycloak SAML client.
Solution:
-
Open the Keycloak Admin Console > Clients >
oneiot-sp-sc(oroneiot-sp-mc). -
Verify that Valid Redirect URIs includes the redirect URL pattern, e.g.
https://portal.example.com/*. -
Verify that Master SAML Processing URL matches the ACS URL pattern:
https://portal.example.com/iot-webservice/login/saml2/sso/saml-support-portal.
6. Complete deployment example
This section provides a full end-to-end walkthrough for deploying Keycloak and the OneIoT backend with SAML SSO enabled.
6.1. Directory structure
Prepare the following directory structure on the deployment host:
/usr/local/ft-services/
├── compose-saml.yml # Keycloak stack (from this guide)
├── compose.yml # OneIoT stack (from iot-web-app-build/docker/)
├── .env.mysql # Database env (from iot-web-app-build/docker/)
├── .env.saml # SAML env (filled in from template)
├── ui-backend-conf/
│ ├── hazelcast-client.xml # Main Hazelcast client configuration
│ ├── ftacs.keystore # Backend keystore used when HTTPS is enabled
│ ├── logback-spring.xml # Default external logging configuration
│ ├── interfaceItems.json # Interface seed data, loaded if present
│ ├── idp.crt # Exported from Keycloak after setup
│ ├── sp.key (optional) # Generated SP private key
│ ├── sp.crt (optional) # Generated SP certificate
│ ├── customization/
│ │ ├── def/
│ │ ├── mc/
│ │ └── sc/
│ └── ssl/ # HTTPS certificates for Nginx/portals when configured
└── ft-data/ui-portals/
├── backend/logs/
└── nginx/logs/
6.2. Verification checklist
After completing the deployment, verify each item:
| # | Check | Command |
|---|---|---|
1 |
Keycloak Admin Console is accessible |
Open |
2 |
Keycloak realm |
Check in Admin Console |
3 |
SAML clients are configured |
Check in Admin Console > Clients |
4 |
Shared Docker network exists |
|
5 |
Backend container is on the shared network |
|
6 |
Backend can resolve Keycloak hostname |
|
7 |
Backend SAML metadata is published |
|
8 |
IdP certificate matches Keycloak |
Compare |
9 |
SAML login URL endpoint responds |
|
10 |
Auth type returns |
|
7. Appendix: Full docker-compose example with SAML
Below is the complete compose.yml with all SAML modifications applied — external network, env file reference, and certificate volume mounts:
version: "3.8"
name: ft-ui-portals
networks:
ft-network: (1)
external: true
name: ft-shared-network
services:
ui-backend:
image: hub.friendly-tech.com/ui/backend:latest
ports:
- "${FT_UI_BACKEND_HTTP_PORT:-8881}:8880"
env_file:
- .env.mysql
- .env.saml (2)
environment:
USE_NOHUP: 0
APP_HOME: "/opt/app"
CLICKHOUSE_SCHEMA: "${CLICKHOUSE_DATABASE}"
JAVA_OPTS: "${FT_UI_BACKEND_JAVA_RAM:--Xms512m -Xmx2g} -Duser.timezone=${TZ}"
volumes:
- ${DATA_FOLDER:-.}/ui-backend-conf:/opt/app/conf
- ${DATA_FOLDER:-.}/ft-data/ui-portals/backend/logs:/opt/app/logs
networks: (4)
- default
- ft-network
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
healthcheck:
test: [ "CMD", "curl", "-f", "http://127.0.0.1:8880/iot-webservice/swagger-ui/index.html" ]
interval: 10s
timeout: 5s
retries: 5
stop_grace_period: 45s
portals:
image: hub.friendly-tech.com/ui/portals:latest
depends_on:
ui-backend:
condition: service_healthy
ports:
- "${FT_UI_HTTP_PORT:-8880}:80"
- "${FT_UI_HTTPS_PORT:-8843}:443"
env_file:
- .env.mysql
volumes:
- ${DATA_FOLDER:-.}/ft-data/ui-portals/nginx/logs:/var/log/nginx
- ${DATA_FOLDER:-.}/ui-backend-conf/ssl:/etc/nginx/ssl:ro
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
healthcheck:
test: [ "CMD", "curl", "-f", "http://127.0.0.1/support-portal/index.html" ]
interval: 10s
timeout: 5s
retries: 5
stop_grace_period: 45s
| 1 | Declares ft-shared-network as an external network (created by the Keycloak stack). |
| 2 | Loads SAML environment variables from .env.saml in addition to the database env file. |
| 3 | Mounts SAML certificates from the host into the container at the paths expected by the application. |
| 4 | Attaches ui-backend to both the default network (for communication with portals) and ft-shared-network (for communication with keycloak). |
8. Related documentation
-
SAML SSO Integration Guide — full SAML configuration reference, authentication flow, Keycloak setup, attribute mapping
-
UI Services on Separate Host — standard Docker deployment without SAML
-
SSL / HTTPS Configuration — TLS setup for production deployments