Pressroom on Docker

Part of PressroomHub. This is the short operator path; the Pressroom repository's docker_deployment.md is the exhaustive reference (custom domains, analytics, newsletter, restore procedures, troubleshooting). For admin accounts specifically, the step-by-step is PressroomAdminAccountRunbook.

Naming. Pressroom was named Roller until release 0.5.0 (2026-10-29, SA-14); releases before 0.5.0 used the old names (ROLLER_*, ghcr.io/jakefearsd/roller, /roller-ui/, rlr_ tokens). A 0.4.x host does not upgrade to 0.5.0: it cuts over once, see "Cutting over a 0.4.x host" below.

Availability. The admin tool described below ships in Pressroom 0.1.11 and later. On 0.1.10 and earlier there is no supported way to create the first admin; upgrade first.

What runs

Everything is in docker-compose.prod.yml, started from one directory (for example /opt/pressroom):

ServiceRole
postgresPostgreSQL 18, the only database
provisionOne-shot: creates the Umami and listmonk databases and applies the schema migrations before anything else starts
appPressroom itself
caddyReverse proxy and TLS, the only ingress
umami, listmonkAnalytics and newsletter
backupDaily pg_dump plus a tarball of the media, search-index and upload volumes
adminTool service for admin accounts. Never started by up or deploy.sh; you invoke it with docker compose run

Install

The host needs Docker with the Compose plugin (v2.17 or newer) and three files from the GitHub Release you are deploying: docker-compose.prod.yml, env.example and deploy.sh. There is no git checkout on the host.

mkdir -p /opt/pressroom && cd /opt/pressroom
curl -LO https://github.com/jakefearsd/pressroom/releases/download/v<version>/docker-compose.prod.yml
curl -LO https://github.com/jakefearsd/pressroom/releases/download/v<version>/env.example
curl -LO https://github.com/jakefearsd/pressroom/releases/download/v<version>/deploy.sh
chmod +x deploy.sh
mv env.example .env

Edit .env. The settings that matter first:

Then run ./deploy.sh. It pulls the images, brings the stack up, waits for app to report healthy, and then runs admin status: if there is no enabled admin it prints the exact create command to run next. A missing admin never fails the deploy.

LAN vs public mode

Behind a tunnel (cloudflared)

On this network, public traffic reaches every host through cloudflared on host cloudflare (HomeLabFleetAndIngress), and Pressroom runs on docker1 (it ran on docker2 until 2026-10-18). For that layout:

  1. Set .env:

    SITE_DOMAIN=:80
    TRUSTED_PROXIES=192.168.5.50
    SITE_HTTP_PORT=8080
    SITE_HTTPS_PORT=8443
    
    • Caddy serves plain HTTP, and the tunnel holds the public certificate.
    • TRUSTED_PROXIES names the connector: the one peer whose X-Forwarded-For and X-Forwarded-Proto Caddy keeps. Name that machine only, never a private range. Separate several addresses with spaces; a malformed value stops Caddy, and the site with it.
    • The two port variables move only the host side of Caddy's ports.
    • Don't set SITE_DOMAIN to the public hostname. Caddy would then redirect to HTTPS, and through the tunnel that redirect loops.
  2. Run ./deploy.sh. Any .env change recreates app as well as caddy, so expect a short restart.

  3. Add the route. In the Cloudflare dashboard, add the public hostname to the tunnel, pointing at http://docker1.lan:<SITE_HTTP_PORT> (8084 on docker1). jakemon's CloudflaredConfigChanged alert fires; that is expected.

  4. Set the site's address. In Admin -> Global Config, set Absolute URL to site (if required) to the public https:// address.

Availability. SITE_HTTP_PORT, SITE_HTTPS_PORT and TRUSTED_PROXIES ship in Pressroom 0.1.12 and later. With an older release, Caddy publishes 80 and 443 and trusts no proxy.

Why TRUSTED_PROXIES. Caddy discards X-Forwarded-For and X-Forwarded-Proto from any peer it does not trust, and writes the peer's address and http in their place. Without the setting, behind the tunnel:

With it, Caddy keeps both headers and appends 192.168.5.50. The app reads X-Forwarded-For right to left and takes the first address that is not private, which is the reader's. Host and CF-Connecting-IP pass through either way, so Umami, which reads CF-Connecting-IP first, counts readers correctly with or without the setting.

