Preserve user sessions over Keycloak restart

Attempt 2 - Using Keycloak 19.0.3 and configure the built-in Infinispan cache to use database persistence (Ideas taken from - jGroups/Infinispan Bedevilling: No DB Table Created (Unable to persist Infinispan internal caches as no global state enabled) - Miscellanaeous - Keycloak)

Result - Keycloak container starts without any errors, the JGROUPSPING table is created in the keycloak PostgreSQL database with 1 row (cluster_name - “ISPN”) but again the user session is lost after restarting the Keycloak container

  • docker-compose.yml

keycloak:
container_name: ${APP_NAME}_keycloak
image: Quay
restart: “no” # unless-stopped

    volumes:
        - ./scripts/keycloak-import:opt/keycloak/imports
        - ./keycloak/conf/cache-ispn.xml:opt/keycloak/conf/cache-ispn.xml

    command: >-
        start-dev
        --cache=ispn
        --cache-config-file=cache-ispn.xml
        --db=postgres
        --db-url-host=db
        --db-url-database=keycloak
        --db-username=$DB_USERNAME
        --db-password=$DB_PASSWORD
        --proxy=edge
        --http-relative-path=/auth/

    ports:
        - 8080:8080

    depends_on:
        - db   

volumes:
my_volume:
external: true
certificates:

  • ./keyclock/conf/cache-ispn.xml
<?xml version="1.0" encoding="UTF-8"?>

<jgroups>
    <stack name="jdbc-ping-tcp" extends="tcp">
        <JDBC_PING connection_driver="org.postgresql.Driver"
                   connection_url="jdbc:postgresql://127.0.0.1:5438/keycloak"
                   connection_username="{UserName}"
                   connection_password="{Password}"
                   initialize_sql="CREATE TABLE IF NOT EXISTS JGROUPSPING (own_addr varchar(200) NOT NULL, cluster_name varchar(200) NOT NULL, ping_data BYTEA, constraint PK_JGROUPSPING PRIMARY KEY (own_addr, cluster_name));"
                   info_writer_sleep_time="500"
                   remove_all_data_on_view_change="true"
                   stack.combine="REPLACE"
                   stack.position="MPING" />
    </stack>
</jgroups>

<cache-container name="keycloak">
    <transport lock-timeout="60000" stack="jdbc-ping-tcp"/>
    <local-cache name="realms">
        <encoding>
            <key media-type="application/x-java-object"/>
            <value media-type="application/x-java-object"/>
        </encoding>
        <memory max-count="10000"/>
    </local-cache>
    <local-cache name="users">
        <encoding>
            <key media-type="application/x-java-object"/>
            <value media-type="application/x-java-object"/>
        </encoding>
        <memory max-count="10000"/>
    </local-cache>
    <distributed-cache name="sessions" owners="2">
        <expiration lifespan="-1"/>
        <persistence>
            <file-store preload="true" fetch-state="true"/>
        </persistence>
    </distributed-cache>
    <distributed-cache name="authenticationSessions" owners="2">
        <expiration lifespan="-1"/>
        <persistence>
            <file-store preload="true" fetch-state="true"/>
        </persistence>
    </distributed-cache>
    <distributed-cache name="offlineSessions" owners="2">
        <expiration lifespan="-1"/>
        <persistence>
            <file-store preload="true" fetch-state="true"/>
        </persistence>
    </distributed-cache>
    <distributed-cache name="clientSessions" owners="2">
        <expiration lifespan="-1"/>
        <persistence>
            <file-store preload="true" fetch-state="true"/>
        </persistence>
    </distributed-cache>
    <distributed-cache name="offlineClientSessions" owners="2">
        <expiration lifespan="-1"/>
        <persistence>
            <file-store preload="true" fetch-state="true"/>
        </persistence>
    </distributed-cache>
    <distributed-cache name="loginFailures" owners="2">
        <expiration lifespan="-1"/>
        <persistence>
            <file-store preload="true" fetch-state="true"/>
        </persistence>
    </distributed-cache>
    <local-cache name="authorization">
        <encoding>
            <key media-type="application/x-java-object"/>
            <value media-type="application/x-java-object"/>
        </encoding>
        <memory max-count="10000"/>
    </local-cache>
    <replicated-cache name="work">
        <expiration lifespan="-1"/>
        <persistence>
            <file-store preload="true" fetch-state="true"/>
        </persistence>
    </replicated-cache>
    <local-cache name="keys">
        <encoding>
            <key media-type="application/x-java-object"/>
            <value media-type="application/x-java-object"/>
        </encoding>
        <expiration max-idle="3600000"/>
        <memory max-count="1000"/>
    </local-cache>
    <distributed-cache name="actionTokens" owners="2">
        <encoding>
            <key media-type="application/x-java-object"/>
            <value media-type="application/x-java-object"/>
        </encoding>
        <expiration max-idle="-1" lifespan="-1" interval="300000"/>
        <memory max-count="-1"/>
        <persistence>
            <file-store preload="true" fetch-state="true"/>
        </persistence>
    </distributed-cache>
</cache-container>