Deploy Blocpress in 10 minutes with Docker Compose — Render API, Workbench, and PostgreSQL.
$ 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" }
Port 8080 — Render API (ODT→PDF/RTF), JWT-secured name-based endpoints
Port 8081 — Workbench UI for template designers, validation, approval workflow
Two schemas: workbench (development) and production (approved templates)
production schema on
approval (APPROVED). The Render API reads exclusively from
production — a clean separation between development and production.
docker compose version)docker --version # Docker version 24.x or newer docker compose version # Docker Compose version v2.x or newer
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
.env file contains secrets — never
commit it to Git! Add .env to your .gitignore.
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.
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
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
| 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 |
docker compose up -d
Verify both services are healthy:
# 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
docker compose logs -f render workbench
Once a template designer has submitted a template for approval
(status SUBMITTED), it appears in the Workbench UI under
"Pending approval". As a reviewer:
APPROVEDproduction schema and immediately available under
POST /api/render/{name} — no manual step required.
# 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
docker compose pull docker compose up -d
docker exec blocpress-db pg_dump -U blocpress blocpress > backup_$(date +%Y%m%d).sql
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;
}
}
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
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.
docker-compose.yml with three services: db, render, workbench.env with DB password and JWT configuration createddocker compose up -d started, health checks green/api/render/{name}How do template designers create ODT templates and submit them for approval?
Open tutorialInteractive API documentation after startup at /q/swagger-ui
Latest images: flaechsig/blocpress-render and flaechsig/blocpress-workbench
Test the stateless Render API — no JWT, no database setup required
Home