Storage Layout
Vylux uses two logical storage roles:
- source store: reads original uploads and upstream source objects from
SOURCE_BUCKETthroughSOURCE_S3_* - media store: reads and writes generated outputs, manifests, keys, previews, and derivative images in
MEDIA_BUCKETthroughMEDIA_S3_*
These two roles may point at the same S3-compatible service or at different backends, but Vylux configures them separately and does not infer one role from the other.
There are also two non-bucket state stores:
- PostgreSQL: job state, workflow results, wrapped encryption keys, and image-cache tracking
- Redis: queue state and rate-limit counters
Storage roles
The source store is treated as immutable input. The media store is where Vylux writes processed artifacts and cleanup targets.
Media-bucket structure
media-bucket/
├── images/{prefix}/{hash}/...
├── videos/{prefix}/{hash}/
│ ├── cover.jpg
│ ├── preview.webp
│ ├── preview.gif
│ ├── master.m3u8
│ ├── audio/und_aac_2ch/
│ │ ├── init.mp4
│ │ ├── playlist.m3u8
│ │ └── seg_*.m4s
│ └── video/
│ ├── r1080_av1/
│ ├── r720_av1/
│ ├── r480_av1/
│ ├── r360_av1/
│ ├── r240_av1/
│ ├── r1080_h264/
│ ├── r720_h264/
│ ├── r480_h264/
│ ├── r360_h264/
│ └── r240_h264/
└── cache/{processing_hash}.{format}
Object-key naming rules
Synchronous image cache
Real-time image results are written to:
cache/{processing_hash}.{format}
processing_hash is derived from:
- the source object key
- width / height / quality
- output format
Asynchronous image thumbnails
image:thumbnail writes to:
images/{hash_prefix}/{hash}/{variant}.{format}
Example:
images/ab/abcdef1234/thumb.webp
images/ab/abcdef1234/large.jpg
Video outputs
Cover images, previews, and HLS artifacts all use:
videos/{hash_prefix}/{hash}/{relative_path}
Examples:
videos/ab/abcdef1234/cover.jpg
videos/ab/abcdef1234/preview.webp
videos/ab/abcdef1234/master.m3u8
videos/ab/abcdef1234/audio/und_aac_2ch/init.mp4
videos/ab/abcdef1234/video/r720_h264/seg_000.m4s
Why the prefix exists
Both image and video object keys use the first two characters of the media hash as a prefix to avoid overloading a single flat namespace:
images/{hash[0:2]}/{hash}/...
videos/{hash[0:2]}/{hash}/...
Playback mapping
Depending on the job type, the media bucket may contain:
- image cache objects
cover.jpgpreview.webppreview.gifmaster.m3u8video/...variant playlists and segmentsaudio/...playlists and segments
The /stream/{hash}/* routes provide stable playback-facing URLs for objects already stored in the media bucket.
Externally:
/stream/{hash}/master.m3u8
/stream/{hash}/audio/{track_id}/playlist.m3u8
/stream/{hash}/video/{variant}/playlist.m3u8
/stream/{hash}/video/{variant}/seg_1.m4s
Internally:
videos/{hash_prefix}/{hash}/{filePath}
where filePath is the remainder after /stream/{hash}/.
What does not live in object storage
Some important data is deliberately not stored in the media bucket:
- wrapped content keys are stored in PostgreSQL, not as bucket objects
wrap_nonceandkek_versionalso live in PostgreSQL- image-cache tracking rows live in PostgreSQL
- job status, progress, results, and retry plans live in PostgreSQL
This split exists because static artifacts belong in object storage, while queryable and mutable metadata belongs in the database.
Source-store role
The source bucket should only hold upstream-provided originals, for example:
uploads/sample.jpg
uploads/sample.mp4
uploads/user-avatars/123.png
Vylux reads from these objects but should not write derived artifacts back into the source bucket.
The /original endpoint is a signed source-object proxy: it reads from the source store and streams the original object without copying it into the media store.
Cleanup scope
When cleanup runs for a media hash, Vylux removes:
images/{prefix}/{hash}/...videos/{prefix}/{hash}/...- related generated-image cache tracking rows
- the corresponding encryption-key rows and job rows