Pre-built Runner Docker Images

Deploy workflows without Java/Quarkus development using pre-built Docker images ready for Docker, Kubernetes, or any container platform.

The best experience is still the Java DSL.

If you’re a Java/Quarkus developer, we strongly recommend incorporating Quarkus Flow into your project for:

  • ✅ Compile-time validation and type safety

  • ✅ Full IDE support with code completion

  • ✅ Access to the complete Quarkus ecosystem

  • ✅ Build-time optimizations and native image support

  • ✅ Direct integration with your CDI beans and business logic

Use pre-built images when:

  • You’re a Kubernetes administrator deploying workflows as configuration

  • Your team doesn’t have Java/Quarkus expertise

  • You want to quickly test the Serverless Workflow specification locally

  • You’re deploying workflows managed by external tools or GitOps

Purpose and Use Cases

The Quarkus Flow Runner images provide a ready-to-run workflow execution engine. Instead of building a Quarkus application, you deploy a pre-built container and mount your workflow definitions (YAML/JSON files) as ConfigMaps or volumes.

Common use cases:

  • GitOps workflow deployment - Store workflows in Git, deploy via Kubernetes ConfigMaps, execute via REST API

  • Polyglot teams - Non-Java teams can deploy workflows using the Serverless Workflow specification

  • Quick prototyping - Test workflow definitions locally with Docker in seconds

  • Platform-as-a-Service - Build a workflow execution platform where users deploy only YAML definitions

  • Multi-tenant hosting - Single Runner instance serves workflows across multiple namespaces/teams

The runner/app Module

The runner/app module is the source for all pre-built images. It’s a pre-configured Quarkus application template that:

  • Includes the quarkus-flow-runner extension

  • Provides Maven profiles for each variant (image-minimal, image-standard, image-messaging)

  • Configures sensible defaults for container deployment

  • Supports REST API workflow execution

  • Can be customized with additional dependencies or configurations

Think of it as a starting point - you can clone it, add your own Quarkus extensions, and build custom images tailored to your needs.

Available Image Variants

We distribute three variants of the Runner to Quay.io, each optimized for different deployment scenarios:

Variant Features HA Support Use Case

minimal

MVStore (file-based H2)

❌ No (single replica only)

Development, testing, single-node deployments

standard

PostgreSQL (JPA) + durable-kubernetes

✅ Yes (multi-replica with lease coordination)

Production deployments without event streaming

messaging

PostgreSQL (JPA) + Kafka + durable-kubernetes

✅ Yes (multi-replica with lease coordination)

Production deployments with event-driven workflows

Image naming convention:

quay.io/quarkiverse/quarkus-flow-runner:<VERSION>-<VARIANT>

# Examples (versioned):
quay.io/quarkiverse/quarkus-flow-runner:1.0.0-minimal
quay.io/quarkiverse/quarkus-flow-runner:1.0.0-standard
quay.io/quarkiverse/quarkus-flow-runner:1.0.0-messaging

# Latest tags (point to most recent release):
quay.io/quarkiverse/quarkus-flow-runner:latest-minimal
quay.io/quarkiverse/quarkus-flow-runner:latest-standard
quay.io/quarkiverse/quarkus-flow-runner:latest-messaging
Pin to specific versions in production (1.0.0-standard), use latest-* tags only for development/testing.

Image Structure and Layering

All variants are based on registry.access.redhat.com/ubi9/openjdk-17-runtime and follow a consistent directory structure:

/deployments/
├── config/          # Custom application.properties (mount here)
├── workflows/       # Workflow YAML/JSON files (mount here)
├── data/            # MVStore persistence (minimal variant only)
├── quarkus-run.jar  # Application JAR
├── app/             # Application classes
├── lib/             # Dependencies
└── quarkus/         # Quarkus runtime

Customization via volume mounts:

Mount Point Purpose

/deployments/config

Custom application.properties to override defaults (credentials, URLs, log levels)

/deployments/workflows

