Set up a publishing instance
A publishing instance is a small, separate server that shows the forms, pages, documents and slideshows you publish to people who do not sign in to Lunar Paper. Your main installation can stay private: the publishing instance is the only part people outside your organisation reach.
You need a working Lunar Paper installation first, from any of the install guides. The publishing instance can run on the same server or on another one.
The publishing instance does not run PostgreSQL. Your main Lunar Paper installation owns the Platform and Intake databases and their protected setup and upgrade flow. Use the publisher image for your selected platform release; there is no additional Lunar Paper database image to install.
How it connects
Section titled “How it connects”- When you publish something, Lunar Paper sends it to the publishing instance’s address.
- Form submissions and uploaded files go from the publishing instance back to
Lunar Paper at
MAIN_APP_URL, which must be the same as the main installation’sAPP_URL. The publishing instance stores no submissions. - People open published content at the publishing instance’s address.
The address you give the publishing instance decides what else you need:
| Address | Example | What you need |
|---|---|---|
| On your own network | http://192.168.1.20:8081 |
List it in PUBLISHER_PRIVATE_TARGETS on the main installation |
| On the internet | https://forms.example.com |
A domain and a reverse proxy with HTTPS in front of port 8081 |
Before you start
Section titled “Before you start”- Install Lunar Paper and sign in as an administrator.
- Select a published publisher image matching your platform release. The examples
target
0.8.1; its image must be published before it can be pulled. - The main installation’s
APP_URLmust be an address the publishing instance can reach, such ashttp://192.168.1.20:3000. An address withlocalhostdoes not work, because inside the publishing instance it means the instance itself. - Choose an available publisher host port. The default is
8081; setPUBLISHER_HOST_PORTto use another port and register that port in its address.
Allow an address on your own network
Section titled “Allow an address on your own network”Skip this section if the publishing instance has a public https:// address.
Lunar Paper refuses to send content to an address on a private network unless
you list it. Add the address, as host:port, to the main installation’s .env
file:
PUBLISHER_PRIVATE_TARGETS=192.168.1.20:8081Then apply it in the main installation’s folder:
docker compose up -d --waitTo list more than one publishing instance, separate them with commas. For a main installation using HTTPS, use the HTTPS startup command. The publishing instance’s own commands below use its separate folder and Compose file.
Register the publishing instance
Section titled “Register the publishing instance”If the publishing instance is already running, for example because you set it up for another Lunar Paper installation, skip to Connect an instance that is already running.
-
In Lunar Paper, open Administration → Publisher Services → Publisher Inventory.
-
Under Create New Publisher, enter a name and the publishing instance’s address, such as
http://192.168.1.20:8081. Leave the API key empty so Lunar Paper generates one, then select Create Publisher. -
Lunar Paper shows the settings for the publishing instance. Select Copy settings and keep them somewhere safe until the next section.
ASSET_ENCRYPTION_KEYandEMAIL_TRACKING_SECRETare shown only this once. You can show the other settings again at any time with Connection settings.
Running multiple publishing instances
Use a separate folder and register each new publisher to obtain its own connection
settings. In the new publisher’s .env, set:
COMPOSE_PROJECT_NAME=lunarpaper-publisher-testPUBLISHER_HOST_PORT=8082PUBLISHER_DATA=publisher-dataChoose an unused project name and port. Named storage becomes
lunarpaper-publisher-test_publisher-data. Use separate paths if selecting host
folders instead. Keep existing secrets with existing content; do not overwrite
another publisher’s .env. Register the new port in Publisher Inventory and,
for private addresses, in the main app’s PUBLISHER_PRIVATE_TARGETS.
Start the publishing instance
Section titled “Start the publishing instance”-
Create the publisher folder on its server.
Terminal window mkdir lunarpaper-publishercd lunarpaper-publisher -
Create
.envwithnano .envornotepad .env, and paste the settings you copied from Publisher Inventory. AddLUNAR_PAPER_VERSIONfor the matching platform release. Keep existing files and keys when reusing a publisher.View the .env reference
This shows the file structure. Use the full settings copied from Lunar Paper; the shortened secrets below cannot be used. Publisher credentials come from registration, not the main application’s
.envgenerator.LUNAR_PAPER_VERSION=0.8.1# Optional publisher image repository override, without a tag.# PUBLISHER_IMAGE=ghcr.io/lunarpaper/publisherPUBLISHER_HOST_PORT=8081PUBLISHER_BIND_ADDRESS=0.0.0.0PUBLISHER_DATA=publisher-dataMAIN_APP_URL=http://192.168.1.20:3000PUBLISHER_INSTANCE_ID=0b6e3c2a-5d41-4f7e-9a8c-2f1d6e4b7a90API_KEY=4f1c9a0e…b27dPUBLISHER_BROKER_TOKEN=9d2e61b4…0c5aASSET_ENCRYPTION_KEY=c83a7f15…e941EMAIL_TRACKING_SECRET=1b7d4e09…6fa2PUBLISHER_BIND_ADDRESS=0.0.0.0listens on all server interfaces. Use an actual server interface IP to restrict access, or127.0.0.1if a reverse proxy on the same host is the only caller. The main app must be able to reach the registered publisher address. ChangingMAIN_APP_URLdoes not change the publisher’s port.Keep a copy of this file with your backups.
ASSET_ENCRYPTION_KEYprotects the data that published forms show from datasets; if it is lost or changed, those forms have to be published again. If you use email tracking, setEMAIL_TRACKING_SECRETto the same value as the main installation’sEMAIL_TRACKING_SECRET. -
Generate
compose.yaml. Paste this into your terminal. Existing files are kept unchanged.Terminal window & {$ErrorActionPreference = 'Stop'$files = [ordered]@{'compose.yaml' = @'# A Lunar Paper publishing instance. It shows published forms, pages,# documents and slideshows to people outside Lunar Paper.# Keep this file and your .env file together in one folder, then run:# docker compose up -d --wait# To upgrade, change LUNAR_PAPER_VERSION in .env to match your main# installation and run the same command.name: lunarpaper-publisherservices:publisher:image: ${PUBLISHER_IMAGE:-ghcr.io/lunarpaper/publisher}:${LUNAR_PAPER_VERSION:?Set LUNAR_PAPER_VERSION in .env}restart: unless-stoppedports:- "${PUBLISHER_BIND_ADDRESS:-0.0.0.0}:${PUBLISHER_HOST_PORT:-8081}:8080"environment:NODE_ENV: production# The main Lunar Paper address. It must be the same as its APP_URL.MAIN_APP_URL: ${MAIN_APP_URL:?Set MAIN_APP_URL in .env}PUBLISHER_INSTANCE_ID: ${PUBLISHER_INSTANCE_ID:?Set PUBLISHER_INSTANCE_ID in .env}API_KEY: ${API_KEY:?Set API_KEY in .env}PUBLISHER_BROKER_TOKEN: ${PUBLISHER_BROKER_TOKEN:?Set PUBLISHER_BROKER_TOKEN in .env}ASSET_ENCRYPTION_KEY: ${ASSET_ENCRYPTION_KEY:?Set ASSET_ENCRYPTION_KEY in .env}EMAIL_TRACKING_SECRET: ${EMAIL_TRACKING_SECRET:?Set EMAIL_TRACKING_SECRET in .env}# 0 when people connect directly, 1 behind a reverse proxy.PUBLISHER_TRUST_PROXY_HOPS: ${PUBLISHER_TRUST_PROXY_HOPS:-0}volumes:- "${PUBLISHER_DATA:-publisher-data}:/app/data"healthcheck:test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:8080/health"]interval: 30stimeout: 10sretries: 3start_period: 40svolumes:publisher-data:'@}foreach ($file in $files.GetEnumerator()) {$destination = Join-Path . $file.Keyif (Test-Path -LiteralPath $destination) {Write-Host "Kept existing $destination"continue}$stream = [System.IO.File]::Open($ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($destination), [System.IO.FileMode]::CreateNew, [System.IO.FileAccess]::Write)try {$bytes = [System.Text.Encoding]::UTF8.GetBytes($file.Value + [Environment]::NewLine)$stream.Write($bytes, 0, $bytes.Length)} finally { $stream.Dispose() }Write-Host "Created $destination"}}Terminal window (set -euCif [ -e ./compose.yaml ] || [ -L ./compose.yaml ]; thenprintf '%s\n' 'Kept existing ./compose.yaml'elsecat > ./compose.yaml <<'LUNAR_PAPER_FILE'# A Lunar Paper publishing instance. It shows published forms, pages,# documents and slideshows to people outside Lunar Paper.# Keep this file and your .env file together in one folder, then run:# docker compose up -d --wait# To upgrade, change LUNAR_PAPER_VERSION in .env to match your main# installation and run the same command.name: lunarpaper-publisherservices:publisher:image: ${PUBLISHER_IMAGE:-ghcr.io/lunarpaper/publisher}:${LUNAR_PAPER_VERSION:?Set LUNAR_PAPER_VERSION in .env}restart: unless-stoppedports:- "${PUBLISHER_BIND_ADDRESS:-0.0.0.0}:${PUBLISHER_HOST_PORT:-8081}:8080"environment:NODE_ENV: production# The main Lunar Paper address. It must be the same as its APP_URL.MAIN_APP_URL: ${MAIN_APP_URL:?Set MAIN_APP_URL in .env}PUBLISHER_INSTANCE_ID: ${PUBLISHER_INSTANCE_ID:?Set PUBLISHER_INSTANCE_ID in .env}API_KEY: ${API_KEY:?Set API_KEY in .env}PUBLISHER_BROKER_TOKEN: ${PUBLISHER_BROKER_TOKEN:?Set PUBLISHER_BROKER_TOKEN in .env}ASSET_ENCRYPTION_KEY: ${ASSET_ENCRYPTION_KEY:?Set ASSET_ENCRYPTION_KEY in .env}EMAIL_TRACKING_SECRET: ${EMAIL_TRACKING_SECRET:?Set EMAIL_TRACKING_SECRET in .env}# 0 when people connect directly, 1 behind a reverse proxy.PUBLISHER_TRUST_PROXY_HOPS: ${PUBLISHER_TRUST_PROXY_HOPS:-0}volumes:- "${PUBLISHER_DATA:-publisher-data}:/app/data"healthcheck:test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:8080/health"]interval: 30stimeout: 10sretries: 3start_period: 40svolumes:publisher-data:LUNAR_PAPER_FILEprintf '%s\n' 'Created ./compose.yaml'fi)Optionally create compose.yaml manually
Create
compose.yamlbeside.envand paste this configuration. Keep the${VARIABLE}expressions; Compose fills them from.env.compose.yaml # A Lunar Paper publishing instance. It shows published forms, pages,# documents and slideshows to people outside Lunar Paper.# Keep this file and your .env file together in one folder, then run:# docker compose up -d --wait# To upgrade, change LUNAR_PAPER_VERSION in .env to match your main# installation and run the same command.name: lunarpaper-publisherservices:publisher:image: ${PUBLISHER_IMAGE:-ghcr.io/lunarpaper/publisher}:${LUNAR_PAPER_VERSION:?Set LUNAR_PAPER_VERSION in .env}restart: unless-stoppedports:- "${PUBLISHER_BIND_ADDRESS:-0.0.0.0}:${PUBLISHER_HOST_PORT:-8081}:8080"environment:NODE_ENV: production# The main Lunar Paper address. It must be the same as its APP_URL.MAIN_APP_URL: ${MAIN_APP_URL:?Set MAIN_APP_URL in .env}PUBLISHER_INSTANCE_ID: ${PUBLISHER_INSTANCE_ID:?Set PUBLISHER_INSTANCE_ID in .env}API_KEY: ${API_KEY:?Set API_KEY in .env}PUBLISHER_BROKER_TOKEN: ${PUBLISHER_BROKER_TOKEN:?Set PUBLISHER_BROKER_TOKEN in .env}ASSET_ENCRYPTION_KEY: ${ASSET_ENCRYPTION_KEY:?Set ASSET_ENCRYPTION_KEY in .env}EMAIL_TRACKING_SECRET: ${EMAIL_TRACKING_SECRET:?Set EMAIL_TRACKING_SECRET in .env}# 0 when people connect directly, 1 behind a reverse proxy.PUBLISHER_TRUST_PROXY_HOPS: ${PUBLISHER_TRUST_PROXY_HOPS:-0}volumes:- "${PUBLISHER_DATA:-publisher-data}:/app/data"healthcheck:test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:8080/health"]interval: 30stimeout: 10sretries: 3start_period: 40svolumes:publisher-data: -
Validate and start the publisher. Validation does not print secrets.
Terminal window docker compose config --quietif ($LASTEXITCODE -eq 0) {docker compose pullif ($LASTEXITCODE -eq 0) { docker compose up -d --wait }}Terminal window docker compose config --quiet &&docker compose pull &&docker compose up -d --waitOpen the publisher address you registered after startup succeeds. This is different from
MAIN_APP_URL, which points back to the main application. -
Check the connection. In Publisher Inventory, select Health Check on the new publisher. Lunar Paper reports whether it can reach the publishing instance.
Next, allow services to publish to it and publish something. See Control what gets published and Publish, submit, and recover.
Connect an instance that is already running
Section titled “Connect an instance that is already running”A publishing instance that is already running can be registered with the
settings it already has. Its .env file and the content it stores stay as they
are.
-
Make sure the main installation can reach the instance’s address. An address on your own network must be listed in
PUBLISHER_PRIVATE_TARGETS, as described in Allow an address on your own network. -
In Lunar Paper, open Administration → Publisher Services → Publisher Inventory and select Connect existing instance.
-
Enter a name and the instance’s address. Then copy
PUBLISHER_INSTANCE_ID,API_KEYandPUBLISHER_BROKER_TOKENfrom the instance’s.envfile, and select Connect.Lunar Paper asks the instance to confirm the instance ID and API key, and keeps the publisher only if the instance accepts them. It cannot check the broker token, so copy it exactly. If it is wrong, form submissions from the instance fail.
-
Lunar Paper shows the settings it now uses for the instance. If the instance’s
MAIN_APP_URLis not the main installation’sAPP_URL, or itsLUNAR_PAPER_VERSIONis different, change them in the instance’s.envfile and repeat the validation, pull and startup commands in its folder. KeepPUBLISHER_IMAGEas a repository without a tag and preserve the project name, connection settings and storage.
Keep the instance’s own ASSET_ENCRYPTION_KEY and EMAIL_TRACKING_SECRET.
Lunar Paper does not need them, and a new ASSET_ENCRYPTION_KEY means
publishing again every form that shows data from a dataset.
Content the instance holds from another installation does not appear under View Assets. Publish it again from this installation to manage it here.
Put it on the internet with HTTPS
Section titled “Put it on the internet with HTTPS”To show published content to anyone on the internet, give the publishing instance a domain and put a reverse proxy with HTTPS in front of port 8081, such as Caddy or Nginx.
- Point the domain at the server, and set up the reverse proxy to forward to port 8081. The proxy requirements are the same as for your own reverse proxy.
- In the publishing instance’s
.env, addPUBLISHER_TRUST_PROXY_HOPS=1, and rundocker compose up -d --wait. - Register the publishing instance with its
https://address.
Lunar Paper must be able to reach that address, and the publishing instance
must still be able to reach MAIN_APP_URL.
Upgrade
Section titled “Upgrade”Keep the publishing instance on the same version as the main installation.
After you upgrade Lunar Paper, change
LUNAR_PAPER_VERSION in the publishing instance’s .env to the same version
and repeat the validation, pull and startup commands in its folder. Keep
PUBLISHER_IMAGE as a repository without a tag and preserve the project name,
connection settings and storage.
Back up
Section titled “Back up”Published content is stored in the lunarpaper-publisher_publisher-data
volume by default. A custom COMPOSE_PROJECT_NAME changes its prefix; a host
folder selected with PUBLISHER_DATA needs a filesystem backup instead.
Back up the selected storage and the .env file. If the volume is lost,
publish each item again.
Disable or delete a publisher
Section titled “Disable or delete a publisher”Both actions are in Publisher Inventory, and both change only this Lunar Paper installation, never the publishing instance itself. The Internal Publisher is part of Lunar Paper, so it can’t be edited, disabled or deleted. Every change to a publisher is recorded in Administration → Security & Access → Audit Log.
Disable takes a publisher out of use but keeps it registered. While it is disabled:
- Services can’t publish anything new to it, and publishing an item again doesn’t update the copy the instance already holds.
- Lunar Paper refuses everything the instance sends back, so submissions to the forms on it fail.
- Content already published to it stays there, and people can still open it. To take an item down, deselect the publisher in the item’s Publishing locations.
Select Enable to put it back in use. Publish an item again to update its copy on the instance.
Delete removes the publisher from Lunar Paper. Lunar Paper first lists the items published to it and checks whether it can reach the instance:
- If it can, it removes those items from the instance, then deletes the publisher. If an item can’t be removed, the publisher is kept so you can try again.
- If it can’t, the items stay on the instance and remain public. Lunar Paper
deletes the publisher only after you confirm. If you no longer need the
instance, run
docker compose down --volumesin its folder to delete it and everything it holds.
To use the instance again after deleting its publisher, connect it with Connect existing instance.
Troubleshooting
Section titled “Troubleshooting”- Create Publisher shows “Failed to create publisher”. Check the address.
An address on your own network must be listed in
PUBLISHER_PRIVATE_TARGETSexactly ashost:port, as described in Allow an address on your own network, and the main installation must have been restarted since. An address on the internet must start withhttps://. - Connect existing instance says the address is not valid. Enter one port:
the port the server exposes, such as
http://192.168.1.20:8081. When Docker maps that port to the container’s port 8080, leave out the 8080. - Connect existing instance says the address is on a private network. Add
it as
host:porttoPUBLISHER_PRIVATE_TARGETS, as described in Allow an address on your own network. Nothing changes on the publishing instance itself. - Connect existing instance says another publisher already uses this API key. A publisher registered earlier for the same instance holds that key. Delete that publisher first, then connect again.
- Connect existing instance says the instance rejected the instance ID or API
key. Copy
PUBLISHER_INSTANCE_IDandAPI_KEYagain from the instance’s.envfile. If the instance was restarted with new values, use those. - Connect existing instance says the address did not answer like a publishing instance. Check that the address is the publishing instance’s, usually on port 8081, and not the main installation’s.
- The publishing instance keeps restarting. Run
docker compose logs publisher. The usual cause is a missing or shortened setting in.env. Show the settings again with Connection settings. - Forms show, but submissions never arrive.
MAIN_APP_URLmust be exactly the main installation’sAPP_URL, and the publishing instance must be able to reach it. - You lost the settings. Connection settings shows everything except
ASSET_ENCRYPTION_KEYandEMAIL_TRACKING_SECRET. If those are lost too, generate new values withopenssl rand -hex 32, and publish again any forms that show data from a dataset.
