Skip to main content

Docker Deployment

Ratchet runs on Jakarta EE 10/11 runtimes. This guide shows how to containerize a Ratchet application using WildFly or Payara as the runtime, with Docker Compose for local development.

Base Image Selection

Choose a base image that provides the Jakarta EE services Ratchet's default RI runtime uses: CDI, JPA, Interceptors, and Jakarta Concurrency.

RuntimeBase ImageNotes
WildFlyquay.io/wildfly/wildfly:39.0.1.Final-jdk17Recommended for Ratchet (used in CI)
Payara Micropayara/micro:6.2025.11-jdk17Lightweight, good for microservices
Open Libertyicr.io/appcafe/open-liberty:26.0.0.2-full-java17-openj9Feature-based configuration

WildFly Dockerfile

This Dockerfile builds a WAR deployment on WildFly with a PostgreSQL data source:

FROM quay.io/wildfly/wildfly:39.0.1.Final-jdk17

# Add the PostgreSQL JDBC driver
ADD --chown=jboss:root \
https://jdbc.postgresql.org/download/postgresql-42.7.7.jar \
/opt/jboss/wildfly/standalone/deployments/postgresql-42.7.7.jar

# Copy WildFly CLI commands for data source setup
COPY wildfly-config.cli /opt/jboss/

# Run CLI configuration
RUN /opt/jboss/wildfly/bin/jboss-cli.sh --file=/opt/jboss/wildfly-config.cli

# Deploy your application WAR
COPY target/myapp.war /opt/jboss/wildfly/standalone/deployments/

# Expose HTTP and management ports
EXPOSE 8080 9990

CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0"]

The wildfly-config.cli script configures the data source:

# wildfly-config.cli
embed-server --server-config=standalone.xml

# Install PostgreSQL driver as a module
module add --name=org.postgresql \
--resources=/opt/jboss/wildfly/standalone/deployments/postgresql-42.7.7.jar \
--dependencies=jakarta.api,jakarta.transaction.api

# Register the JDBC driver
/subsystem=datasources/jdbc-driver=postgresql:add( \
driver-name=postgresql, \
driver-module-name=org.postgresql, \
driver-class-name=org.postgresql.Driver)