Workflow definitions (.yaml, .yml, .json files) - scanned recursively

/deployments/data

Persistent storage for MVStore (minimal variant only) - mount a PVC here

How layering works:

  1. Base configuration - Built into the image (sensible defaults for each variant)

  2. Custom configuration - Your application.properties mounted to /deployments/config (overrides base)

  3. Environment variables - OS-level env vars (override both base and custom config)

This allows zero-config Docker runs while supporting full customization for production.

Quick Start with Docker

Run a workflow engine locally in under 60 seconds:

1. Create a workflow directory:

mkdir workflows && cd workflows

2. Create an example workflow:

cat > hello-world.yaml <<'EOF'
document:
  dsl: "1.0.0"
  namespace: examples
  name: hello-world
  version: "1.0.0"
input:
  schema:
    format: json
    document:
      type: object
      required:
        - name
      properties:
        name:
          type: string
do:
  - greet:
      set:
        message: '${ "Hello, " + .name + "!" }'
EOF

3. Run the minimal variant:

docker run -d \
  --name flow-runner \
  -p 8080:8080 \
  -v $(pwd):/deployments/workflows \
  quay.io/quarkiverse/quarkus-flow-runner:latest-minimal

4. Wait for startup (~5 seconds):

docker logs -f flow-runner
# Wait for: "Quarkus ... started in ..."

5. Execute the workflow:

curl -X POST http://localhost:8080/examples/hello-world/1.0.0 \
  -H "Content-Type: application/json" \
  -d '{"name": "World"}'

6. Check the result:

# Response will be:
# {"message": "Hello, World!"}

7. Cleanup:

docker stop flow-runner && docker rm flow-runner
See REST API documentation for the complete API reference, including workflow execution, querying, and management endpoints.

Building Your Own Runner Image

Use the runner/app module as a template to build custom images with additional Quarkus extensions or configurations.

When to build custom images:

  • Add Quarkus extensions not included in pre-built variants (Redis, MongoDB, custom connectors)

  • Include proprietary dependencies or internal libraries

  • Pre-configure application settings baked into the image

  • Add custom workflow functions or task handlers

Build process:

1. Clone the repository:

git clone https://github.com/quarkiverse/quarkus-flow.git
cd quarkus-flow/runner/app

2. (Optional) Add custom dependencies:

Edit pom.xml to add Quarkus extensions. For example, to add Redis support:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-redis-client</artifactId>
</dependency>

3. (Optional) Customize default configuration:

Edit src/main/resources/application-{variant}.properties to change defaults.

4. Build the variant you need:

# Build minimal variant
make build-minimal

# Build standard variant (PostgreSQL + HA)
make build-standard

# Build messaging variant (PostgreSQL + Kafka + HA)
make build-messaging

This creates images tagged locally as:

quay.io/quarkiverse/quarkus-flow-runner:<VERSION>-minimal
quay.io/quarkiverse/quarkus-flow-runner:<VERSION>-standard
quay.io/quarkiverse/quarkus-flow-runner:<VERSION>-messaging

5. Tag for your registry:

docker tag quay.io/quarkiverse/quarkus-flow-runner:1.0.0-SNAPSHOT-standard \
  myregistry.io/my-flow-runner:latest

6. Push:

docker push myregistry.io/my-flow-runner:latest
See the runner/app README for advanced build options, including native image builds and custom Dockerfiles.

Deploying to Kubernetes

We provide production-ready Kustomize manifests for all three variants. These include PostgreSQL, RBAC for HA coordination, Kafka (messaging variant), and secure defaults.

Prerequisites

1. Build dependencies and process manifests:

The manifests use Maven resource filtering to inject the correct image version. Run this once:

# From project root
git clone https://github.com/quarkiverse/quarkus-flow.git
cd quarkus-flow

# Build dependencies and process k8s manifests
mvn install -pl runner/app -am

This creates filtered manifests in runner/app/target/k8s/ with the correct image tags.

