Skip to content

Keycloak (OIDC)

This guide walks through running Carbon Panel with Keycloak as an OIDC identity provider using a ready-made Docker Compose stack. The included realm config pre-creates a client, roles, groups, protocol mappers, and a default admin user so everything works out of the box.

  • Docker and Docker Compose

Clone the repo and navigate to the oidc/keycloak/ directory, then start the stack:

Terminal window
cd oidc/keycloak
docker compose up -d

Keycloak takes 30–60 seconds to start. Carbon Panel waits for it via a health check.

oidc/keycloak/docker-compose.yaml
# SPDX-License-Identifier: MIT AND MPL-2.0
# This file includes DiscoPanel material under the MIT License,
# Copyright (c) 2025 Nicholas W. Heyer. The MIT license text is in LICENSE
# and still applies to that inherited material.
# Maintainer changes are licensed under the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain
# one at https://mozilla.org/MPL/2.0/.
# Carbon Panel + Keycloak (OIDC)
#
# This is a complete docker-compose with OIDC authentication pre-configured using Keycloak.
#
# Keycloak takes ~30-60 seconds to start. Carbon Panel waits for it.
#
# Keycloak Admin UI (See KC_BOOTSTRAP_ADMIN_USERNAME/KC_BOOTSTRAP_ADMIN_PASSWORD below): http://localhost:8180/admin
services:
carbon-panel:
build:
context: ../../
dockerfile: docker/Dockerfile.carbon-panel
#image: athNdev/carbon-panel:dev
container_name: carbon-panel
restart: unless-stopped
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /tmp/carbon-panel:/app/data
environment:
- TZ=UTC
# ------------------------------------ AUTH CONFIG STARTS HERE FOR CARBON PANEL + KEYCLOAK ------------------------------------
- CARBONPANEL_AUTH_LOCAL_ENABLED=true
- CARBONPANEL_AUTH_OIDC_ENABLED=true
# ONLY CHANGE THIS IF YOU ARE HOSTING OIDC ON ANOTHER MACHINE OR WITH A DIFFERENT "realm" NAME
- CARBONPANEL_AUTH_OIDC_ISSUER_URI=http://localhost:8180/realms/CARBONPANEL
# ONLY CHANGE THIS IF YOUR CLIENT NAME IS DIFFERENT
- CARBONPANEL_AUTH_OIDC_CLIENT_ID=CARBONPANEL
# YOU SHOULD CHANGE THIS HERE AS WELL AS IN oidc/keycloak/config/realm.json (inside the clients object)
- CARBONPANEL_AUTH_OIDC_CLIENT_SECRET=CARBONPANEL-dev-secret
# YOU SHOULD CHANGE "localhost:8080" TO WHATEVER YOUR PUBLIC DOMAIN IS FOR CARBON PANEL (ie: https://mypanel.com/api/v1/auth/oidc/callback)
- CARBONPANEL_AUTH_OIDC_REDIRECT_URL=http://localhost:8080/api/v1/auth/oidc/callback
- CARBONPANEL_AUTH_OIDC_ROLE_CLAIM=groups
depends_on:
keycloak:
condition: service_healthy
keycloak:
image: quay.io/keycloak/keycloak:26.1
container_name: keycloak
command: start-dev --import-realm
volumes:
# THIS IS AN EXAMPLE REALM CONFIG, MODIFY AS NEEDED (WHICH MAY REQUIRE MODIFYING CARBON PANEL CONFIG TO MATCH, SEE ABOVE)
- ./config/realm.json:/opt/keycloak/data/import/realm.json:ro
environment:
# KEYCLOAK ADMIN LOGIN CREDENTIALS - CHANGE THESE!!!!!
- KC_BOOTSTRAP_ADMIN_USERNAME=admin
- KC_BOOTSTRAP_ADMIN_PASSWORD=admin
# KEYCLOAK DATABASE CONFIG, DONT CHANGE THESE UNLESS YOU KNOW WHAT YOU ARE DOING OR USING EXISTING DATABASE
- KC_DB=postgres
- KC_DB_URL_HOST=keycloak-db
- KC_DB_URL_DATABASE=keycloak
- KC_DB_USERNAME=keycloak
- KC_DB_PASSWORD=keycloak
# MISC KEYCLOAK CONFIGS
- KC_HOSTNAME_STRICT=false
- KC_HTTP_ENABLED=true
- KC_HEALTH_ENABLED=true
- KC_PROXY_HEADERS=xforwarded
ports:
- "8180:8080"
healthcheck:
test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9000; echo -e 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3; timeout 2 cat <&3 | grep -q '200 OK'"]
interval: 20s
timeout: 5s
retries: 12
start_period: 30s
depends_on:
keycloak-db:
condition: service_healthy
keycloak-db:
image: postgres:17-alpine
container_name: keycloak-db
environment:
- POSTGRES_DB=keycloak
- POSTGRES_USER=keycloak
- POSTGRES_PASSWORD=keycloak
volumes:
- keycloak-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U keycloak"]
interval: 10s
timeout: 3s
retries: 5
volumes:
keycloak-db-data:
Variable Purpose
CARBONPANEL_AUTH_OIDC_ENABLED Enables OIDC authentication
CARBONPANEL_AUTH_OIDC_ISSUER_URI Keycloak realm URL — change if hosting on another machine or using a different realm name
CARBONPANEL_AUTH_OIDC_CLIENT_ID Must match the clientId in the realm config
CARBONPANEL_AUTH_OIDC_CLIENT_SECRET Must match the secret in the realm config — change this for production
CARBONPANEL_AUTH_OIDC_REDIRECT_URL The callback URL — update localhost:8080 to your public domain
CARBONPANEL_AUTH_OIDC_ROLE_CLAIM Set to groups to read Keycloak group membership as Carbon Panel roles

