Docker Deployment
The service ships as two images, both based on ghcr.io/wultra/wultra-base-images (jre-semeru-25_2 and jre-semeru-25-liquibase-4_2). Neither image contains credentials or a PKCS#11 library; secrets arrive at run time.
The docker commands below use the build context . after mvn clean package (the image copies whatever war is in target/). Passwords are passed as -e NAME without a value, so they are read from the exported variable of your shell and do not appear on the docker command line; they remain visible to docker inspect on the host.
Migration Image
The migration image (docker-init/Dockerfile) is run first with the schema owner’s credentials; the service image holds none. The base image entrypoint does not start Liquibase, so the entrypoint is overridden:
docker build -f docker-init/Dockerfile -t secure-keystore-init .
docker run --rm --network <network> --entrypoint liquibase -e LIQUIBASE_COMMAND_PASSWORD secure-keystore-init \
--search-path=/usr/local/liquibase/db/changelog --changelog-file=db.changelog-master.xml \
--url=jdbc:postgresql://<db-host>:5432/secure_keystore --username=<owner> update
Service Image
The service image is started with a non-owner database role (the command above creates the schema; grant the role only the data privileges it needs). The port is published on the loopback interface only.
Proof of concept. Not for production use. The service does not authenticate callers yet, so run it only on an isolated network. All attestations and certificates it creates are for testing only.
mvn clean package
docker build -t secure-keystore .
docker run --rm --network <network> -p 127.0.0.1:8080:8080 \
--read-only --tmpfs /tmp --cap-drop ALL --security-opt no-new-privileges --ulimit core=0 \
-e SECURE_KEYSTORE_DATASOURCE_URL=jdbc:postgresql://<db-host>:5432/secure_keystore \
-e SECURE_KEYSTORE_DATASOURCE_USERNAME=<non-owner-role> -e SECURE_KEYSTORE_DATASOURCE_PASSWORD \
secure-keystore
- The base image already runs as the non-root user
wultra(uid 999). The container runs with a read-only root file system (/tmpis atmpfs), no capabilities andno-new-privileges. - The image has no PKCS#11 library; once the
keystore.hsm.enabledproperty exists, run it withkeystore.hsm.enabled=false. - Reverse proxies and load balancers in front of the service must not log request paths or bodies.
- Check the war manifest:
unzip -p target/secure-keystore-*.war META-INF/MANIFEST.MF | grep Enable-Native-Access(the war declaresEnable-Native-Access: ALL-UNNAMEDfor the PKCS#11 binding).
Memory Dumps
Token PINs live in memory, so a core, heap or system dump would expose them. The image sets JAVA_OPTS=-Xdump:none (OpenJ9, the runtime of the base image; HotSpot needs other flags) and the run command adds --ulimit core=0. Do not override JAVA_OPTS at run time without keeping -Xdump:none, or core, heap and system dumps are written again. Also check the host’s kernel.core_pattern, which is outside the image.