Pressroom serves from the root of the host (https://<domain>/<handle>/); do not assume a context path.

The first admin

Self-registration is removed, so a fresh install has no way in until the host owner creates an admin. That is deliberate: whoever can run docker compose on the host already controls .env and the database credentials, and nothing reachable over HTTP can create an admin.

From the deploy directory:

C="docker compose -f docker-compose.prod.yml"
$C run --rm admin status
$C run --rm admin create --username NAME --email ADDRESS

create prompts for the password twice (at least 8 characters). Use an address you read: reset links are sent there. The username may contain ASCII letters and digits only (1-255 characters), and the email address may be at most 255 characters. The account gets the admin and editor roles and can log in immediately. Full detail, including the non-interactive form, is in PressroomAdminAccountRunbook.

Site URL and admin email

Log in at /pressroom-ui/login.rol, then open Admin -> Global Config and set:

On 0.1.12 and earlier, Global Config cannot be saved until at least one weblog exists: the save fails with "Property site.frontpage.weblog.handle is null". Upgrade to 0.1.13, or create a weblog first. Saving on those versions also makes the first weblog the front page.

What / shows

Mail via Brevo

Password-reset and notification mail go through an SMTP relay. Brevo works with these .env settings (placeholders shown; use your own SMTP login and key):

PRESSROOM_MAIL_CONFIGURATIONTYPE=properties
PRESSROOM_MAIL_HOSTNAME=smtp-relay.brevo.com
PRESSROOM_MAIL_PORT=587
PRESSROOM_MAIL_USERNAME=<your Brevo SMTP login>
PRESSROOM_MAIL_PASSWORD=<your Brevo SMTP key>

Leave PRESSROOM_MAIL_SECURITY at its default, starttls. The From address is the Site Administrator's email address, so it must be a sender or domain you have verified in Brevo. Reset mail needs both these settings and a non-blank Site Administrator's email address in Global Config; until both are set, the forgot-password page says the server has no outgoing mail configured (or no site email address set). A change to .env needs docker compose -f docker-compose.prod.yml up -d --force-recreate app; a plain restart does not re-read .env.

Recovery

  1. Forgot password first. Use "Forgot your password?" on the login page (enter the username or email address). The emailed link lasts 1 hour and works once. This needs mail configured as above.

  2. Last resort: reset-password. When mail itself is broken:

    $C run --rm admin reset-password --username NAME
    $C restart app
    

    The restart is required: the running app caches accounts it has already loaded, so a password changed underneath it does not take effect until app restarts. The tool prints this command when it finishes.

Upgrades

From 0.5.0 on, upgrade within the same release family as follows. Download the new release's docker-compose.prod.yml and deploy.sh (both, not just one), bump IMAGE_VERSION in .env, and run ./deploy.sh. Never overwrite .env; diff the new env.example against it when the release notes mention new keys. The script is idempotent and safe to re-run. Take a backup first before crossing a schema change.

curl -LO https://github.com/jakefearsd/pressroom/releases/download/v<new-version>/docker-compose.prod.yml
curl -LO https://github.com/jakefearsd/pressroom/releases/download/v<new-version>/deploy.sh
chmod +x deploy.sh
./deploy.sh

Cutting over a 0.4.x host

0.5.0 renames the database and role (pressroom, was rollerdb/roller), the tables (pressroom_*), the images, the volumes, the compose project and the deploy directory (~/pressroom, was ~/roller). A 0.4.x host therefore does not upgrade; upgrade.sh refuses and points to the repository's docs/dev/pressroom-cutover.md. The cutover script copies the old stack into the new project and renames the copy, so nothing in the old directory or volumes is modified; the rollback (rollback-pressroom.sh) puts the old 0.4.x stack back, losing anything written after the cutover. Keep the old directory, volumes and images for 30 days. Environment variables must be renamed ROLLER_* to PRESSROOM_* (the script does it); a leftover ROLLER_* variable or roller.* system property makes Pressroom refuse to start, by design. The .env keys otherwise stay.

Backups

The backup service runs daily at BACKUP_HOUR (UTC), writing a PostgreSQL dump and a tarball of the media, search-index and upload volumes to the pressroom-backups volume, and deleting files older than BACKUP_RETENTION_DAYS. The dumps contain password hashes: copy them off the host and restrict access. Run one by hand with:

docker compose -f docker-compose.prod.yml exec backup /app/backup/backup.sh

Restore commands are in docker_deployment.md (and in the header of deploy/backup/backup.sh). Test a restore against a scratch copy before you need it.