The included realm JSON is imported automatically on first start via --import-realm. It configures:

  • Client (CARBONPANEL): confidential client with standard (authorization code) flow
  • Roles: admin and user realm roles
  • Groups: admin and user groups mapped to their respective roles — new users are added to the user group by default
  • Protocol mappers: a groups mapper that puts group membership into the groups claim on all tokens, and a realm-roles mapper for the roles claim
  • Default user: admin / admin with both admin and user groups
oidc/keycloak/config/realm.json
{
"realm": "CARBONPANEL",
"enabled": true,
"registrationAllowed": false,
"resetPasswordAllowed": true,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"sslRequired": "none",
"roles": {
"realm": [
{
"name": "admin",
"description": "Carbon Panel administrator"
},
{
"name": "user",
"description": "Carbon Panel user"
}
]
},
"groups": [
{
"name": "admin",
"realmRoles": ["admin"]
},
{
"name": "user",
"realmRoles": ["user"]
}
],
"defaultGroups": ["user"],
"clients": [
{
"clientId": "CARBONPANEL",
"enabled": true,
"protocol": "openid-connect",
"publicClient": false,
"secret": "CARBONPANEL-dev-secret",
"standardFlowEnabled": true,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": false,
"redirectUris": [
"http://localhost:8080/*",
"http://localhost:5173/*"
],
"webOrigins": [
"http://localhost:8080",
"http://localhost:5173"
],
"defaultClientScopes": [
"openid",
"profile",
"email",
"roles"
],
"protocolMappers": [
{
"name": "groups",
"protocol": "openid-connect",
"protocolMapper": "oidc-group-membership-mapper",
"config": {
"full.path": "false",
"introspection.token.claim": "true",
"userinfo.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "groups",
"jsonType.label": "String"
}
},
{
"name": "realm-roles",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-realm-role-mapper",
"config": {
"introspection.token.claim": "true",
"userinfo.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "roles",
"jsonType.label": "String",
"multivalued": "true"
}
}
]
}
],
"users": [
{
"username": "admin",
"enabled": true,
"email": "[email protected]",
"emailVerified": true,
"firstName": "Disco",
"lastName": "Admin",
"credentials": [
{
"type": "password",
"value": "admin",
"temporary": false
}
],
"groups": ["admin", "user"]
}
]
}
Service URL Username Password
Carbon Panel http://localhost:8080 — Log in via OIDC
Keycloak Admin Console http://localhost:8180/admin admin admin
Keycloak OIDC user — admin admin
  • Change all secrets: CARBONPANEL_AUTH_OIDC_CLIENT_SECRET, KC_BOOTSTRAP_ADMIN_PASSWORD, the client secret in realm.json, and the Postgres password
  • Enable TLS: put Keycloak behind a reverse proxy with a real certificate and update CARBONPANEL_AUTH_OIDC_ISSUER_URI to https://
  • Update redirect URIs: change localhost entries in both the compose file and realm.json to your actual domain
  • Disable local auth (optional): set CARBONPANEL_AUTH_LOCAL_ENABLED=false if you want OIDC-only login