Filesystem and persistence reference
Default roots are /cms (private) and /www (published). These are container
mount paths, not directories an administrator must create on their workstation.
The CMS writes both; frontend pods mount only /www, read-only. Both generated
PVCs use ReadWriteMany. Separate mounts prevent public handlers from reaching
private source or authentication credentials.
Private volume
/cms/
project/
project.json project identity and created/updated times
settings.json site/SEO/theme/layout settings, after first save
pages/<page-id>.json authoritative editable page documents
components/plugin-<id>.json installed renderer/block package documents
assets/
<asset-id> immutable uploaded binary bytes
<asset-id>.json filename, MIME, raw flag, size and update time
templates/<theme-id>.json custom theme source with encoded asset bytes
drafts/ reserved directory, not a separate page draft store
working/ mutation recovery and temporary publish snapshots
Bundled themes and sample pages are embedded in the CMS binary; custom themes
are on the private PVC. Plugin documents include renderer bytes; they are not
installed in the frontend. A saved draft is a page document in project/pages,
not generated HTML and not a file under working/pages.
Page IDs and asset IDs are generated independently. A page JSON stores title, path, update time, content version/format, format-specific content and optional menu/layout/object metadata. See content and extensions for shapes. Settings are documented in site settings guide.
JSON saves use a same-directory temporary file, sync and atomic rename. Asset
bytes commit before metadata, avoiding visible metadata referencing an absent
file. Page mutations/branch deletes use recovery journals; startup and readiness
recover pending transactions. Publication snapshots hold asset hard links under
working/publish-snapshot-* until complete. Startup removes abandoned snapshots.
Do not edit, prune or back up only selected private files while a writer is active.
Published volume
/www/
release-sequence durable last allocated release number
activation-intent.json prepared/latest activation for crash recovery
activations.json history of publish and rollback pointer moves
releases/
000001/
index.html page path /
about/index.html page path /about
api/assets/<id>/content copied uploaded bytes
api/assets/<id>/metadata.json validated delivery MIME/filename metadata
theme-assets/<sha256>/<filename> selected theme images/fonts, when used
sitemap.xml
robots.txt
release.json completion marker and source revision
current -> releases/000001
The CMS creates current.new and renames it over current on the same filesystem.
The frontend uses /www/current, resolving the symlink again after its freshness
interval; it does not bind a specific release using Kubernetes subPath.
Releases are immutable after completion. release.json stores id, publishedAt,
sourceRevision, result, previousRelease and description. The durable
sequence prevents a cleaned-up release ID from being reused. Failed publications
can leave gaps; gaps are not missing successful releases. IDs run from 000001
to 999999; exhaustion reports an error rather than wrapping or reusing IDs.
Do not reset the sequence to work around exhaustion without an explicit migration
of the entire release/history contract.
activations.json records pointer changes, including rollback, rather than
mutating old release metadata. The intent record protects history across a
crash around pointer activation. Check the active pointer and status after a
late storage error: a reported error does not prove the pointer stayed unchanged.
Do not manually rewrite sequence/history/intent to make an error disappear.
Every uploaded asset joins a snapshot, even if no page links to it. Public releases contain public data only. Renderer executable modules and auth Secrets are not copied into releases. Regular media and raw attachments preserve their same-origin API-shaped URL on the static site; it is a file path there, not a live API call. Older releases may lack asset delivery metadata until republished.
Storage guarantees and capacity
The driver must support symlinks, hard links within private storage, atomic rename within published storage, and file/directory sync. Verify behaviour on your actual RWX provider. A filesystem that reports sync errors cannot safely be assumed to have durably saved a release. Publication first syncs files and subdirectories, writes its durable completion/activation records and then moves the active pointer. See publishing for the release workflow.
The CMS runs as UID/GID 65532. The generated init container recursively chowns
both volume roots using root plus CHOWN; it does not follow symlinks. A driver
that denies chown, including some root-squash exports, needs storage-side
preparation or policy changes before this init step can succeed.
Published storage needs room for the current release, configured history,
a new in-progress release and temporarily retired releases. Media is copied
into each release, so capacity can grow much faster than page text. Retention
is current plus N most recent other releases after a 30-second grace period;
cleanup is asynchronous and logged on failure. Snapshots temporarily retain
private asset bytes even if the editor deletes the original reference.
Deleting a Website retains its PVC objects. This is distinct from a PV's
persistentVolumeReclaimPolicy; deleting a PVC can still remove actual storage.
Changing a Website's storage class/size is not a data migration procedure:
existing claims may be immutable, expansion depends on the driver, so plan and validate storage changes
with the platform administrator using storage-provider procedures. Use backup/restore
for recovery instead of treating release history as source backups.
