Palori

BYOS · Self-hosted rendering

Self-hosted PlantUML rendering for Confluence Cloud

If you write PlantUML in Confluence, your diagram source travels to whichever server renders it. That single fact is the whole reason self-hosted rendering exists — and this article is the setup, end to end: why you might want your own server, how to deploy one, and the configuration mistake that breaks most first attempts.

Where your diagram source actually goes

PlantUML rendering works by encoding the diagram source into a URL. The macro takes your text, compresses and encodes it, and requests the image from a PlantUML server:

@startuml
actor Engineer
participant "Confluence page" as CC
participant "PlantUML server" as PS

Engineer -> CC : publish page
CC -> PS : GET /svg/<encoded diagram source>
PS --> CC : rendered SVG
@enduml

By default that server is the public one at plantuml.com. For a conference-talk sequence diagram, that's fine. For the architecture of your payment system, the question "who operates the server that receives my diagram source?" may have a required answer: we do. That's what bring-your-own-server (BYOS) means here — the app keeps working exactly the same way, but the render requests go to a PlantUML server inside your network.

The open-source part is what makes this possible

Self-hosting only works because the pieces are open. The PlantUML server is an open-source project with an official Docker image — you can run the same renderer the public site runs. And PlantUML for Confluence, the app this site is about, is source-available too: the code that composes the render URL and displays the result is public at github.com/paloriapps/plantuml-confluence. So the entire path your diagram takes — from macro body to rendered SVG — is code you can read and infrastructure you can own. You are not trusting a vendor's description of the data path; you can check it.

The setup

1. Deploy the PlantUML server

The official Docker image is the simplest path — it ships with Tomcat included, so one container gives you a working render server. Run it wherever your internal services live.

2. Put it behind HTTPS

Confluence Cloud requires encrypted connections, so the render server must be reachable over HTTPS. In practice that means a reverse proxy — nginx, Caddy, or Traefik — in front of the container, terminating TLS.

3. Fix the CORS gotcha before it finds you

This is the step that catches most first-time deployments. The browser calls your render server directly from the Confluence page, so the response must carry exactly one Access-Control-Allow-Origin header. The Tomcat bundled in the Docker image already emits one. If your reverse proxy adds another, browsers reject the response as invalid — and the failure looks like the app being broken, not like a proxy misconfiguration. Pick one owner for the header:

Then verify there is exactly one header:

curl -sI https://your-render-server/plantuml/ | grep -i access-control-allow-origin

One line of output, not two.

4. Point the app at your server

In Confluence: Settings → Apps → PlantUML, enter the HTTPS URL of your server. The setting is per-site and applies to every PlantUML macro on it.

What gets stored, and where

Worth stating precisely, because it's the substance behind the privacy claim:

The honest caveat

A self-hosted render server is an ops commitment. It's small — one stateless container — but it's yours: you patch it, you keep it reachable, and if it goes down, diagrams stop rendering until it's back. If your diagrams aren't sensitive, the public server is the right amount of infrastructure, which is to say none. Self-host when the data path requires it, not for its own sake.

If you're arriving here after moving from Data Center and deciding where rendering should happen afterwards, the migration walkthrough covers that decision in context.