This repository contains a runnable example that deploys the PDFreactor Web Service as three instances on Kubernetes, in three variants:
| Variant | Pattern | License types |
|---|---|---|
| A — load-balanced | 3 single-replica Deployments, one key per instance, one Service across all | CPU, Pro, Premium, Cluster |
| B — standalone | 3 single-replica Deployments, one key per instance, one Service per instance (no load balancing) | CPU, Pro, Premium, Cluster |
| C — cluster license | one Deployment with replicas: 3, one shared key, one Service |
Cluster only |
With CPU, Pro, and Premium licenses every instance must run with its own
license key — that is what variants A and B ensure. A Cluster license, where
one key may be used by multiple instances, works with every variant; only a
Cluster license permits a single Deployment with replicas: 3 (variant C).
In all variants the instances share one ReadWriteMany volume for the
results of asynchronous conversions, so a conversion started on one instance
can be retrieved from any other.
About these examples: all variants are intentionally small, illustrative examples. They demonstrate the PDFreactor-specific aspects of a multi-instance deployment — they are not reference architectures and not necessarily best practice for your environment. See the deployment guide for the underlying concepts, for what is a PDFreactor requirement vs. an adaptable example, and for how to adapt the example to a real environment.
📘 Documentation: docs/kubernetes-deployment-guide.md — concepts (license types and workload structure, sync vs. async, shared storage vs. sticky sessions), the example's structure, production adaptation (RWX storage, exposure, sizing), and license troubleshooting.
docs/ deployment guide (start here for concepts)
kind/cluster.yaml local kind cluster definition (POC only)
licenses/ place key files here (never committed)
base/ namespace + shared storage (used by all variants)
instances/ the three per-instance Deployments (variants A+B)
variant-loadbalanced/ variant A: one load-balancing Service
variant-standalone/ variant B: one Service per instance
variant-cluster-license/ variant C: one Deployment (replicas: 3) + Service
scripts/ create/deploy/test/teardown helpers
output/ content of the shared volume (POC only)
results/ PDFs downloaded by the smoke tests (POC only)
- Docker (built against Docker Desktop on Windows with WSL 2)
kubectl- kind — installed automatically by
scripts/create-cluster.shif missing
# 1. Provide the license keys
# variants A+B: one Web Service key per instance
cp <your keys> licenses/key-1.txt licenses/key-2.txt licenses/key-3.txt
# variant C: one Cluster license key
cp <your key> licenses/key-cluster.txt
# 2. Create the local cluster (installs kind if necessary)
scripts/create-cluster.sh
# 3. Create the license Secrets from the key files
scripts/create-secrets.sh
# 4. Deploy: "loadbalanced", "standalone", "cluster-license" or "all"
scripts/deploy.sh all
# 5. Verify
scripts/smoke-test-lb.sh # variant A
scripts/smoke-test-standalone.sh # variant B + license & shared-volume proofs
scripts/smoke-test-cluster-license.sh # variant C (shared cluster key)
scripts/check-licenses.sh # which license does each pod run with?
# Teardown
scripts/destroy-cluster.shWith the local kind configuration the services are exposed on localhost:
| URL | Service | Reaches |
|---|---|---|
http://localhost:8080 |
pdfreactor-lb | any per-instance pod (LB) |
http://localhost:8081 |
pdfreactor-1 | instance 1 only |
http://localhost:8082 |
pdfreactor-2 | instance 2 only |
http://localhost:8083 |
pdfreactor-3 | instance 3 only |
http://localhost:8084 |
pdfreactor-cluster | any replica of variant C (LB) |
Inside the cluster the same services are reachable as
pdfreactor-lb.pdfreactor.svc.cluster.local:9423,
pdfreactor-{1,2,3}.pdfreactor.svc.cluster.local:9423 and
pdfreactor-cluster.pdfreactor.svc.cluster.local:9423.
Conversion results of asynchronous conversions appear on the shared volume,
which the local cluster maps to the repository's output/ directory.
smoke-test-lb.sh(variant A): every request through the load balancer returns a valid PDF; the conversions of the test run are distributed across all three pods; an asynchronous conversion works end-to-end through the load balancer; no instance rejects conversions for license reasons.smoke-test-standalone.sh(variant B): each instance is individually addressable and converts on its own; each pod's mounted key matches itslicenses/key-N.txtfile, all three keys are pairwise different, and each instance's license is accepted (serial number logged, no rejected conversions); an asynchronous conversion started on instance 1 is retrieved from instance 2 and deleted via instance 3 (shared-volume proof).smoke-test-cluster-license.sh(variant C): all replicas mount the same key (matchinglicenses/key-cluster.txt) and log the same license serial with no rejected conversions; conversions are distributed across the replicas; an asynchronous conversion works end-to-end through the variant's Service.
Note that a key of the wrong product type is only detectable in the logs or the document content — rejected conversions still return HTTP 200 with an error PDF. See the guide, section 5.
# replace e.g. licenses/key-2.txt (or licenses/key-cluster.txt), then:
scripts/create-secrets.sh
kubectl --context kind-pdfreactor-poc -n pdfreactor rollout restart deploy/pdfreactor-2 # or deploy/pdfreactor-cluster
scripts/smoke-test-standalone.sh # or scripts/smoke-test-cluster-license.sh