Skip to content

Storage

Odeion uses two classes of filesystem storage: a single data directory for persistent state, and one or more cache directories for transient processing output. They have different contents, different backup needs, and very different performance profiles. Media files themselves always live on your own storage and are never copied into either.

The data directory is where Odeion keeps everything that needs to survive restarts: the embedded Postgres cluster, generated artwork and preview assets, downloaded tooling, backups, and logs.

The data directory is resolved from, in order:

  1. The --data-dir flag on odeion serve and related subcommands.
  2. The ODEION_DATA_DIR environment variable.
  3. A platform default.

All subcommands that touch persistent state (serve, doctor, psql, reset-password, config) accept the same flag and environment variable. They must agree across commands so they point at the same Postgres cluster.

EnvironmentDefault path
Docker image/var/lib/odeion
XDG_DATA_HOME set$XDG_DATA_HOME/odeion
Otherwise~/.local/share/odeion

Odeion creates the root and the standard subdirectories with mode 0700 on first startup.

odeion-data/
├── postgres/ embedded Postgres cluster
├── postgres-bin/ extracted Postgres binaries
├── postgres-cache/ downloaded Postgres archives
├── backups/ archives from Admin > Backups
├── thumbnails/ still thumbnails and animated previews
├── trickplay/ sprite sheets for timeline scrubbing
├── subtitles/ extracted and downloaded subtitle files
├── assets/
│ ├── blobs/ original upstream artwork bytes
│ └── variants/ generated resizes and format conversions
├── bin/ downloaded tooling (ffmpeg, ffprobe)
└── logs/ server and Postgres logs

postgres/ contains the embedded Postgres data cluster when you use the built-in database: WAL segments, relation files, postgresql.conf, pg_hba.conf, and the superuser credential files (.pg_password, .pg_password_ro). Empty when you point Odeion at an external Postgres server.

backups/ stores archives produced by the built-in backup system, named like odeion-backup-2026-04-14-030000.tar.zst. Odeion does not rotate them; retention is your responsibility. See Backups for the mechanics.

thumbnails/ holds still JPEG thumbs (a few KB each) and animated WebP seek previews (typically 1 to 5 MB per item). A 1,000 item library with previews enabled lands in the 1 to 5 GB range.

trickplay/ holds sprite sheets used for timeline scrubbing. Each item gets a sequence of JPEG sheets and a manifest.json describing the grid. Only populated when the trickplay feature is enabled in setup.

assets/ is a content-addressed store for artwork. assets/blobs/ keeps the original bytes downloaded from upstream metadata providers, keyed by SHA-256. assets/variants/ keeps transcoded and resized versions at the specific dimensions the UI requests. Both repopulate on demand.

subtitles/ holds subtitle files extracted from media and fetched from external providers. Plain text, negligible size.

postgres-bin/ and postgres-cache/ hold the extracted Postgres binaries and the downloaded archives they came from. Populated only when using the embedded database, and re-downloaded on next startup if removed.

bin/ is where Odeion drops helper binaries it downloads itself, currently ffmpeg and ffprobe when they are not found on PATH.

logs/ contains rotated server and Postgres log files. Small.

Separate from the data directory, Odeion writes transient processing output to dedicated cache directories. Unlike the data directory, nothing here is load-bearing: caches can be wiped at any time and will rebuild on demand, at the cost of reprocessing work.

There are three cache directories, each individually configurable from the setup wizard and from Admin > Settings:

  • Transcode cache. HLS segments, remuxed streams, and intermediate outputs produced while a client is streaming. Grows quickly during playback. Bounded by a size cap (default 32 GB) that Odeion enforces by evicting the oldest segments.
  • Metadata cache. Cached responses from upstream metadata providers (TMDB, TVDB, FanartTV, and so on). Small, read-heavy, low churn.
  • Subtitle cache. Extracted and downloaded subtitle files. In Docker this is merged into subtitles/ under the data directory; in other deployments it defaults to its own cache subdirectory.

Defaults by environment:

EnvironmentDefault root
Docker/var/cache/odeion/
Otherwise~/.cache/odeion/

Inside that root you get transcode/, metadata/, and (outside Docker) subtitles/ subdirectories.

The two classes of storage have very different requirements.

The data directory does not need fast storage. Postgres benefits modestly from low-latency SSDs, and the thumbnail and asset directories are accessed often enough that a fast disk feels nicer, but there is no hard requirement. Spinning disks, network storage, and shared volumes are all fine for the data directory in typical deployments.

The cache directories, especially the transcode cache, do. During playback Odeion continuously writes new transcode segments and reads them back a moment later to serve the client. Slow or high-latency storage directly translates into:

  • Longer startup times when a stream begins.
  • Stalls and rebuffering during playback when segment writes back up.
  • Sluggish seeks, since the target segment has to be produced and flushed before it can be served.
  • Reduced concurrency, because every active session competes for the same I/O budget.

For the transcode cache, prefer the fastest local storage you have available. NVMe is ideal, SATA SSD is fine, and rotational disks are workable for one or two light sessions but fall over quickly under concurrent transcoding. Network-mounted storage (NFS, SMB) is a poor fit here regardless of headline throughput because the per-request latency dominates.

The metadata and subtitle caches are much less sensitive and can share whatever storage is convenient.

Mount the data directory as a named volume or bind mount so it survives container replacement:

volumes:
- odeion-data:/var/lib/odeion

Put the cache on its own volume so you can wipe it independently, and point it at fast local storage following the guidance above:

volumes:
- odeion-cache:/var/cache/odeion

A common pattern is to back odeion-data with a general-purpose volume on bulk storage and back odeion-cache with a bind mount onto an NVMe device.