← Back to overview

Tutorial: Sysadmin

Deploy Blocpress in 10 minutes with Docker Compose — Render API, Workbench, and PostgreSQL.

🕑 ~10 minutes 🐳 Docker Compose 🔒 JWT auth
docker compose up -d
$ docker compose up -d
✓ Container blocpress-db        Started
✓ Container blocpress-render    Started
✓ Container blocpress-workbench Started

$ curl http://localhost:8080/q/health
{ "status": "UP" }

$ curl http://localhost:8081/q/health
{ "status": "UP" }
Step 1: Prerequisites Step 2: docker-compose.yml Step 3: Configure JWT Step 4: Start & verify Step 5: Approve first template Step 6: Production

Architecture overview

📑
blocpress-render

Port 8080 — Render API (ODT→PDF/RTF), JWT-secured name-based endpoints

🛠
blocpress-workbench

Port 8081 — Workbench UI for template designers, validation, approval workflow

🗃
PostgreSQL

Two schemas: workbench (development) and production (approved templates)

Deployment principle: Templates are developed in the Workbench and automatically copied via HTTP into the production schema on approval (APPROVED). The Render API reads exclusively from production — a clean separation between development and production.
1

Check prerequisites

  • Docker Engine ≥ 24 and Docker Compose v2 (docker compose version)
  • Free ports: 5432 (PostgreSQL), 8080 (render), 8081 (workbench)
  • Internet access for the initial Docker pull (offline-capable afterwards)
docker --version        # Docker version 24.x or newer
docker compose version  # Docker Compose version v2.x or newer
2

Create docker-compose.yml

Create a new directory blocpress/ and place the following docker-compose.yml inside:

services:

  db:
    image: postgres:16
    container_name: blocpress-db
    environment:
      POSTGRES_DB: blocpress
      POSTGRES_USER: blocpress
      POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme}
    volumes:
      - pg_data:/var/lib/postgresql/data
      - ./init-schemas.sql:/docker-entrypoint-initdb.d/init-schemas.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U blocpress"]
      interval: 5s
      retries: 10

  render:
    image: flaechsig/blocpress-render:latest
    container_name: blocpress-render
    ports:
      - "8080:8080"
    environment:
      QUARKUS_DATASOURCE_JDBC_URL: jdbc:postgresql://db:5432/blocpress?currentSchema=production
      QUARKUS_DATASOURCE_USERNAME: blocpress
      QUARKUS_DATASOURCE_PASSWORD: ${DB_PASSWORD:-changeme}
      MP_JWT_VERIFY_PUBLICKEY: ${JWT_PUBLIC_KEY}
      MP_JWT_VERIFY_ISSUER: ${JWT_ISSUER}
    depends_on:
      db:
        condition: service_healthy

  workbench:
    image: flaechsig/blocpress-workbench:latest
    container_name: blocpress-workbench
    ports:
      - "8081:8081"
    environment:
      QUARKUS_DATASOURCE_JDBC_URL: jdbc:postgresql://db:5432/blocpress?currentSchema=workbench
      QUARKUS_DATASOURCE_USERNAME: blocpress
      QUARKUS_DATASOURCE_PASSWORD: ${DB_PASSWORD:-changeme}
      RENDER_URL: http://render:8080
      MP_JWT_VERIFY_PUBLICKEY: ${JWT_PUBLIC_KEY}
      MP_JWT_VERIFY_ISSUER: ${JWT_ISSUER}
    depends_on:
      db:
        condition: service_healthy

volumes:
  pg_data:

Also create a .env file in the same directory (see step 3 for JWT values):

DB_PASSWORD=your-secure-password-here
JWT_PUBLIC_KEY=...
JWT_ISSUER=https://your-idp.example.com
Security: The .env file contains secrets — never commit it to Git! Add .env to your .gitignore.
3

Configure JWT authentication

Blocpress validates JWTs using a public key (RS256). You can either use your existing Identity Provider (Keycloak, Okta, Azure AD, …) or generate a local key pair for testing.

Option A: Existing Identity Provider

Enter the PEM-encoded public key of your IdP as JWT_PUBLIC_KEY and set JWT_ISSUER to the issuer value from the token claims.

JWT_PUBLIC_KEY=-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...
JWT_ISSUER=https://auth.example.com/realms/myrealm

Option B: Generate a test key pair

For development and initial testing you can generate an RSA key pair yourself:

# Generate private key
openssl genrsa -out private.pem 2048