# Create the data source
/subsystem=datasources/data-source=RatchetDS:add( \
jndi-name=java:/RatchetDS, \
driver-name=postgresql, \
connection-url=${env.DB_URL:jdbc:postgresql://localhost:5432/ratchet}, \
user-name=${env.DB_USERNAME:ratchet}, \
password=${env.DB_PASSWORD:ratchet}, \
min-pool-size=5, \
max-pool-size=20, \
valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker)

stop-embedded-server

WildFly with MySQL

For MySQL, swap the driver and data source configuration:

FROM quay.io/wildfly/wildfly:39.0.1.Final-jdk17

ADD --chown=jboss:root \
https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/8.3.0/mysql-connector-j-8.3.0.jar \
/opt/jboss/wildfly/standalone/deployments/mysql-connector-j-8.3.0.jar

COPY wildfly-mysql-config.cli /opt/jboss/
RUN /opt/jboss/wildfly/bin/jboss-cli.sh --file=/opt/jboss/wildfly-mysql-config.cli

COPY target/myapp.war /opt/jboss/wildfly/standalone/deployments/

EXPOSE 8080
CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0"]
# wildfly-mysql-config.cli
embed-server --server-config=standalone.xml

module add --name=com.mysql \
--resources=/opt/jboss/wildfly/standalone/deployments/mysql-connector-j-8.3.0.jar \
--dependencies=jakarta.api,jakarta.transaction.api

/subsystem=datasources/jdbc-driver=mysql:add( \
driver-name=mysql, \
driver-module-name=com.mysql, \
driver-class-name=com.mysql.cj.jdbc.Driver)

/subsystem=datasources/data-source=RatchetDS:add( \
jndi-name=java:/RatchetDS, \
driver-name=mysql, \
connection-url=${env.DB_URL:jdbc:mysql://localhost:3306/ratchet}, \
user-name=${env.DB_USERNAME:ratchet}, \
password=${env.DB_PASSWORD:ratchet}, \
min-pool-size=5, \
max-pool-size=20, \
transaction-isolation=TRANSACTION_READ_COMMITTED, \
valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker)

stop-embedded-server
MySQL Isolation Level

MySQL defaults to REPEATABLE READ, which causes gap locks on SELECT ... FOR UPDATE that block concurrent inserts. Always set transaction-isolation=TRANSACTION_READ_COMMITTED on the data source, or append ?sessionVariables=transaction_isolation='READ-COMMITTED' to the JDBC URL.

Payara Micro Dockerfile

Payara Micro deploys WARs directly without a full application server install:

FROM payara/micro:6.2025.11-jdk17

# Copy the PostgreSQL driver into the lib directory
COPY --chown=payara:payara postgresql-42.7.7.jar /opt/payara/libs/

# Copy your application WAR
COPY --chown=payara:payara target/myapp.war /opt/payara/deployments/

# Copy post-boot commands for data source configuration
COPY --chown=payara:payara post-boot.txt /opt/payara/

CMD ["--deploymentDir", "/opt/payara/deployments", \
"--postbootcommandfile", "/opt/payara/post-boot.txt", \
"--addLibs", "/opt/payara/libs/postgresql-42.7.7.jar"]
# post-boot.txt
create-jdbc-connection-pool --datasourceclassname=org.postgresql.ds.PGSimpleDataSource --restype=javax.sql.DataSource --property=serverName=${ENV=DB_HOST}:portNumber=${ENV=DB_PORT}:databaseName=${ENV=DB_NAME}:user=${ENV=DB_USERNAME}:password=${ENV=DB_PASSWORD} RatchetPool
create-jdbc-resource --connectionpoolid=RatchetPool java:/RatchetDS

Environment Variables

Configure Ratchet via environment variables in your container:

ENV RATCHET_THREAD_POOL_SIZE_SINGLE=16
ENV RATCHET_POLLER_MIN_DELAY_MS=2000
ENV RATCHET_POLLER_MAX_DELAY_MS=10000
ENV RATCHET_POLLER_BATCH_SIZE=100
ENV RATCHET_JOB_RETENTION_DAYS=30
ENV RATCHET_NODE_HEARTBEAT_INTERVAL_SECONDS=10

Or pass them at runtime:

docker run -d \
-e DB_URL=jdbc:postgresql://db:5432/ratchet \
-e DB_USERNAME=ratchet \
-e DB_PASSWORD=secret \
-e RATCHET_THREAD_POOL_SIZE_SINGLE=32 \
-e RATCHET_POLLER_MIN_DELAY_MS=1000 \
-e RATCHET_POLLER_BATCH_SIZE=100 \
-p 8080:8080 \
myapp:latest

Docker Compose

With PostgreSQL

services:
app:
build: .
ports:
- "8080:8080"
environment:
DB_URL: jdbc:postgresql://postgres:5432/ratchet
DB_USERNAME: ratchet
DB_PASSWORD: ratchet
RATCHET_THREAD_POOL_SIZE_SINGLE: "16"
RATCHET_POLLER_MIN_DELAY_MS: "2000"
RATCHET_POLLER_BATCH_SIZE: "100"
depends_on:
postgres:
condition: service_healthy

postgres:
image: postgres:15
environment:
POSTGRES_DB: ratchet
POSTGRES_USER: ratchet
POSTGRES_PASSWORD: ratchet
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
- ./schema/postgresql-schema.sql:/docker-entrypoint-initdb.d/01-schema.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ratchet"]
interval: 5s
timeout: 5s
retries: 5

volumes:
pgdata:

The postgresql-schema.sql file is applied automatically by the PostgreSQL container on first startup via the docker-entrypoint-initdb.d mechanism.

To extract the DDL from the Ratchet JAR:

# Extract from the store module JAR
jar xf ratchet-store-postgresql-0.1.0-SNAPSHOT.jar ddl/postgresql-schema.sql
cp ddl/postgresql-schema.sql schema/

With MySQL

services:
app:
build: .
ports:
- "8080:8080"
environment:
DB_URL: "jdbc:mysql://mysql:3306/ratchet?sessionVariables=transaction_isolation='READ-COMMITTED'"
DB_USERNAME: ratchet
DB_PASSWORD: ratchet
depends_on:
mysql:
condition: service_healthy

mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: ratchet
MYSQL_USER: ratchet
MYSQL_PASSWORD: ratchet
MYSQL_ROOT_PASSWORD: rootpassword
ports:
- "3306:3306"
volumes:
- mysqldata:/var/lib/mysql
- ./schema/mysql-schema.sql:/docker-entrypoint-initdb.d/01-schema.sql
command: >
--default-authentication-plugin=caching_sha2_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
--transaction-isolation=READ-COMMITTED
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 10

volumes:
mysqldata:

Multi-Node Cluster

For testing clustered deployments locally:

services:
app1:
build: .
hostname: ratchet-node-1
ports:
- "8081:8080"
environment:
DB_URL: jdbc:postgresql://postgres:5432/ratchet
DB_USERNAME: ratchet
DB_PASSWORD: ratchet
RATCHET_NODE_HEARTBEAT_INTERVAL_SECONDS: "10"
depends_on:
postgres:
condition: service_healthy

app2:
build: .
hostname: ratchet-node-2
ports:
- "8082:8080"
environment:
DB_URL: jdbc:postgresql://postgres:5432/ratchet
DB_USERNAME: ratchet
DB_PASSWORD: ratchet
RATCHET_NODE_HEARTBEAT_INTERVAL_SECONDS: "10"
depends_on:
postgres:
condition: service_healthy

postgres:
image: postgres:15
environment:
POSTGRES_DB: ratchet
POSTGRES_USER: ratchet
POSTGRES_PASSWORD: ratchet
volumes:
- pgdata:/var/lib/postgresql/data
- ./schema/postgresql-schema.sql:/docker-entrypoint-initdb.d/01-schema.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ratchet"]
interval: 5s
timeout: 5s
retries: 5

volumes:
pgdata:

There is no RATCHET_CLUSTER_ENABLED switch. Sharing the same store across multiple Ratchet nodes makes the deployment multi-node automatically. One-shot job claiming, recurring-scheduler singleton execution, and destructive startup cleanup are already coordinated through the store. Add a real ClusterCoordinator only if you want cross-node wakeups.

Volume Configuration

Schema Files

Mount your DDL files into the database container's init directory:

volumes:
- ./schema/postgresql-schema.sql:/docker-entrypoint-initdb.d/01-schema.sql

These run automatically on first container creation. For subsequent schema updates, use your migration tool or apply manually:

docker exec -i my-postgres psql -U ratchet -d ratchet < schema/postgresql-schema.sql

Persistent Storage

Always use named volumes for database data to survive container restarts:

volumes:
pgdata:
driver: local

Health Checks

Add a health check endpoint to your application and configure Docker to use it:

HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/health/ready || exit 1

WildFly exposes MicroProfile Health at /health/ready and /health/live when the microprofile-health subsystem is enabled.

Building the Image

# Build the application
mvn clean package -DskipTests

# Build the Docker image
docker build -t myapp:latest .

# Run with Docker Compose
docker compose up -d

# Check logs
docker compose logs -f app

# Verify schema was applied
docker compose exec postgres psql -U ratchet -c "\dt scheduler_*"

See Also