Do NOT deploy from src/main/k8s/ directly!

Source manifests contain Maven placeholders like ${project.version} that must be replaced during build. Always deploy from target/k8s/.

Deploy minimal variant

Single replica, file-based persistence, no external dependencies:

# Deploy to quarkus-flow namespace
kubectl apply -k runner/app/target/k8s/overlays/minimal

# Wait for deployment
kubectl -n quarkus-flow rollout status deployment/quarkus-flow-runner-minimal

# Verify
kubectl -n quarkus-flow get pods

# Port-forward to access locally
kubectl -n quarkus-flow port-forward svc/quarkus-flow-runner-minimal 8080:8080

# Test
curl http://localhost:8080/q/health/ready

Deploy standard variant (with HA)

Multi-replica with PostgreSQL and durable-kubernetes for high availability:

# Deploy (includes PostgreSQL, RBAC, 3 replicas)
kubectl apply -k runner/app/target/k8s/overlays/standard

# Wait for PostgreSQL
kubectl -n quarkus-flow rollout status deployment/postgresql

# Wait for runner
kubectl -n quarkus-flow rollout status deployment/quarkus-flow-runner-standard

# Verify all replicas are running
kubectl -n quarkus-flow get pods -l app=quarkus-flow-runner,variant=standard

# Check HA lease acquisition
kubectl -n quarkus-flow get lease

# Expected output:
# NAME                                       HOLDER
# flow-pool-leader-flow-runner-standard      pod-name-xyz
# flow-pool-member-flow-runner-standard-00   pod-name-abc
# flow-pool-member-flow-runner-standard-01   pod-name-def
# flow-pool-member-flow-runner-standard-02   pod-name-xyz

Deploy messaging variant (with Kafka)

Multi-replica with PostgreSQL, Kafka, and durable-kubernetes:

# Deploy (includes PostgreSQL, Kafka, Zookeeper, RBAC, 3 replicas)
kubectl apply -k runner/app/target/k8s/overlays/messaging

# Wait for all components
kubectl -n quarkus-flow get pods

# Verify Kafka topics are created
kubectl -n quarkus-flow exec -it deployment/kafka -- \
  kafka-topics.sh --list --bootstrap-server localhost:9092
See Kubernetes manifests README for detailed customization options, scaling guides, and production best practices.

Customizing Kubernetes deployments

Add custom application.properties:

apiVersion: v1
kind: ConfigMap
metadata:
  name: runner-custom-config
  namespace: quarkus-flow
data:
  application.properties: |
    # Override logging
    quarkus.log.level=DEBUG

    # Custom database (standard/messaging)
    quarkus.datasource.jdbc.url=jdbc:postgresql://my-postgres:5432/mydb

    # Enable OIDC security
    quarkus.flow.runner.security.type=OIDC
    quarkus.oidc.auth-server-url=https://keycloak.example.com/realms/myrealm

Add a volume mount to the runner deployment:

spec:
  template:
    spec:
      containers:
      - name: runner
        volumeMounts:
        - name: custom-config
          mountPath: /deployments/config
      volumes:
      - name: custom-config
        configMap:
          name: runner-custom-config

Add workflow definitions:

Create a ConfigMap with your workflows:

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-workflows
  namespace: quarkus-flow
data:
  order-processing.yaml: |
    document:
      dsl: "1.0.0"
      namespace: orders
      name: process-order
      version: "1.0.0"
    do:
      - validateOrder:
          # ... your workflow tasks ...

  payment-verification.yaml: |
    document:
      dsl: "1.0.0"
      namespace: payments
      name: verify-payment
      version: "1.0.0"
    # ... workflow definition ...

Mount to /deployments/workflows:

volumeMounts:
- name: workflows
  mountPath: /deployments/workflows
volumes:
- name: workflows
  configMap:
    name: my-workflows

Multiple ConfigMaps can be mounted to the same directory - create separate workflow ConfigMaps per team/namespace and mount them all:

volumeMounts:
- name: team-a-workflows
  mountPath: /deployments/workflows/team-a
- name: team-b-workflows
  mountPath: /deployments/workflows/team-b
volumes:
- name: team-a-workflows
  configMap:
    name: team-a-workflows
- name: team-b-workflows
  configMap:
    name: team-b-workflows

Configuration Reference

All variants support the same configuration properties. Override defaults by mounting a custom application.properties to /deployments/config or via environment variables.

Core settings (pre-configured)

These are already set in the images with sensible defaults:

# Workflow loading
quarkus.flow.runner.enabled=true
quarkus.flow.runner.source.path=/deployments/workflows

# Security (disabled by default - MUST configure for production!)
quarkus.flow.runner.security.type=NONE

# Logging
quarkus.log.console.format=%d{HH:mm:ss} %-5p [%c{2.}] (%t) %s%e%n
quarkus.log.level=INFO
Security is disabled (NONE) by default in pre-built images for ease of local testing. Production deployments MUST configure API_KEY or OIDC authentication.

Variant-specific defaults

Minimal variant (minimal)
# MVStore file-based persistence
quarkus.flow.persistence.mvstore.enabled=true
quarkus.flow.persistence.mvstore.db-path=/deployments/data/flow.mv.db

# No HA support (single replica)
Standard variant (standard)
# PostgreSQL JPA persistence
quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://postgresql:5432/flowdb
quarkus.datasource.username=flowuser
# Password via env var: QUARKUS_DATASOURCE_PASSWORD

# Durable Kubernetes HA
quarkus.flow.durable.kube.enabled=true
quarkus.flow.durable.kube.pool.name=flow-runner-standard
Messaging variant (messaging)
# PostgreSQL + Kafka (same as standard, plus...)
kafka.bootstrap.servers=kafka:9092

# Reactive Messaging for CloudEvents
mp.messaging.incoming.flow-in.connector=smallrye-kafka
mp.messaging.incoming.flow-in.topic=workflow-events
mp.messaging.outgoing.flow-out.connector=smallrye-kafka
mp.messaging.outgoing.flow-out.topic=workflow-results

Common production overrides

Security:

# Option 1: API Key authentication
quarkus.flow.runner.security.type=API_KEY
quarkus.flow.runner.security.api-keys.admin.secret=${FLOW_ADMIN_KEY}
quarkus.flow.runner.security.api-keys.admin.roles=flow-admin

# Option 2: OIDC authentication (requires quarkus-oidc dependency)
quarkus.flow.runner.security.type=OIDC
quarkus.oidc.auth-server-url=https://keycloak.example.com/realms/myrealm
quarkus.oidc.client-id=flow-runner
quarkus.oidc.credentials.secret=${OIDC_CLIENT_SECRET}

Database (standard/messaging):

quarkus.datasource.jdbc.url=jdbc:postgresql://prod-postgres:5432/workflows
quarkus.datasource.username=prod-user
quarkus.datasource.password=${DB_PASSWORD}  # From K8s secret
quarkus.datasource.jdbc.max-size=20

Kafka (messaging):

kafka.bootstrap.servers=kafka-cluster:9092
mp.messaging.incoming.flow-in.topic=production-workflow-events
mp.messaging.outgoing.flow-out.topic=production-workflow-results

Logging:

quarkus.log.level=INFO
quarkus.log.category."io.quarkiverse.flow".level=DEBUG
quarkus.log.category."io.serverlessworkflow".level=DEBUG

Environment variables from secrets (Kubernetes):

env:
- name: QUARKUS_DATASOURCE_PASSWORD
  valueFrom:
    secretKeyRef:
      name: postgresql-secret
      key: password
- name: QUARKUS_FLOW_RUNNER_SECURITY_API_KEY
  valueFrom:
    secretKeyRef:
      name: runner-secret
      key: api-key
- name: OIDC_CLIENT_SECRET
  valueFrom:
    secretKeyRef:
      name: oidc-secret
      key: client-secret