# Extract public key (→ use as JWT_PUBLIC_KEY)
openssl rsa -in private.pem -pubout -out public.pem

# Write public.pem content into .env:
JWT_PUBLIC_KEY=$(cat public.pem | tr -d '\n' | sed 's/-----BEGIN PUBLIC KEY-----/-----BEGIN PUBLIC KEY-----\\n/' | sed 's/-----END PUBLIC KEY-----/\\n-----END PUBLIC KEY-----/')
JWT_ISSUER=https://dev.local

Reference: All environment variables

Variable Description Default
DB_PASSWORD PostgreSQL password changeme
JWT_PUBLIC_KEY RSA public key (PEM, newlines escaped as \n)
JWT_ISSUER Allowed JWT issuer value
RENDER_URL Internal URL of the Render API (Workbench only) http://render:8080
QUARKUS_PROFILE prod enforces DDL validate instead of update dev
4

Start & health check

docker compose up -d

Verify both services are healthy:

Health checks
# Render API
$ curl http://localhost:8080/q/health
{"status":"UP","checks":[{"name":"Database connections health check","status":"UP"}]}

# Workbench API
$ curl http://localhost:8081/q/health
{"status":"UP","checks":[{"name":"Database connections health check","status":"UP"}]}

# Swagger UI for the Render API
$ open http://localhost:8080/q/swagger-ui

# Workbench UI (open in browser)
$ open http://localhost:8081

View logs

docker compose logs -f render workbench
First start takes longer: Docker images are pulled from the registry once. LibreOffice in the render container needs a moment for font initialization on startup — this is normal.
5

Approve your first template (Reviewer role)

Once a template designer has submitted a template for approval (status SUBMITTED), it appears in the Workbench UI under "Pending approval". As a reviewer:

  1. Select the template and review the details
  2. Check the test data previews
  3. Run the regression test ("Run all")
  4. If there are concerns: "Reject" with a reason
  5. To approve: "Approve" → status changes to APPROVED
Automatic deploy: Approval automatically triggers an HTTP call from the Workbench to the Render API. The template is physically copied into the production schema and immediately available under POST /api/render/{name} — no manual step required.

Render a template via API (after approval)

Name-based rendering
# With JWT token in Authorization header
$ curl -X POST \
    http://localhost:8080/api/render/invoice \
    -H "Authorization: Bearer $JWT_TOKEN" \
    -H "Accept: application/pdf" \
    -H "Content-Type: application/json" \
    -d '{"customer":{"name":"Acme Corp"},"lineItems":[]}' \
    -o invoice.pdf

✓  invoice.pdf  (89 KB)  200 OK
6

Production operation

Apply updates

docker compose pull
docker compose up -d

Back up PostgreSQL data

docker exec blocpress-db pg_dump -U blocpress blocpress > backup_$(date +%Y%m%d).sql

Reverse proxy (nginx)

Recommended for production: nginx as a reverse proxy in front of both services. Workbench at /workbench/, Render API at / or its own subdomain.

server {
    listen 443 ssl;
    server_name blocpress.example.com;

    # Render API
    location /api/render/ {
        proxy_pass http://localhost:8080/api/render/;
        proxy_set_header Host $host;
    }

    # Workbench
    location /workbench/ {
        proxy_pass http://localhost:8081/;
        proxy_set_header Host $host;
    }
}

Scaling

The Render API is stateless — multiple instances can run in parallel. The Workbench requires a single instance (state stored in PostgreSQL).

docker compose up -d --scale render=3
Important: Set QUARKUS_PROFILE=prod in production so Hibernate does not auto-migrate the schema (validate instead of update). Schema updates are then only applied via init-schemas.sql.

✅ Summary

  1. Docker Engine and Docker Compose v2 installed
  2. docker-compose.yml with three services: db, render, workbench
  3. .env with DB password and JWT configuration created
  4. docker compose up -d started, health checks green
  5. As reviewer: approve submitted templates in the Workbench
  6. Approved templates immediately available via /api/render/{name}

Further resources

📑 Designer tutorial

How do template designers create ODT templates and submit them for approval?

Open tutorial
📚 Swagger UI

Interactive API documentation after startup at /q/swagger-ui

Open GitHub
📦 Docker Hub

Latest images: flaechsig/blocpress-render and flaechsig/blocpress-workbench

Docker Hub
📄 Quickstart

Test the stateless Render API — no JWT, no database setup required

Home