A community-maintained Docker port of the Rock RMS Cloud Print proxy service.
The original application is Windows-only. This image runs as a headless Linux container with a browser-based admin UI, on any Linux server — including a Raspberry Pi or TrueNAS SCALE — on the same network as your label printers.
Rock RMS Server ──WebSocket──▶ This container ──TCP:9100──▶ Local Printer
Commonly used for Rock Check-in label printing where printers are on a local church network but the Rock server is cloud-hosted.
After any of the installs below: open http://<server-ip>:8080 → Settings → enter your Rock server URL and Proxy ID → Save & Reconnect. Then set a PIN under Settings → Security.
# 1. Create a config folder (settings are written here by the web UI)
mkdir -p rock-cloudprint/config
# 2. Create docker-compose.yml
cat > rock-cloudprint/docker-compose.yml <<'EOF'
services:
rock-cloudprint:
image: asdfinit/rock-cloudprint:latest
network_mode: host
volumes:
- ./config:/app/config
restart: unless-stopped
EOF
# 3. Start the container
cd rock-cloudprint
docker compose up -d
# 4. Open the web UI
# Navigate to http://<server-ip>:8080
sudo bash -c 'cat > /etc/systemd/system/rock-cloudprint.service <<EOF
[Unit]
Description=Rock Cloud Print Proxy
After=docker.service
Requires=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/rock-cloudprint
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=300
[Install]
WantedBy=multi-user.target
EOF'
sudo systemctl daemon-reload
sudo systemctl enable rock-cloudprint
TrueNAS SCALE 25.10 supports Docker Compose via Install via YAML in the Apps section.
rock-cloudprint under your pool — e.g. tank/rock-cloudprint, path /mnt/tank/rock-cloudprint.rock-cloudprint, and paste:services:
rock-cloudprint:
image: asdfinit/rock-cloudprint:latest
ports:
- "8080:8080"
volumes:
- /mnt/tank/rock-cloudprint:/app/config
restart: unless-stopped
Adjust the host path if your pool is named differently.
Open http://<truenas-ip>:8080 and configure. Settings persist in the dataset and survive container updates.
Deploy as a Stack — Portainer's equivalent of a Compose file. Stacks → Add stack → Web editor, name it rock-cloudprint, paste, and Deploy the stack:
services:
rock-cloudprint:
image: asdfinit/rock-cloudprint:latest
container_name: rock-cloudprint
ports:
- "8080:8080"
volumes:
- rock-cloudprint-config:/app/config
restart: unless-stopped
volumes:
rock-cloudprint-config:
Use a named volume, not a bind mount. The container runs as UID 1000 (
appuser). A fresh named volume inherits that ownership and stays writable; a host directory Docker auto-creates comes up root-owned and saving settings will fail. For a bind mount,chown -R 1000:1000the host path first and use an absolute path —./configdoes not resolve predictably in Portainer web-editor stacks.
Updating: Portainer will not re-pull
lateston its own. Use Stacks → rock-cloudprint → Editor → Update the stack with Re-pull image ticked.
Two ways. Environment variables take precedence over the web UI.
Web UI (recommended) — Settings tab:
| Field | Description |
|---|---|
| Rock Server URL | Full URL of your Rock instance, e.g. https://origin.church.com |
| Proxy ID | The IdKey (e.g. da0BJR0Bpz) from Rock's Cloud Print Proxy device record |
| Proxy Name | Optional friendly name; defaults to the container hostname |
Environment variables:
environment:
- Url=https://origin.church.com
- Id=da0BJR0Bpz
- Name=Office Proxy
- Password=mypin # optional PIN to protect the web UI
The web UI is open by default. Set a PIN. Anyone who can reach port 8080 can change every setting, and can upload a label and print it to any address they choose — which means the port offers a way to send arbitrary bytes to any host and port on your network. That is inherent to what a print proxy does, but it makes the PIN worth setting even on a network you trust.
Set it via Settings → Security → Set PIN, or with - Password=mypin in the docker-compose.yml environment block. Tokens are in-memory only, so users log in again after a restart.
On a standard Linux server this image uses network_mode: host, so the container reaches printers at their local IP addresses (port 9100) and the web UI needs no port mapping.
On TrueNAS SCALE host networking is not available in the Apps system; use port mapping (ports: - "8080:8080") instead. The container still reaches LAN printers through the host.
macOS / Windows Docker Desktop: host networking is not supported. This image is intended for Linux servers only.
Printers are configured in Rock, not in this app. In Rock's check-in printer settings, set the printer's local IP:
192.168.1.50 — default port 9100192.168.1.50:9100 — explicit portIf your Rock site sits behind Cloudflare or another CDN, use the origin server URL rather than the primary domain. CDNs typically do not forward WebSocket upgrade requests, giving:
The server returned status code '400' when status code '101' was expected.
Your origin URL is often https://origin.yourdomain.com, or the direct IP/hostname of your web server.
docker compose pull
docker compose up -d
Settings are stored in config/ outside the container and are unaffected by updates.
| Tab | Description |
|---|---|
| Dashboard | Connection status, uptime, labels printed counter |
| Logs | Live service log in columns — date and time, log type, source, message — with toggles for filtering by type and by source. Holds the last 2,000 entries, cleared on restart |
| Printers | Test whether a printer can be reached, using the same connection a print uses. Nothing is printed |
| Blank Labels | Store ZPL templates and print pre-coded blank check-in labels. Works with Rock unreachable — see below |
| Settings → Connection | Rock server URL, Proxy ID, Proxy Name |
| Settings → Notifications | Tell Rock when a printer fails. Needs Rock-side setup first — see below |
| Settings → Security | Set, change, or remove web UI PIN |
The running version is shown beside the title, next to a button that switches between the light and dark themes. The page follows your browser's own light/dark setting until you press it, then remembers what you chose.
When check-in goes down, families fill in labels by hand — which only works if a supply of pre-printed blanks already exists. Each blank carries a security code, the same one on the child's tag and the parent's receipt, so pickup still matches when the names are handwritten.
The Blank Labels tab prints them. It needs nothing from the Rock server, and that is the point — the proxy already holds the templates and already talks to the printers, so it still works when Rock does not. Print them in advance, not during the outage.
Three demo templates are included, so there is something to print on a fresh install. They can be deleted, and downloaded again from the repository.
A label is a ZPL file with a placeholder where the code goes: WWW as the whole of a field (what Rock's own legacy labels use), or ??? anywhere inside one. Write it anywhere that produces ZPL and upload it. The label's size is fixed in the file by ^PW and ^LL, so load the stock that matches — the size is shown beside each label. Put anything in cutter mode (^MMC) last in the order, or the cut lands in the middle of a copy.
Codes are random from the digits and capital letters, or sequential. How many characters fit is decided by the label — the three demo templates are designed for three. Longer codes are clipped rather than shrunk, so preview first, or upload a template laid out for more. Sequential numbering is remembered between runs, so two batches printed months apart cannot carry the same codes, and codes are reserved before anything is sent, so a failed run leaves a gap rather than repeating itself later.
A few things worth knowing:
If the printer has a cutter, tick Has cutter beside the address and the proxy sends the cut commands itself — one cut after the last label of each copy. It uses the same commands Rock sends during check-in, so the cut no longer depends on which uploaded label happens to carry ^MMC.
Printers can be saved by name. The Printer box takes an address or the name of a saved printer, and the list under it narrows as you type. Press + to save what is in the box — the dialog asks for a name, the address, and whether that printer has a cutter, so the cutter setting comes back whenever the printer is chosen. The list is held on the proxy, so it is the same for everyone who opens the page, and whoever sets a printer up types its address once while everybody after them picks the name.
Capturing a label from Rock. Rock will not give you the ZPL for a label designed in its own designer, but the proxy is in the middle of every print. Capture from Rock catches one by being an ordinary printer: arm it, then point a printer address in Rock at this machine and print one label. It listens on 9100, only while armed, and stops after one label.
It is for a label designed as a blank — a normal check-in label has nothing to write on, so blanking one is useless. Expect the security code field to arrive empty: Rock substitutes its token before printing, and a test print has no attendance behind it. Fields are picked by position, and the list shows each field's font height, because the code is the one thing printed large.
Legacy labels cannot be captured. Rock's legacy label editor test-prints straight from the Rock server and never consults the proxy, so the job never arrives. Legacy labels are raw ZPL you can already see — copy it out of the editor and upload it instead.
Templates are stored in config/labels/, the record of used codes in config/blank-labels.json, and saved printers in config/printers.json — all inside the folder you already back up.
The proxy can tell your Rock server when a printer fails, or when a print finishes too slowly to be any use, so somebody can be told. Off by default.
This needs setting up in Rock first
The container only posts to a web address. On its own that does nothing. Someone has to create three things in Rock, and the container cannot create any of them:
- A Lava webhook to receive the message.
- A workflow for that webhook to launch.
- The communications inside that workflow — who gets told, and how.
Until those exist, turning notifications on only records problems in the log.
An importable workflow and full setup steps are in the repository, under
docs/rock/. It imports inert and notifies nobody until you set the groups.
Once Rock is ready, configure it in Settings → Notifications: the webhook URL, the shared secret, and a quiet period. Then press Send test notification — it sends a real request and reports exactly what came back, so the URL, the secret, the webhook and the workflow are all proved at setup time rather than during an outage.
If Rock is configured correctly, pressing it may message people. That is how you know it worked.
A few things worth knowing:
https — the shared secret travels in a request header. A plain http URL is refused rather than sent.docker compose pull # pull latest image
docker compose up -d # start / apply updates
docker compose logs -f # live logs
docker compose restart # restart container
docker compose down # stop
github.com/The-Ark-Church/rock-cloud-print-docker
Based on Rock RMS — licensed under the Rock Community License.
Content type
Image
Digest
sha256:6b85ba717…
Size
93.7 MB
Last updated
6 days ago
docker pull asdfinit/rock-cloudprint