Preserve login/session over KeyCloak restart

Issue

I’m running a single KeyCloak instance as docker container. For the purpose of this post this can be a development setup. Each time after restarting KeyCloak I need to login again on my client. I want KeyCloak to preserve all sessions so that after a restart the user is still logged in.

My attempt to a solution

Configure the included/embedded infinispan cache to use file-based persistence. The cache file is written to a location where a docker volume is mounted. KeyCloak starts just fine and the xml config seems to be loaded correctly and shows no errors. However, sessions are not preserved.

Docker compose

services:
    keycloak:
        image: keycloak-with-chmod:latest # based on quay.io/keycloak/keycloak:19.0.3
        command: >-
            start
            --db=postgres
            --http-relative-path=/kc
            --cache=ispn --cache-config-file=cache-ispn.xml
            --proxy=edge
        volumes:
            - infinispan_data:/opt/keycloak/cache
            - ./cache-ispn.xml:/opt/keycloak/conf/cache-ispn.xml:ro

Custom Dockerfile

FROM quay.io/keycloak/keycloak:19.0.3
RUN mkdir /opt/keycloak/cache
RUN chmod 777 /opt/keycloak/cache

cache-ispn.xml

<infinispan xmlns="urn:infinispan:config:11.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:infinispan:config:11.0 http://www.infinispan.org/schemas/infinispan-config-11.0.xsd">

    <cache-container name="keycloak">
        <transport lock-timeout="60000"/>

        <global-state>
            <persistent-location path="/opt/keycloak/cache"/>
        </global-state>

        <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>
        <local-cache name="sessions">
            <expiration lifespan="-1"/>
            <persistence passivation="false">
                <file-store path="/opt/keycloak/cache" shared="false" purge="false"/> <!-- fetch-state="true -->
            </persistence>
        </local-cache>
        <local-cache name="clientSessions">
            <expiration lifespan="-1"/>
            <persistence passivation="false">
                <file-store path="/opt/keycloak/cache" shared="false" purge="false"/>
            </persistence>
        </local-cache>
        <local-cache name="authenticationSessions">
            <expiration lifespan="-1"/>
            <persistence passivation="false">
                <file-store path="/opt/keycloak/cache" shared="false" purge="false"/>
            </persistence>
        </local-cache>
        <local-cache name="offlineSessions">
            <expiration lifespan="-1"/>
        </local-cache>
        <local-cache name="offlineClientSessions">
            <expiration lifespan="-1"/>
        </local-cache>
        <local-cache name="loginFailures">
            <expiration lifespan="-1"/>
        </local-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"/>
        </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>
        <local-cache name="actionTokens">
            <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"/>
        </local-cache>
    </cache-container>
</infinispan>

KeyCloak log

keycloak-keycloak-1  | 2022-11-30 14:49:06,165 WARN  [org.infinispan.PERSISTENCE] (keycloak-cache-init) ISPN000554: jboss-marshalling is deprecated and planned for removal
keycloak-keycloak-1  | 2022-11-30 14:49:06,188 INFO  [org.infinispan.CONTAINER] (keycloak-cache-init) ISPN000556: Starting user marshaller 'org.infinispan.jboss.marshalling.core.JBossUserMarshaller'
keycloak-keycloak-1  | 2022-11-30 14:49:06,514 INFO  [org.infinispan.CONTAINER] (keycloak-cache-init) ISPN000128: Infinispan version: Infinispan 'Triskaidekaphobia' 13.0.9.Final
keycloak-keycloak-1  | 2022-11-30 14:49:06,516 INFO  [org.infinispan.CONTAINER] (keycloak-cache-init) ISPN000389: Loaded global state, version=13.0.9.Final timestamp=2022-11-30T14:48:34.264306Z
keycloak-keycloak-1  | 2022-11-30 14:49:06,646 INFO  [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000078: Starting JGroups channel `ISPN`
keycloak-keycloak-1  | 2022-11-30 14:49:06,647 INFO  [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000088: Unable to use any JGroups configuration mechanisms provided in properties {}. Using default JGroups configuration!
keycloak-keycloak-1  | 2022-11-30 14:49:06,729 WARN  [org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the send buffer of socket MulticastSocket was set to 1.00MB, but the OS only allocated 212.99KB
keycloak-keycloak-1  | 2022-11-30 14:49:06,730 WARN  [org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the receive buffer of socket MulticastSocket was set to 20.00MB, but the OS only allocated 212.99KB
keycloak-keycloak-1  | 2022-11-30 14:49:06,730 WARN  [org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the send buffer of socket MulticastSocket was set to 1.00MB, but the OS only allocated 212.99KB
keycloak-keycloak-1  | 2022-11-30 14:49:06,731 WARN  [org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the receive buffer of socket MulticastSocket was set to 25.00MB, but the OS only allocated 212.99KB
keycloak-keycloak-1  | 2022-11-30 14:49:08,744 INFO  [org.jgroups.protocols.pbcast.GMS] (keycloak-cache-init) 0e7857366864-37494: no members discovered after 2004 ms: creating cluster as coordinator
keycloak-keycloak-1  | 2022-11-30 14:49:08,757 INFO  [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000094: Received new cluster view for channel ISPN: [0e7857366864-37494|0] (1) [0e7857366864-37494]
keycloak-keycloak-1  | 2022-11-30 14:49:08,761 INFO  [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000079: Channel `ISPN` local address is `0e7857366864-37494`, physical addresses are `[172.31.0.3:46921]`
keycloak-keycloak-1  | 2022-11-30 14:49:09,389 INFO  [org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory] (main) Node name: 0e7857366864-37494, Site name: null
keycloak-keycloak-1  | 2022-11-30 14:49:10,060 INFO  [io.quarkus] (main) Keycloak 19.0.3 on JVM (powered by Quarkus 2.7.6.Final) started in 8.330s. Listening on: http://0.0.0.0:8080
keycloak-keycloak-1  | 2022-11-30 14:49:10,061 INFO  [io.quarkus] (main) Profile prod activated.
keycloak-keycloak-1  | 2022-11-30 14:49:10,061 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-h2, jdbc-mariadb, jdbc-mssql, jdbc-mysql, jdbc-oracle, jdbc-postgresql, keycloak, logging-gelf, narayana-jta, reactive-routes, resteasy, resteasy-jackson, smallrye-context-propagation, smallrye-health, smallrye-metrics, vault, vertx]

Can anybody see what I’m missing?