Skip to content

Administrator Guide

This guide covers installing, configuring, and operating Plate on a Linux host. The recommended deployment method is Docker Compose. A bare-metal systemd deployment is also supported for hosts without Docker.

Plate requires a running Snackbox backend. It has no built-in content storage and will not start successfully without a reachable Snackbox API.

For a technical overview of how Plate is structured, see docs/ARCHITECTURE.md. For contributing to the codebase, see docs/DEVELOPER.md.


Resource requirements

Figures from baseline benchmarks (500 posts, cache-warm, loopback):

Minimal Recommended
RAM 256 MB 512 MB
CPU 1 vCPU 1 vCPU
Disk 50 MB 50 MB

Memory profile: ~159 MB idle, ~207 MB after cache warm-up, ~376 MB peak under 50 concurrent connections. Throughput: ~28,000 req/s on cached responses.


Pre-built images are published to the GitLab container registry:

registry.gitlab.com/cozybadgerde/applications/plate:latest
registry.gitlab.com/cozybadgerde/applications/plate:<version>

1. Fetch the compose file and env template:

curl -LO https://gitlab.com/cozybadgerde/applications/plate/-/raw/trunk/deployments/docker/docker-compose.yml
curl -LO https://gitlab.com/cozybadgerde/applications/plate/-/raw/trunk/configs/.env.example
cp .env.example .env
chmod 0600 .env

2. Set your Snackbox URL in .env:

SNACKBOX_API_URL=http://<your-snackbox-host>:8080

All other settings have production-safe defaults. See Configuration for the full reference.

3. Start Plate:

docker compose -f docker-compose.yml up -d

4. Verify:

docker compose -f docker-compose.yml logs -f
curl http://localhost:3000/health

Upgrading

docker compose -f docker-compose.yml pull
docker compose -f docker-compose.yml up -d

To pin a specific version, set TAG=v1.2.0 in .env or on the command line.


systemd

Prerequisites

  • Node.js 24 or later (node on $PATH)
  • A release archive or locally built dist/ tree

Install

1. Create a system user and directories:

useradd --system --no-create-home --shell /usr/sbin/nologin plate
mkdir -p /usr/local/lib/plate /etc/plate /var/lib/plate/themes
chown plate:plate /var/lib/plate/themes

2. Deploy the application:

# Unpack the release archive or copy the build output:
cp -r dist node_modules package.json /usr/local/lib/plate/

3. Configure:

cp plate.env.example /etc/plate/plate.env
$EDITOR /etc/plate/plate.env   # set SNACKBOX_API_URL at minimum
chmod 640 /etc/plate/plate.env
chown root:plate /etc/plate/plate.env

The annotated template is in deployments/systemd/plate.env.example.

4. Install and start the service:

cp deployments/systemd/plate.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now plate

5. Verify:

systemctl status plate
curl http://localhost:3000/health

Upgrading

Replace the contents of /usr/local/lib/plate/ with the new release and restart the service:

systemctl restart plate

Reverse proxy