Choosing the Right Variant

Use this decision tree:

Do you need Kafka/messaging integration?
├─ YES → messaging variant
└─ NO
   └─ Do you need high availability (multiple replicas)?
      ├─ YES → standard variant
      └─ NO → minimal variant

Detailed comparison:

Feature minimal standard messaging

Deployment

Single pod

Multi-pod (PostgreSQL + 3 replicas)

Multi-pod (PostgreSQL + Kafka + 3 replicas)

High Availability

❌ No

✅ Yes (lease coordination)

✅ Yes (lease coordination)

Persistence

File-based (MVStore)

PostgreSQL (JPA)

PostgreSQL (JPA)

Event streaming

❌ No

❌ No

✅ Kafka (Reactive Messaging)

Storage

PVC for /deployments/data

PostgreSQL database

PostgreSQL database

Startup time

~5 seconds

~8 seconds

~10 seconds

Memory usage

~200MB

~300MB

~400MB

Production ready?

Single-node only

✅ Yes

✅ Yes

Best for

Dev/test, demos

Production (stateless workflows)

Production (event-driven workflows)

Troubleshooting

Container won’t start

Check container logs:

# Docker
docker logs flow-runner

# Kubernetes
kubectl -n quarkus-flow logs deployment/quarkus-flow-runner-standard

Common issues:

  • No workflows found - Verify /deployments/workflows is mounted and contains .yaml/.yml/.json files

  • Database connection failed (standard/messaging) - Check PostgreSQL is running and credentials are correct

  • Permission denied on /deployments/data (minimal) - Ensure the PVC has correct permissions (UID 185)

Workflows not loading

Verify workflows directory:

# Docker
docker exec flow-runner ls -la /deployments/workflows

# Kubernetes
kubectl -n quarkus-flow exec deployment/quarkus-flow-runner-standard -- \
  ls -la /deployments/workflows

Check file format:

# Files must end in .yaml, .yml, or .json
docker exec flow-runner cat /deployments/workflows/my-workflow.yaml

Examine startup logs for parsing errors:

docker logs flow-runner | grep -A5 "Flow Runner: Loading workflows"

Database connection issues (standard/messaging)

Verify PostgreSQL is running:

kubectl -n quarkus-flow get pods -l app=postgresql
kubectl -n quarkus-flow get svc postgresql

Check connection string in logs:

kubectl -n quarkus-flow logs deployment/quarkus-flow-runner-standard \
  | grep -i datasource

Test connectivity from runner pod:

kubectl -n quarkus-flow exec deployment/quarkus-flow-runner-standard -- \
  sh -c 'nc -zv postgresql 5432'

HA coordination not working (standard/messaging)

Check RBAC permissions:

kubectl -n quarkus-flow get role quarkus-flow-runner-lease-manager
kubectl -n quarkus-flow get rolebinding quarkus-flow-runner-lease-manager
kubectl -n quarkus-flow get serviceaccount quarkus-flow-runner

Verify lease acquisition:

kubectl -n quarkus-flow get lease

# Expected: 1 leader + N member leases
# NAME                                       HOLDER
# flow-pool-leader-flow-runner-standard      pod-abc
# flow-pool-member-flow-runner-standard-00   pod-abc
# flow-pool-member-flow-runner-standard-01   pod-def
# flow-pool-member-flow-runner-standard-02   pod-ghi

Check pod logs for lease errors:

kubectl -n quarkus-flow logs deployment/quarkus-flow-runner-standard \
  | grep -i lease

Common HA issues:

  • No leases acquired - RBAC permissions missing or ServiceAccount not assigned to deployment

  • Lease contention - Multiple pools using the same pool name (check quarkus.flow.durable.kube.pool.name)

  • Pods crash looping - PostgreSQL not ready or database migrations failed

What’s next?

Now that you understand the pre-built Runner images:

Learn the complete Runner API:

Core workflow features:

Production deployment:

Integrations:

Monitoring: