Docs / Configuration & operations

Configuration & operations v0.2.0

picturium reads an optional config.toml at startup and then applies any PICTURIUM__ environment variables on top. This page covers what matters most when you run it: caching, concurrency, CORS and logs. Every single key is listed in the config key reference, and settings that affect responses are explained in Output format & quality.

Config file

  • picturium loads config.toml from the working directory, or whichever file PICTURIUM_CONFIG points to. The file is optional; every key has a default.
  • Settings are layered: built-in defaults, then config.toml, then PICTURIUM__ environment variables, each overriding the one before.
  • You only need the keys you want to change. Anything left out keeps its default.
  • Unknown keys are rejected. A typo in the file or in an environment variable stops startup, and the error names the key.
  • Values are checked at startup too. Bad cache sizes, quality curves, encoder effort, watermark settings and the like stop picturium from starting.
  • The startup log prints the bind address, directories, cache setup, CORS origins, whether signing and health details are on, the libvips settings, and the worker and queue counts.
  • Changing any setting changes every cache key and ETag. Previously cached output is no longer used and gets rendered again on the next request.

Sections

[server] [security] [cors] [vips] [data] [cache]: [cache.memory], [cache.disk] [svg] [pdf] [image] [watermark]: [watermark.image],[watermark.text] [output]: [output.quality_curves], [output.quality_curves.<format>], [output.encoder.<format>] [office] [vector] [video]

The config key reference lists every key with its type, default and what it does. The picturium repository also includes config.toml.example with comments.

A minimal file

Only include what you want to change:

# config.toml

[server]
port = 8080
log_level = "info"

[cache.disk]
limit = 4096

Environment overrides

  • To set a key from the environment, uppercase it, add the PICTURIUM__ prefix, and put a double underscore between levels.
  • Environment variables win over both the defaults and config.toml, which makes them handy for tweaking one deployment without touching the file.
  • Values are parsed, so true, false and numbers work as you’d expect.
  • Lists are comma-separated. That applies to cors.allowed_origins, data.serve, output.format_priority and output.metadata.
  • Only use double underscores between levels. Single underscores inside a key name stay as they are: output.quality_curves.jpeg.min becomes PICTURIUM__OUTPUT__QUALITY_CURVES__JPEG__MIN.
  • Unknown keys fail here too: PICTURIUM__SERVER__PROT=8080 won’t start.
  • PICTURIUM_CONFIG (single underscore) isn’t a config key. It only tells picturium which file to load.

Enter a configuration key to convert it to the environment variable name picturium expects.

PICTURIUM__SERVER__PORT
PICTURIUM__SERVER__PORT=8080

Listen on port 8080.

PICTURIUM__SERVER__WORKERS=8

Allow 8 images to render at once instead of using twice the CPU count.

PICTURIUM__CACHE__MEMORY__ENABLED=false

Disk-only cache.

PICTURIUM__CACHE__DISK__LIMIT=4096

Use a 4 GiB disk cache.

PICTURIUM__CORS__ALLOWED_ORIGINS=https://app.example,https://cdn.example

Two explicit origins (comma-separated list).

PICTURIUM__OUTPUT__QUALITY_CURVES__JPEG__MIN=40

Keep single underscores inside key names; use double underscores only between levels.

PICTURIUM__OUTPUT__ENCODER__AVIF__BITDEPTH=10

10-bit AVIF output.

Cache tuning

Both cache tiers under [cache] are on by default. picturium checks memory first, then disk, and a result too big for either is served without being cached. The same tiers also hold Office, SVG, vector and video intermediates.

cache.dir

Directory used by the disk cache. Only one picturium process can use it at a time.

cache.cache_control

Sent verbatim on every 200. A force=true response sends no-store instead.

cache.memory.enabled

Checks the memory cache before the disk cache.

cache.memory.limit

MiB of total memory held by the memory cache. Entries are charged their own size.

cache.memory.entry_limit

MiB. A render larger than this skips memory: to disk if enabled, otherwise served uncached.

cache.disk.enabled

Enables the disk cache and locks its directory for this process.

cache.disk.limit

MiB of effective capacity, shared by finished responses and Office / SVG / video intermediates. foyer evicts and reclaims automatically.

cache.disk.entry_limit

MiB. Largest single response written to disk, and foyer's block size — the unit it reclaims in.

  • One process per cache directory. With the disk cache on, picturium locks .picturium-cache.lock inside cache.dir. A second process pointed at the same directory won’t start, so give each instance its own.
  • A single disk entry can be at most cache.disk.entry_limit (16 MiB by default). That value is also foyer’s block size, the unit it reclaims space in. A bigger value evicts more entries at once and keeps one block in memory per flusher. Responses too big for disk can still be cached in memory if they fit cache.memory.entry_limit; otherwise they aren’t cached.
  • Changing cache.disk.entry_limit throws away the disk cache. A cache written with one block size can’t be read with another, so picturium keeps a directory per block size and deletes the others at startup.
  • An enabled tier needs a limit above zero, and its entry_limit can’t be larger than its limit. Set enabled = false to switch a tier off. With both off, picturium takes no lock, doesn’t need a cache directory, and renders every request from scratch.
  • The memory tier holds at most cache.memory.limit MiB (256 by default). Each entry counts with its real size, so the budget is the same whether it’s full of thumbnails or large renders. libvips needs working memory for each request in progress on top of that.
  • If picturium finds an old intermediate folder in cache.dir at startup, it removes it.
  • A failed cache read counts as a miss. If writing to the cache fails, the response still goes out fine; the next request just renders again.
  • Nothing expires on a timer. Entries go away through eviction, force=true, or a config change that produces new keys. To empty the disk cache, stop picturium and delete cache.dir.

Caching & force explains how requests use the cache, and the difference between cache= and force=true.

Concurrency & load

server.workers

Maximum number of images rendered at once. 0 means twice the logical CPU count.

server.queue_size

Maximum number of requests waiting for a worker. A full queue rejects new requests.

vips.concurrency

Threads libvips uses inside a single operation. Total threads ≈ workers × vips.concurrency.

  • A request waiting for a worker holds a queue slot, and gives it up once a worker picks it up. In total, picturium handles at most workers + queue_size requests at a time.
  • Beyond that, new requests immediately get 503 Too many requests with Cache-Control: no-store.
  • Rendering is CPU-heavy. A good starting point is keeping workers × vips.concurrency close to your number of CPU cores. More workers help with lots of requests at once; a higher vips.concurrency can speed up a single large image on an otherwise idle server.
  • GET /health returns just a status, always with HTTP 200, so a monitor that should alert on warning or unhealthy needs to read the body. With security.health_token set, sending Authorization: Bearer <token> (or ?token=<token>, which ends up in access logs) adds name, version, total_workers, available_workers, queue_size, available_queue_size and a memory object with process RSS and peak RSS, libvips memory, allocations, open files and cached operations, and memory cache usage and capacity. A wrong token gets 401. See Health & capacity.
  • The worker and queue counts are printed at startup.
healthy

Normal operation.

warning

Free queue slots have fallen below 10% of the worker count.

unhealthy

No free worker and no free queue slot.

CORS & logging

cors.allowed_origins

Use "*" to allow every origin, method, and header. Otherwise, list each allowed origin exactly. Invalid entries are ignored.

server.log_level

Sets log detail to debug, info, warn, or error. RUST_LOG overrides this value when set.

vips.debug

Turns on libvips leak checking and prints the objects still alive at shutdown. Diagnostics only; it adds overhead.

  • If only certain sites should be able to read responses, list their origins: PICTURIUM__CORS__ALLOWED_ORIGINS=https://app.example,https://cdn.example.
  • Logs are plain text on standard output. debug is chatty, so use info or warn in production.
  • RUST_LOG accepts the full tracing filter syntax and overrides server.log_level, for example RUST_LOG=picturium=info,tower_http=warn.
  • vips.debug is for troubleshooting only. Leave it off in production.
  • URL signing (security.signature_enabled, security.signature_secret) is covered in Signed URLs.