Plate listens on 127.0.0.1:3000 by default and should not be exposed directly to the internet. Place a reverse proxy in front of it for TLS termination and to route /media/* requests to Snackbox.

Caddy

A Caddyfile is provided in deployments/caddy/. It proxies /media/* to Snackbox and all other requests to Plate.

Docker Compose overlay:

docker compose \
  -f docker-compose.yml \
  -f docker-compose.caddy.yml \
  up -d

Set SERVER_NAME to your domain for automatic HTTPS via Let's Encrypt:

SERVER_NAME=plate.example.com docker compose \
  -f docker-compose.yml \
  -f docker-compose.caddy.yml \
  up -d

Bare-metal:

Copy deployments/caddy/Caddyfile to /etc/caddy/Caddyfile, set the SERVER_NAME, PLATE_UPSTREAM, and SNACKBOX_UPSTREAM environment variables (or edit the defaults in the file directly), then reload Caddy:

systemctl reload caddy

Configuration

All settings are read from environment variables. Plate ships with production-safe defaults for everything except SNACKBOX_API_URL.

For Docker, set variables in a .env file next to the compose file. For systemd, edit /etc/plate/plate.env. A fully annotated template is available at deployments/systemd/plate.env.example and configs/.env.example.

Reference

Variable Default Description
SNACKBOX_API_URL http://localhost:8080 URL of the Snackbox API. Set this.
SNACKBOX_TIMEOUT 5000 Snackbox request timeout in milliseconds.
NODE_ENV production Keep production in production.
LISTEN_ADDRESS 127.0.0.1:3000 Address and port Plate listens on. Use 0.0.0.0:3000 in Docker.
SHUTDOWN_TIMEOUT 10000 Drain timeout before force-closing connections on shutdown (ms).
TRUSTED_PROXIES (unset) Comma-separated proxy IPs or CIDR ranges to trust for X-Forwarded-For. Set to your reverse proxy IP(s) in production.
RATE_LIMIT_WINDOW_MS 60000 Rate limit window in milliseconds.
RATE_LIMIT_MAX 100 Maximum requests per window per IP.
THEME_NAME (unset) Lock Plate to a specific theme and disable hot-reload. Leave unset to follow Snackbox settings.
DEFAULT_THEME picnic Fallback theme when none is configured or the requested theme is not found.
THEMES_DIR themes Directory for external themes.
POSTS_PER_PAGE 10 Number of posts per page (1–100).
PAGE_PREFIX pages URL prefix for static pages (/pages/<slug>). Set to empty string for clean URLs (/<slug>). When empty, posts, tags, and health are reserved and cannot be used as page slugs.
CACHE_ENABLED true Enable the in-memory API response cache.
CACHE_TTL_MULTIPLIER 1 Multiplies base content TTLs (post list: 60 s, individual post/page: 300 s). Admin-controlled resources (settings, navigation, tags) use a fixed 15 s TTL. Minimum 1. Use CACHE_ENABLED=false to disable caching entirely.
CACHE_MAX_ENTRIES 100 Maximum cached entries (LRU eviction).
METRICS_ADDRESS 127.0.0.1:9091 Address for the Prometheus metrics endpoint. Never expose publicly.
LOG_LEVEL info Log level: trace | debug | info | warn | error | fatal | silent.
LOG_FORMAT json Log format: json (production) or pretty (development).
LOG_ACCESS true Emit an access log entry per request.
SITE_URL (unset) Public base URL of the site (e.g. https://example.com). Required for sitemap.xml generation; also adds a Sitemap: line to robots.txt. Omit the trailing slash.

Metrics

Plate exposes a Prometheus metrics endpoint at METRICS_ADDRESS (default 127.0.0.1:9091). Add it as a scrape target in your Prometheus configuration:

scrape_configs:
  - job_name: plate
    static_configs:
      - targets: ["localhost:9091"]

The endpoint binds to localhost by default. Do not expose it publicly.


Logging

Plate writes structured logs to stdout. LOG_FORMAT=json is the default in production — set this so your log collector can parse them. Pipe stdout to your preferred aggregation stack (journald, Loki, Fluentd, etc.).

With systemd, logs are available via:

journalctl -u plate -f

External themes

Drop theme directories into THEMES_DIR (default /var/lib/plate/themes for systemd deployments). Plate picks up the active theme from Snackbox settings and hot-reloads on change — no restart required.

To pin a theme regardless of Snackbox settings, set THEME_NAME.

The built-in Picnic theme is always available as a fallback and requires no additional setup.


Built-in routes

Plate serves the following routes automatically. Themes have no control over their output.

Route Description
GET /robots.txt Standard robots file. Includes a Sitemap: directive when SITE_URL is set.
GET /feed RSS 2.0 feed of the 20 most recent posts.
GET /sitemap.xml XML sitemap of all posts, pages, and tags. Returns 404 when SITE_URL is not set.
GET /health Health check endpoint — returns 200 OK when Snackbox is reachable.
GET /_theme/assets/* Static assets served from the active theme's assets/ directory.

Set SITE_URL to your public base URL (e.g. https://example.com) to enable the sitemap and the Sitemap: line in robots.txt.