ProxMenux 1.2.6.2-beta: OCI containers in the Monitor, docs and fixes

OCI manager Apps
- App tab: containers installed from an OCI image are identified from their
  installation record; the application and image versions are shown and an
  update is detected by image digest; repository link; Refresh data.
- Updates tab for OCI containers: Update and Recreate run the same flow as the
  OCI menu in the Monitor terminal; the pre-update backup can be kept in a
  backup storage; scheduled image updates with an optional minimum age.
- Logs tab: console output of the application, kept on the host
  (lxc.console.logfile + logrotate) and followed live.
- The Proxmox console opens a shell (cmode: shell) when the image has one.
- A damaged image download is fetched again before failing.
- Multi-container applications open at their LAN address; volume mount
  points on block storage report their usage.

Monitor
- Proxmox notifications are delivered to a loopback-only HTTP listener when
  HTTPS is enabled, so they no longer fail certificate verification.
- Log persistence counts recurring patterns only; an ended burst is not
  reported as persistent and its warning clears on its own (#386).
- Proxmox notification config backups are deduplicated and capped at three.
- The update icon on the Apps page opens the container on its Updates tab.
- Version 1.2.6.2-beta and its release notes in every Monitor language.

Docs
- OCI manager Apps and Audit & Report rebuilt as per-page message files,
  with a new page for OCI containers in the Monitor.
- Seven pages fixed where rich-text tags were missing from t.rich.

Translations
- Spanish fixes across the OCI engine, the Monitor and the TUI menus.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
MacRimi
2026-09-25 21:51:12 +02:00
co-authored by Claude Opus 5.5
parent 386d33df6e
commit 4437a671d2
524 changed files with 14459 additions and 3841 deletions
@@ -0,0 +1,114 @@
{
"meta": {
"title": "How an OCI image is translated | ProxMenux",
"description": "From the image repository and its Compose file to a reviewable template, a deployment plan and a native Proxmox VE LXC, without Docker inside."
},
"header": {
"title": "How an OCI image is translated",
"description": "From the image repository and its Compose file to a reviewable template, a deployment plan and a native LXC, without installing Docker inside.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "pipeline",
"title": "The translation pipeline",
"blocks": [
{
"mermaid": {
"chartCode": "flowchart LR\n A[\"{{repo}}\"] --> B[\"Compose + README\"]\n B --> C[\"{{converter}}\"]\n C --> D[\"{{template}}\"]\n D --> E{\"{{blockers}}\"}\n E -- \"{{no}}\" --> F[\"{{review}}\"]\n F --> D\n E -- \"{{yes}}\" --> G[\"{{plan}}\"]\n G --> H[\"pct create\"]\n H --> I[\"{{lxc}}\"]",
"labels": {
"repo": "Image repository",
"converter": "Converter",
"template": "JSON template",
"blockers": "No blockers?",
"no": "No",
"yes": "Yes",
"review": "Review / overlay",
"plan": "Deployment plan",
"lxc": "Native LXC"
}
}
},
{
"p": "The converter reads the image and the Compose file its project publishes and writes a JSON template. A template with untranslated blockers goes through review, where a curated overlay resolves them, before it is published in the catalog. Only templates without blockers are offered for installation."
}
]
},
{
"id": "template",
"title": "What the template keeps",
"blocks": [
{
"cards": {
"items": [
{ "icon": "archive", "title": "Image identity", "body": "Repository, rolling tag, architecture, resolved digest and source revision." },
{ "icon": "braces", "title": "Container contract", "body": "Entrypoint, Cmd, environment, user, working directory, stop signal, ports and volumes." },
{ "icon": "layers", "title": "Proxmox VE translation", "body": "Resources, security, mount points, devices, sysctls, healthchecks and the adaptations each one needs, with their reason." },
{ "icon": "shield", "title": "Compatibility", "body": "Supported keys, untranslated blockers and the state of each validation." }
]
}
}
]
},
{
"id": "sources",
"title": "OCI provides the process; Compose provides the environment",
"blocks": [
{
"table": {
"headers": ["Source", "Example", "Native result"],
"rows": [
["OCI metadata", "<code>Entrypoint</code>, <code>Cmd</code>, <code>User</code>", "Proxmox VE imports them when the CT is created"],
["Docker Compose", "<code>environment</code>, <code>volumes</code>, <code>devices</code>", "LXC environment entries, <code>mpN</code> and <code>devN</code>"],
["ProxMenux profile", "GPU, healthcheck, credentials", "questions and reviewed adaptations"],
["User", "VMID, storage, network", "the instance contract"]
]
}
}
]
},
{
"id": "install",
"title": "What happens during an installation",
"blocks": [
{
"steps": {
"items": [
{ "title": "Resolve", "body": "The registry is queried, the host architecture is selected and the effective digest of the rolling tag is fixed." },
{ "title": "Download and verify", "body": "Skopeo downloads the image as an OCI archive, and every layer is checked against its digest and decompressed before anything is created. A damaged download is fetched a second time before the installation stops." },
{ "title": "Build", "body": "<code>pct create</code> builds the rootfs from the archive and keeps the official process metadata of the image." },
{ "title": "Connect", "body": "The declared volumes, network, environment, devices and security profiles are attached." },
{ "title": "Console", "body": "The console output of the container is kept on the host, and the Proxmox VE console opens a shell when the image ships one." },
{ "title": "Check", "body": "The first start waits for an address and for the service to answer; a failure is not reported as a successful installation." },
{ "title": "Register", "body": "The effective configuration is written to the instance contract that updates and recreations use." }
]
}
}
]
},
{
"id": "example",
"title": "Example: an image with /config and /downloads",
"intro": "A common Compose definition and the Proxmox VE configuration it becomes. The paths the application expects do not change.",
"blocks": [
{
"codeGrid": {
"items": [
{
"title": "Docker Compose",
"code": "image: lscr.io/linuxserver/example:latest\nenvironment:\n - PUID=1000\n - PGID=1000\nvolumes:\n - config:/config\n - /srv/downloads:/downloads\nports:\n - 8080:8080"
},
{
"title": "/etc/pve/lxc/VMID.conf (excerpt)",
"code": "entrypoint: /init\nmp0: local-lvm:vm-VMID-disk-1,mp=/config,backup=1,size=8G\nmp1: /srv/downloads,mp=/downloads\nnet0: name=eth0,bridge=vmbr0,ip=dhcp,type=veth\nlxc.environment.runtime: PUID=1000\nlxc.environment.runtime: PGID=1000"
}
]
}
},
{
"p": "<code>mp0</code> is a second disk that belongs to the container, named <code>vm-VMID-disk-N</code> on the selected storage. It is mounted at <code>/config</code> and, with <code>backup=1</code>, it is part of the container backup. <code>mp1</code> creates no disk: it binds the host directory <code>/srv/downloads</code> to <code>/downloads</code> inside the LXC. Port 8080 is not mapped: the LXC has an address of its own and the service answers on it."
}
]
}
]
}
@@ -0,0 +1,134 @@
{
"meta": {
"title": "Install an image that is not in the catalog | ProxMenux",
"description": "Translate an OCI image from a Compose file, a URL, a docker run command or an image reference, and review what ProxMenux can reproduce before installing it."
},
"header": {
"title": "Install an image that is not in the catalog",
"description": "An image of your own is translated from its Compose file, a docker run command or its reference alone, and the result is shown for review before any LXC is created.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "intro",
"blocks": [
{
"calloutInfo": {
"title": "Same converter, same installer",
"body": "The option <strong>Install an image that is not in the catalog</strong> uses the converter and the installer of the catalog templates. The difference is that the contract is generated at that moment from the definition given, and validated before any LXC is created."
}
}
]
},
{
"id": "sources",
"title": "How the image is described",
"intro": "The first screen asks how the image is described. There are four ways to give a complete definition and a fifth that uses only the image name.",
"blocks": [
{
"table": {
"headers": ["Option", "What it reads"],
"rows": [
["Paste its Compose file in the terminal", "The YAML is pasted in the terminal and ends with Ctrl+D on an empty line."],
["Read its Compose file from a file of this host", "A path on the node, <code>/root/docker-compose.yml</code> by default. The file must exist and be at most 256 KiB."],
["Download its Compose file from an address", "An <code>http://</code> or <code>https://</code> address that serves the raw YAML. At most 256 KiB are downloaded."],
["Paste its docker run command in the terminal", "The command published by the project. Ports, environment, volumes, devices, capabilities, user, shared memory and the other supported options are translated."],
["Only the image reference, with no Compose file", "A reference such as <code>ghcr.io/user/application:latest</code>. The registry is queried and the OCI metadata of the image is kept."]
]
}
},
{
"figure": {
"src": "/oci-manager/custom-source-menu.png",
"alt": "Menu that asks how an image that is not in the catalog is described",
"caption": "The five ways to describe an image that is not in the catalog."
}
}
]
},
{
"id": "reference-only",
"title": "What an image reference alone provides",
"intro": "An image carries its process metadata, but not everything a Compose file usually adds around it.",
"blocks": [
{
"table": {
"headers": ["Read from the image", "Not in the image", "Consequence"],
"rows": [
["<code>Entrypoint</code>, <code>Cmd</code>, <code>User</code>, <code>WorkingDir</code> and embedded environment", "Variables that only appear in the documentation", "They are added during the installation or a recreation"],
["Volumes declared by the image", "Host directories that only a Compose file names", "Additional paths are added before installing"],
["<code>EXPOSE</code> ports", "The URL, protocol or functional healthcheck", "The port and the way the service is checked are confirmed"],
["Architectures and digest in the registry", "Devices, privileges or external dependencies", "None of them is enabled without a definition that asks for it"]
]
}
}
]
},
{
"id": "review",
"title": "Review before installing",
"intro": "After translating the definition, ProxMenux shows what it understood before asking for VMID, resources or storage.",
"blocks": [
{
"steps": {
"items": [
{ "title": "Analyse", "body": "Services, image, ports, paths, environment, devices, security and healthcheck are read." },
{ "title": "Query the registry", "body": "The image must exist in a public registry, and its published architectures are listed." },
{ "title": "List what is not applied", "body": "Labels, Docker networks, Swarm settings and other keys that have no effect on an LXC are listed." },
{ "title": "Block what cannot be translated", "body": "A key with no safe equivalent is shown under <strong>What cannot be translated</strong> and the installation does not continue. Nothing is dropped silently." },
{ "title": "Ask for secrets", "body": "Variables whose names read as a password, token or key become sensitive questions of the installation." }
]
}
},
{
"figure": {
"src": "/oci-manager/custom-review.png",
"alt": "Summary of what ProxMenux understood from a Compose file",
"caption": "The summary shown before the installation asks for VMID and resources."
}
},
{
"p": "After accepting the summary, the flow is the one of the catalog: name, default or advanced mode, VMID, CPU, memory, network, start with the node, persistent paths, additional paths, compatible devices and a final summary."
}
]
},
{
"id": "examples",
"title": "Input examples",
"blocks": [
{
"codeGrid": {
"items": [
{
"title": "Compose",
"code": "services:\n app:\n image: ghcr.io/example/app:latest\n ports:\n - \"8080:8080\"\n volumes:\n - ./config:/config\n - /srv/media:/media\n environment:\n TZ: Europe/Madrid"
},
{
"title": "docker run",
"code": "docker run -d \\\n --name app \\\n -p 8080:8080 \\\n -e TZ=Europe/Madrid \\\n -v app-config:/config \\\n -v /srv/media:/media \\\n ghcr.io/example/app:latest"
}
]
}
}
]
},
{
"id": "limits",
"title": "Definitions that are not installed",
"blocks": [
{
"list": {
"items": [
"Images in private registries: registry credentials are not requested.",
"A Dockerfile without a published image: the image has to be built and published in an OCI registry first.",
"A Compose file with several services: it is blocked because it describes more than one image. This option installs a single container.",
"<code>configs</code>, external secrets or device formats that cannot be translated unambiguously.",
"Shell substitutions such as <code>$(command)</code>: the resulting value has to be written instead.",
"<code>docker run</code> options that are not recognised, and <code>--env-file</code>: an explicit Compose file is read instead."
]
}
}
]
}
]
}
@@ -0,0 +1,232 @@
{
"meta": {
"title": "Devices and acceleration | ProxMenux",
"description": "How OCI manager Apps passes GPU, NVIDIA, Coral, USB, FUSE and other devices to an OCI container as validated native Proxmox VE resources."
},
"header": {
"title": "Devices and acceleration",
"description": "GPU, NVIDIA, Coral, USB, FUSE and block devices become validated native Proxmox VE resources of the container.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "principle",
"title": "The device the application needs, not the whole host",
"blocks": [
{
"p": "A device requested by the Compose file or by the application profile becomes a concrete <code>devN</code> entry or LXC mount. Asking for a GPU, a USB device or a Coral does not make the container privileged."
},
{
"flow": {
"nodes": [
{ "label": "Host inventory", "detail": "/dev/dri/renderD128\nGID 993 · Intel" },
{ "label": "ProxMenux", "detail": "vendor and\npermissions checked" },
{ "label": "LXC", "detail": "same device\neffective GID" }
]
}
}
]
},
{
"id": "origin",
"title": "Where the device request comes from",
"blocks": [
{
"table": {
"headers": ["Source", "What is read", "What the installer does"],
"rows": [
["Docker Compose", "<code>devices</code>, <code>group_add</code>, <code>deploy.resources</code> and NVIDIA requests", "Each requirement becomes a device request shown for review"],
["Catalog profile", "The GPU, Coral, OpenCL, USB or FUSE support the application actually has", "Only the options validated for that image are offered"],
["Image metadata and documentation", "VA-API, Selkies, LinuxServer mods or the NVIDIA runtime", "The documented variables and preparation are added"],
["User selection", "CPU only, Intel/AMD, OpenCL, NVIDIA or an optional device", "The selection is stored in the instance contract"]
]
}
},
{
"calloutWarning": {
"title": "Detected devices are not attached on their own",
"body": "The host is inventoried, but only devices declared by the Compose file or by a compatible profile are offered and attached. A GPU, USB dongle or Coral present on the host is not exposed to every LXC."
}
}
]
},
{
"id": "identify",
"title": "How the host device is identified",
"intro": "Before the LXC is modified, the device is read on the host and matched against the chosen profile.",
"blocks": [
{
"table": {
"headers": ["Type", "Identity", "Validation"],
"rows": [
["Intel/AMD DRM", "<code>/dev/dri/renderD*</code> and <code>/sys/class/drm/NODE/device/vendor</code>", "A character device with vendor <code>0x8086</code> (Intel) or <code>0x1002</code> (AMD)"],
["AMD OpenCL", "The render node, plus <code>/dev/kfd</code> when the profile needs it", "Existence, type, vendor, permissions and declared compatibility"],
["NVIDIA", "<code>nvidia-smi</code> and <code>nvidia-container-cli</code>", "GPU, UUID, PCI bus, driver version, Toolkit, <code>/dev/nvidia*</code> nodes, binaries and libraries"],
["Coral PCIe/M.2", "<code>/dev/apex_N</code> and its link in <code>/sys/dev/char/MAJOR:MINOR</code>", "Character node, major/minor, owner, GID and permissions"],
["USB and serial", "<code>/dev/ttyUSB*</code>, <code>/dev/ttyACM*</code> or <code>/dev/bus/usb/BBB/DDD</code>", "Character node; for USB also vendor, product and serial when sysfs publishes them"],
["KVM, TUN, FUSE, video and generic SCSI", "<code>/dev/kvm</code>, <code>/dev/net/tun</code>, <code>/dev/fuse</code>, <code>/dev/videoN</code> or <code>/dev/sgN</code>", "Supported path, node type and effective permissions"],
["Optical drive", "<code>/dev/srN</code>", "A block device"]
]
}
}
]
},
{
"id": "install",
"title": "What happens during the installation",
"blocks": [
{
"steps": {
"items": [
{ "title": "The template offers its profiles", "body": "For example CPU only, Intel/AMD VA-API, AMD OpenCL, Intel OpenCL or NVIDIA. The options belong to the image, not to a common menu." },
{ "title": "A profile is chosen", "body": "It defines the device nodes, environment, mods or runtime the application needs." },
{ "title": "A path is proposed", "body": "For DRM, <code>/dev/dri/renderD128</code>, which can be changed on a host with several render nodes. For USB or serial, the concrete node is selected." },
{ "title": "Validation", "body": "Existence, type, allowed vendor, permissions and GID are checked. A mismatch stops the operation." },
{ "title": "The contract is written", "body": "Path, mode, GID, write access and profile are recorded for updates and recreations." },
{ "title": "Attach and test", "body": "<code>pct set</code> adds the <code>devN</code> entry and access is then checked inside the LXC. LinuxServer images are also checked as user <code>abc</code>." }
]
}
}
]
},
{
"id": "config",
"title": "How it appears in the LXC configuration",
"intro": "Illustrative values: <code>dev0</code> and <code>dev1</code> are the free slots Proxmox VE assigns, and <code>renderD128</code>, <code>apex_0</code> and the GID depend on the hardware of the node.",
"blocks": [
{
"codeGrid": {
"items": [
{ "title": "Intel/AMD VA-API", "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=993,deny-write=0" },
{ "title": "Coral PCIe/M.2", "code": "dev0: path=/dev/apex_0,mode=0660,gid=GID,deny-write=0" },
{ "title": "A specific USB device", "code": "dev0: path=/dev/bus/usb/003/004,mode=0660,gid=GID,deny-write=0" },
{ "title": "AMD OpenCL", "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=GID,deny-write=0\ndev1: path=/dev/kfd,mode=0660,gid=GID,deny-write=0" }
]
}
},
{
"p": "The GID is read with <code>stat</code> on the host and written to the <code>devN</code> entry; the <code>render</code> and <code>video</code> groups are not assumed to have a fixed number. The device keeps the same <code>/dev</code> path inside the LXC, where the application's own mechanisms look for it."
}
]
},
{
"id": "profiles",
"title": "Profiles an image can offer",
"blocks": [
{
"table": {
"headers": ["Profile", "Translation", "Offered when"],
"rows": [
["Intel/AMD VA-API", "<code>/dev/dri</code> render node", "The application supports video acceleration"],
["OpenCL", "Render node, <code>/dev/kfd</code> when needed and the official mod", "The image or profile documents it"],
["NVIDIA", "Driver devices and libraries of the host", "The host has a working driver and the NVIDIA Container Toolkit"],
["Coral", "<code>/dev/apex_0</code> or the USB bus", "The profile declares Coral support (Frigate)"],
["USB, serial, FUSE", "A single device, a validated tree or an LXC feature", "The contract asks for it"]
]
}
}
]
},
{
"id": "nvidia",
"title": "NVIDIA",
"blocks": [
{
"calloutWarning": {
"title": "Node requirement: NVIDIA Container Toolkit",
"body": "A working driver on Proxmox VE is not enough to give an NVIDIA GPU to an OCI image. OCI manager Apps uses <code>nvidia-container-cli</code>, from the NVIDIA Container Toolkit, to identify the devices and to obtain the binaries and libraries that match the loaded driver."
}
},
{
"p": "The <nvidiaLink>ProxMenux NVIDIA installer</nvidiaLink> installs the NVIDIA Container Toolkit from the official NVIDIA repository together with the driver. It checks its four packages, validates <code>nvidia-container-cli</code> and records the result in the change journal of <auditLink>Audit & Report</auditLink>."
},
{
"p": "These two commands on the host show whether the driver and the Toolkit are available:"
},
{
"shell": { "code": "nvidia-smi -L\nnvidia-container-cli --version" }
},
{
"p": "On a host where the driver was installed by other means, the Toolkit is installed from the official stable repository:"
},
{
"shell": {
"code": "apt-get update\napt-get install -y --no-install-recommends ca-certificates curl gnupg2\n\ncurl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \\\n | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg\n\ncurl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \\\n | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \\\n > /etc/apt/sources.list.d/nvidia-container-toolkit.list\n\napt-get update\napt-get install -y nvidia-container-toolkit libnvidia-container-tools"
}
},
{
"p": "The inventory OCI manager Apps uses is the output of:"
},
{
"shell": { "code": "nvidia-container-cli list --device all --libraries --binaries --firmwares --ipcs" }
},
{
"calloutInfo": {
"title": "Docker runtime configuration is not involved",
"body": "The containers are native LXCs and no Docker daemon is used, so <code>nvidia-ctk runtime configure --runtime=docker</code> plays no part: ProxMenux queries <code>nvidia-container-cli</code> directly and writes the LXC devices and mounts. The commands and supported platforms are maintained in the <toolkitLink>NVIDIA Container Toolkit installation guide</toolkitLink>."
}
},
{
"cards": {
"items": [
{ "icon": "cpu", "title": "Inventory from the driver", "body": "<code>nvidia-container-cli</code> lists the device nodes, binaries, firmware and libraries of the installed driver." },
{ "icon": "refresh", "title": "No fixed version", "body": "The template does not name library files. The profile is generated from the current host." },
{ "icon": "shield", "title": "Read-only mounts", "body": "The host libraries are mounted read-only instead of being copied into the container." },
{ "icon": "hardDrive", "title": "Driver changes", "body": "After a driver change the inventory is generated again before the affected LXCs start." }
]
}
},
{
"code": {
"title": "NVIDIA result (simplified)",
"code": "devN: path=/dev/nvidia0,...\ndevN: path=/dev/nvidiactl,...\ndevN: path=/dev/nvidia-uvm,...\nlxc.mount.entry: HOST_LIBRARY CONTAINER_LIBRARY none ro,bind,create=file 0 0"
}
},
{
"p": "Passing only <code>/dev/nvidia0</code> is not enough. The user-space components of the loaded driver are mounted read-only, and <code>nvidia-smi</code> then runs inside the LXC to compare GPU, UUID, PCI bus and version with the host inventory."
}
]
},
{
"id": "usb",
"title": "USB, serial and USB Coral",
"blocks": [
{
"calloutWarning": {
"title": "USB numbering can change",
"body": "A path such as <code>/dev/bus/usb/003/004</code> can change when the device is reconnected or the host restarts. The profile records vendor, product and serial when they are available, but a new bus address is not remapped automatically."
}
},
{
"p": "A peripheral is given by its concrete node: <code>/dev/ttyUSB0</code>, <code>/dev/ttyACM0</code>, <code>/dev/apex_0</code> or <code>/dev/bus/usb/BBB/DDD</code>. Passing the whole of <code>/dev</code> is not accepted. Coral is offered only to applications whose profile declares it."
}
]
},
{
"id": "trees",
"title": "Device trees and LXC features",
"blocks": [
{
"table": {
"headers": ["Request", "Translation", "Scope"],
"rows": [
["<code>/dev/dvb</code>, <code>/dev/snd</code> or <code>/dev/bus/usb</code>", "Each character node of the tree gets its own <code>devN</code> entry with the host mode and GID", "Only the requested tree, not the rest of <code>/dev</code>"],
["<code>/dev/fuse</code>", "The node and, when the profile needs it, the <code>fuse=1</code> feature", "FUSE alone does not publish mounts to other LXCs"],
["<code>/dev/net/tun</code>", "A <code>devN</code> entry at the same path inside the LXC", "The VPN or network configuration stays in the application"],
["<code>/dev/kvm</code>", "A validated <code>devN</code> entry", "Offered only when the contract asks for it"]
]
}
}
]
},
{
"id": "security",
"title": "Confirmations by level of risk",
"blocks": [
{
"p": "Concrete devices, optional privilege, required privilege, AppArmor or seccomp relaxation and access to the host PID namespace are treated as separate cases, not under one generic privileged label. Each option with a risk is explained and confirmed during the installation."
}
]
}
]
}
+116
View File
@@ -0,0 +1,116 @@
{
"meta": {
"title": "OCI manager Apps | ProxMenux",
"description": "Run official OCI images as native Proxmox VE LXC containers, with persistent data, devices, multi-container applications and transactional updates."
},
"header": {
"title": "OCI manager Apps",
"description": "Official OCI images run as native Proxmox VE LXC containers. The image stays the one its maintainer publishes; ProxMenux reproduces the environment Docker Compose would have created around it.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "model",
"blocks": [
{
"calloutInfo": {
"title": "One OCI image, one native LXC",
"body": "No Docker engine runs inside the containers. Proxmox VE imports the image's filesystem and its OCI metadata, and the application becomes the main process of a native LXC, managed with the same tools as any other container of the node."
}
},
{
"flow": {
"nodes": [
{ "label": "Source", "detail": "OCI image\nCompose and documentation" },
{ "label": "ProxMenux", "detail": "JSON contract\nvalidation and plan" },
{ "label": "Proxmox VE", "detail": "native LXC\nvolumes and devices" }
],
"caption": "The application is not rebuilt: its environment is reproduced declaratively."
}
}
]
},
{
"id": "where",
"title": "Where it is",
"intro": "OCI manager Apps opens from option <strong>OCI manager Apps (beta)</strong> of the ProxMenux main menu, on the Proxmox node and as root. Its first screen offers:",
"blocks": [
{
"list": {
"items": [
"<strong>Search applications</strong> — search by name across the catalog.",
"<strong>All applications</strong> — the complete catalog, with the number of applications.",
"<strong>Manage installed OCI applications</strong> — update, recreate, remove or recover what was installed.",
"<strong>Install an image that is not in the catalog</strong> — translate a Compose file, a <code>docker run</code> command or an image reference.",
"The catalog categories, each with its number of applications."
]
}
},
{
"figure": {
"src": "/oci-manager/main-menu.png",
"alt": "Main screen of OCI manager Apps with search, all applications, management, custom image and the catalog categories",
"caption": "Main screen of OCI manager Apps."
}
},
{
"p": "Selecting an application shows its description, the image it runs and two installation modes: <strong>Install with default settings</strong>, which asks almost nothing, and <strong>Install with advanced settings</strong>, which offers the real storage, bridge and resource selectors of the node."
}
]
},
{
"id": "translation",
"title": "What Docker expresses and what OCI manager Apps turns it into",
"blocks": [
{
"table": {
"headers": ["Requirement", "Docker expresses it as", "OCI manager Apps turns it into"],
"rows": [
["Run the application", "<code>image</code>, <code>entrypoint</code>, <code>command</code>", "OCI rootfs and the main process of the LXC"],
["Keep configuration", "<code>volumes: /config</code>", "a persistent <code>mpN</code> disk included in the backup, or a host directory"],
["Publish the service", "<code>ports</code>", "an address of its own for the LXC and the access URL of the service"],
["Use hardware", "<code>devices</code>, <code>group_add</code>", "<code>devN</code> entries with the effective host GID and a validated profile"],
["Connect dependencies", "<code>networks</code>, <code>depends_on</code>", "a private bridge, fixed addresses, start order and healthchecks"],
["Update", "<code>pull</code> and recreate", "a new rootfs with the same persistent contract"]
]
}
}
]
},
{
"id": "kept",
"title": "What is kept and what changes",
"blocks": [
{
"cards": {
"items": [
{ "icon": "archive", "title": "Kept", "body": "The official image, its internal paths, its environment, its startup process and its functional documentation." },
{ "icon": "boxes", "title": "Adapted", "body": "The runtime environment: network, persistence, devices, permissions and dependencies use native Proxmox VE LXC primitives." },
{ "icon": "shield", "title": "Not translated", "body": "A Compose key with no safe equivalent is listed as a blocker. Templates with blockers are not offered, and sensitive options are confirmed during the installation." },
{ "icon": "refresh", "title": "Recorded", "body": "Image digest, resources, paths, network, devices and stack membership are stored in the instance contract, which updates and recreations reproduce." }
]
}
}
]
},
{
"id": "pages",
"title": "Pages of this section",
"blocks": [
{
"next": {
"items": [
{ "label": "How an OCI image is translated", "href": "/docs/oci-manager/architecture", "tail": "from the image and its Compose file to a native LXC." },
{ "label": "Install an image that is not in the catalog", "href": "/docs/oci-manager/custom-image", "tail": "Compose, docker run or an image reference." },
{ "label": "Data, paths and networking", "href": "/docs/oci-manager/storage-network", "tail": "container disks, host directories, Rclone mounts and addresses." },
{ "label": "Devices and acceleration", "href": "/docs/oci-manager/hardware", "tail": "GPU, NVIDIA, Coral, USB and other devices." },
{ "label": "Multi-container applications", "href": "/docs/oci-manager/stacks", "tail": "application, database and cache as coordinated LXCs." },
{ "label": "Install, update and recreate", "href": "/docs/oci-manager/lifecycle", "tail": "the instance contract and its operations." },
{ "label": "In ProxMenux Monitor", "href": "/docs/oci-manager/monitor", "tail": "versions, updates, console output and terminal." }
]
}
}
]
}
]
}
@@ -0,0 +1,243 @@
{
"meta": {
"title": "Install, update and recreate | ProxMenux",
"description": "The instance contract of an OCI container and the operations that use it: update, recreate, remove and recovery of an interrupted operation."
},
"header": {
"title": "Install, update and recreate",
"description": "Each instance keeps a reproducible contract, so its rootfs can be replaced without losing configuration or persistent data.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "contract",
"title": "The instance contract",
"blocks": [
{
"p": "After an installation, the effective configuration is stored in <code>/usr/local/share/proxmenux/oci/instances/VMID/oci-compose.json</code>. It is not a copy of the original Compose file: it is the reproducible contract of the LXC that exists on this host."
},
{
"code": {
"code": "instances/\n└── 105/\n └── oci-compose.json\n ├── image and resolved digest\n ├── resources and network\n ├── environment (secrets protected)\n ├── container disks and host directories\n ├── hardware profile and devices\n ├── console log and terminal mode\n └── stack membership and lifecycle"
}
}
]
},
{
"id": "manage",
"title": "Manage installed OCI applications",
"intro": "This option of the main screen lists the registered instances with their application and image. Each one is checked against its contract before any action; a CT that no longer matches its record is neither modified nor deleted. The same update and recreation are offered in the Updates tab of <monitorLink>ProxMenux Monitor</monitorLink>.",
"blocks": [
{
"table": {
"headers": [
"Option",
"What changes",
"What is kept"
],
"rows": [
[
"Update the image with the saved configuration",
"The rootfs is replaced by the image the saved channel publishes today",
"Contract, container disks, host directories, network and devices"
],
[
"Recreate: edit resources, network, paths and GPU",
"The editor opens with the current contract; the CT is rebuilt with the changes",
"The data of container disks and host directories"
],
[
"Remove: delete the application and its containers",
"The LXC, or every member of a stack, and their contracts are deleted",
"Host directories, with their content"
]
]
}
},
{
"p": "For a multi-container application the menu offers <strong>Update every container of the application</strong> and the removal. A stack is not recreated."
},
{
"figure": {
"src": "/oci-manager/manage-menu.png",
"alt": "List of installed OCI applications and the update, recreate and remove options",
"caption": "Manage installed OCI applications."
}
}
]
},
{
"id": "update",
"title": "A transactional update",
"blocks": [
{
"mermaid": {
"chartCode": "sequenceDiagram\n participant U as {{user}}\n participant P as ProxMenux\n participant R as {{registry}}\n participant X as Proxmox VE\n U->>P: {{update}}\n P->>R: {{resolve}}\n R-->>P: digest\n P->>P: {{verify}}\n P->>X: {{backup}}\n P->>X: {{import}}\n P->>X: {{reapply}}\n X-->>P: healthcheck\n alt {{healthy}}\n P-->>U: {{commit}}\n else {{failure}}\n P->>X: Rollback\n P-->>U: {{restored}}\n end",
"labels": {
"user": "User",
"registry": "OCI registry",
"update": "Update instance",
"resolve": "Resolve the tag",
"verify": "Verify archive and preflight",
"backup": "Stop and back up the CT",
"import": "Import the new rootfs",
"reapply": "Apply the contract again",
"healthy": "healthy",
"failure": "failure",
"commit": "Contract published with the new digest",
"restored": "Previous instance restored"
}
}
},
{
"list": {
"items": [
"When the registry still serves the installed digest, nothing is downloaded and the instance is not touched.",
"The new image is verified layer by layer before the CT stops. A download that arrives damaged is fetched a second time; an image already cached that fails the check is downloaded again.",
"Settings changed in Proxmox VE after the installation (memory, swap, cores, CPU limit, CPU priority, start with the node) are kept and carried into the new contract.",
"Any other difference between the CT and its contract stops the update before the CT is stopped."
]
}
},
{
"flow": {
"nodes": [
{
"label": "Before",
"detail": "rootfs A\n/config mp0\n/media host directory"
},
{
"label": "Update",
"detail": "replaces only\nthe rootfs"
},
{
"label": "After",
"detail": "rootfs B\n/config mp0\n/media host directory"
}
]
}
}
]
},
{
"id": "recovery",
"title": "Recovering an interrupted operation",
"intro": "Update and recreation use a persistent transaction. If the process, the terminal or the node is interrupted after the CT stops, the operation is not taken as finished.",
"blocks": [
{
"steps": {
"items": [
{
"title": "Pending marker",
"body": "When Manage installed OCI applications opens, the saved state shows that the replacement was never published."
},
{
"title": "View status",
"body": "Shows the phase that was reached without changing containers or data."
},
{
"title": "Recover the previous installation",
"body": "Restores the verified native backup taken before the replacement and the previous contract."
},
{
"title": "Stacks as a unit",
"body": "For a multi-container application, every member is recovered from the same transaction point, not only the selected one."
}
]
}
},
{
"calloutWarning": {
"title": "Host directories are outside the rollback",
"body": "The backup covers the rootfs and the container disks it includes. A host directory is not reverted, because other LXCs may use its data. Updating, recreating or recovering an instance with host directories asks for a confirmation of this first."
}
}
]
},
{
"id": "remove",
"title": "Removing an OCI application",
"blocks": [
{
"p": "Before the confirmation, a summary is built from the real configuration: the containers that are removed, the data deleted with them, the private network that is released and the host directories that are kept. The confirmation defaults to <strong>No</strong>, since the data of the deleted disks cannot be recovered afterwards."
},
{
"table": {
"headers": [
"Resource",
"On removal",
"Reason"
],
"rows": [
[
"rootfs and container disks",
"Deleted",
"They belong only to the container"
],
[
"Host directory",
"Kept, with its content",
"Other applications may use it"
],
[
"Instance contract",
"Retired after a successful removal",
"No CT is associated with it any more"
],
[
"Private bridge of a stack",
"Released with the stack",
"It has no members left to connect"
],
[
"A single member of a stack",
"Not removed on its own",
"The whole application is removed, so no stack is left incomplete"
]
]
}
},
{
"figure": {
"src": "/oci-manager/remove-summary.png",
"alt": "Removal summary with the containers, data and host directories affected",
"caption": "The removal summary, built from the real configuration."
}
}
]
},
{
"id": "archives",
"title": "Downloaded images and host space",
"blocks": [
{
"p": "The OCI archive is used to build the rootfs; the running CT does not read it. After an installation or an update, the downloaded archives of that operation are listed with their size and their deletion is offered. Deleting them frees the space without affecting the container or its data."
},
{
"calloutInfo": {
"title": "Updates do not need the archive",
"body": "Without the archive, an update resolves the saved channel and downloads the new digest. With it, an archive is reused only when its reference and integrity match what is requested."
}
}
]
},
{
"id": "registry",
"title": "Registry and cleanup",
"blocks": [
{
"p": "The registered contracts are compared with the real CTs. A contract is orphaned only when its VMID no longer exists or no longer carries the expected instance identity. The cleanup does not delete volumes or external data by inference."
}
]
},
{
"id": "channel",
"title": "A rolling tag is not an unattended update",
"blocks": [
{
"p": "The catalog installs the rolling tag its maintainer publishes, but the effective digest is resolved, recorded and changed only through an explicit update with preflight and rollback. <monitorLink>ProxMenux Monitor</monitorLink> compares the installed digest with the one the registry publishes and shows, and notifies, when a new image is available."
}
]
}
]
}
@@ -0,0 +1,203 @@
{
"meta": {
"title": "OCI containers in ProxMenux Monitor | ProxMenux",
"description": "What ProxMenux Monitor shows for a container installed by OCI manager Apps: application and image versions, new images, console output and the Proxmox VE terminal."
},
"header": {
"title": "In ProxMenux Monitor",
"description": "A container installed by OCI manager Apps appears in VMs & LXCs like any other LXC. Its modal reads the installation record: application and image versions, new images, console output and terminal.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "intro",
"blocks": [
{
"calloutInfo": {
"title": "The record is the source",
"body": "OCI manager Apps knows what it installed and where it came from. The Monitor reads that installation record instead of probing the container, so the data is the same whether the container is running or stopped."
}
},
{
"table": {
"headers": [
"Tab",
"What changes for an OCI container"
],
"rows": [
[
"App",
"The application is identified from the record, and updates are tracked by image"
],
[
"Updates",
"The image is updated or the container recreated, with the same flow as the OCI menu"
],
[
"Mounts",
"Container disks and host directories, with their usage"
],
[
"Logs",
"Only for OCI containers: the console output of the application"
]
]
}
}
]
},
{
"id": "app",
"title": "App",
"intro": "The <appLink>App</appLink> tab offers the installed application as a detection, with the name, logo, port and scheme of the record. Registering it opens the editor with the method <strong>OCI image (installed by ProxMenux)</strong>, which needs no configuration.",
"blocks": [
{
"cards": {
"items": [
{
"icon": "boxes",
"title": "Application",
"body": "The version of the application inside the image, read from the image itself: its environment or its <code>org.opencontainers.image.version</code> label. It is informative."
},
{
"icon": "archive",
"title": "Image",
"body": "The build date and digest of the installed image. The update is decided here: the installed digest is compared with the one the registry publishes today for the same tag."
}
]
}
},
{
"list": {
"items": [
"When the registry publishes a new digest, the card shows <strong>New image</strong> with its date and digest, even if the application version inside did not change: a rebuild on an updated base is an update.",
"When the digests match, the card reads <strong>Version</strong>.",
"The card links to the repository of the image: its GitHub project, or its Docker Hub page for an official image.",
"The access link uses the LAN address of the container, also for the main member of a multi-container application, which has a second address on its private network.",
"The button <strong>Refresh data</strong> reads the record and the registry again. The options to search for applications or register another one are not offered: the container holds exactly the application of its record."
]
}
},
{
"figure": {
"src": "/oci-manager/monitor-app-tab.png",
"alt": "App tab of an OCI container with the application version, the image and the repository link",
"caption": "App tab of a container installed by OCI manager Apps."
}
},
{
"p": "The check runs once a day with the update checks of the Monitor, and <strong>Refresh data</strong> runs it at once. A new image is sent as a notification through the channels configured in <notificationsLink>Notifications</notificationsLink>. The update is applied from the Updates tab."
}
]
},
{
"id": "updates",
"title": "Updates",
"intro": "For a container installed by OCI manager Apps the Updates tab shows the application with its installed image and, when the registry publishes one, the new image, in the same format as any other application. The package and application updaters of an ordinary LXC do not appear.",
"blocks": [
{
"table": {
"headers": [
"Button",
"What it opens"
],
"rows": [
[
"Update",
"The update of <lifecycleLink>Manage installed OCI applications</lifecycleLink> for this container, in the Monitor terminal. It can run whether or not there is a new image; with none, nothing is changed. In a multi-container application it updates every member."
],
[
"Recreate",
"The recreation editor (resources, network, paths and GPU). It is not offered for a multi-container application."
],
[
"Recover",
"Replaces Update when an operation on the container was interrupted, and opens its recovery."
]
]
}
},
{
"p": "External changes, host directories and multi-container applications are handled as in the OCI menu. When the terminal closes, the image, the record and the mounts are read again."
},
{
"table": {
"headers": [
"Option",
"Behaviour"
],
"rows": [
[
"Keep the backup taken before updating",
"Every update backs up the container to restore it if the update fails. With this option that same backup is kept in the chosen storage and appears among the backups of the CT; on Proxmox Backup Server a backup is written before the update."
],
[
"Scheduled updates",
"The image is updated at the chosen time only when the registry publishes a new one, and optionally only once it is 1, 3, 7 or 14 days old. A container with changes made outside ProxMenux is skipped and reported; one with host directories runs only when that was confirmed when the schedule was saved."
]
]
}
}
]
},
{
"id": "logs",
"title": "Logs",
"intro": "The Logs tab appears only for containers installed by OCI manager Apps, between Mounts and Backups. It shows the standard output and error of the main process of the image, the same output <code>docker logs</code> shows for a Docker container.",
"blocks": [
{
"list": {
"items": [
"The output is kept on the host in <code>/var/log/proxmenux/oci/VMID.console.log</code> (mode 0600), from the first start and across restarts, so it can be read with the container stopped.",
"The file is rotated at 10 MB, keeping three compressed copies (<code>/etc/logrotate.d/proxmenux-oci</code>).",
"The last 100, 500 or 1000 lines are shown. While the container runs, new lines are followed live; scrolling up pauses the follow, and <strong>Follow</strong> resumes it.",
"A filter shows only the lines that contain a text, and <strong>Download</strong> saves the lines loaded.",
"Colour codes are removed and a line that a progress bar redraws is shown in its final state.",
"The tab reads the file on every open; it is not cached."
]
}
},
{
"calloutInfo": {
"title": "First-start credentials",
"body": "Images that print a generated password on their first start leave it in this output, as <code>docker logs</code> does. The installer reads it from there to show it in its summary."
}
},
{
"figure": {
"src": "/oci-manager/monitor-logs-tab.png",
"alt": "Logs tab with the console output of an OCI container, the line selector, the filter and the follow button",
"caption": "Console output of an OCI container."
}
}
]
},
{
"id": "terminal",
"title": "Proxmox VE console",
"blocks": [
{
"p": "An OCI image runs its own process as PID 1 and no login service, so the default console of an LXC would open a terminal nothing answers on. Containers installed by OCI manager Apps are created with <code>cmode: shell</code>: the Proxmox VE console opens a shell with <code>lxc-attach</code>, the equivalent of <code>docker exec</code>, with the running application untouched."
},
{
"list": {
"items": [
"The shell is the one <code>/etc/passwd</code> gives to root in the image. An image whose root has no shell, or ships none, keeps the default console.",
"The shell is root inside the container, without a password. Who can open it is decided by the <code>VM.Console</code> permission of Proxmox VE.",
"The terminal of the Monitor enters the container with <code>pct enter</code>, which works the same way."
]
}
}
]
},
{
"id": "mounts",
"title": "Mounts",
"blocks": [
{
"p": "The <mountsLink>Mounts</mountsLink> tab lists the container disks and host directories of the container. The usage of a container disk on block storage such as LVM-thin is read from the filesystem of the disk, which is mounted only inside the container."
}
]
}
]
}
@@ -0,0 +1,383 @@
{
"meta": {
"title": "Multi-container applications | ProxMenux",
"description": "How OCI manager Apps turns an application with its database and cache into coordinated native LXCs: plan, private network, dependency hook, validation and transactional updates."
},
"header": {
"title": "Multi-container applications",
"description": "An application with its database, cache and other services becomes several coordinated native LXCs, installed and updated as one.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "intro",
"blocks": [
{
"calloutInfo": {
"title": "One application for the user, several LXCs for Proxmox VE",
"body": "A multi-container definition is one entry of the catalog. The installer creates one native LXC per service and keeps their dependencies explicit. Immich, Nextcloud, Paperless-ngx and Tandoor are installed this way."
}
},
{
"mermaid": {
"chartCode": "flowchart LR\n C[\"Docker Compose\"] --> O[\"{{plan}}\"]\n O --> A[\"{{app}}<br/>{{appNet}}\"]\n O --> D[\"{{db}}<br/>{{private}}\"]\n O --> R[\"{{cache}}<br/>{{private}}\"]\n V1[(\"config\")] --> A\n V2[(\"database\")] --> D\n V3[(\"cache\")] --> R\n D --> A\n R --> A",
"labels": {
"plan": "Stack plan",
"app": "Application LXC",
"appNet": "LAN + private",
"db": "PostgreSQL LXC",
"cache": "Valkey LXC",
"private": "private"
}
}
}
]
},
{
"id": "plan",
"title": "1. The plan, before any container exists",
"blocks": [
{
"p": "No LXC is created while the Compose definition is interpreted. A complete plan comes first, with every member, image, VMID, network, path, secret, order and health check. If the plan is not consistent, the installation does not start."
},
{
"table": {
"headers": [
"Plan element",
"Contents",
"Checked before creating anything"
],
"rows": [
[
"Members",
"Main application, database, cache, machine learning and other dependencies",
"Unique VMIDs, known roles and exactly one main member"
],
[
"Images",
"Rolling reference, architecture and resolved digest of each service",
"All exist, support the architecture and pass the OCI integrity check"
],
[
"Network",
"Private bridge, subnet, a fixed address per service and LAN access for the main member",
"No collision with existing bridges or subnets and no repeated address"
],
[
"Persistence",
"Container disks, host directories, owners and backup",
"No overlapping paths, storage available and declared permissions"
],
[
"Secrets",
"Database password, application keys and initial credentials",
"Generated once and given only to the members that use them"
],
[
"Lifecycle",
"Start order, stop order and a health check per member",
"The main application starts last and stops first"
]
]
}
}
]
},
{
"id": "create",
"title": "2. Creation, member by member",
"blocks": [
{
"steps": {
"items": [
{
"title": "Reserve every VMID",
"body": "The Proxmox VE inventory and the instance registry are checked. An existing CT is not adopted and a contract that still belongs to another instance is not reused."
},
{
"title": "Prepare every image",
"body": "All images are resolved, downloaded and verified before the first container is created."
},
{
"title": "Create each rootfs",
"body": "The official OCI metadata is imported, and the ProxMenux instance identity and the member role are added."
},
{
"title": "Private network",
"body": "A bridge and subnet are created and each member gets its fixed address; only the main member also gets the LAN interface."
},
{
"title": "Persistence",
"body": "Each database and configuration gets its own container disk; only data meant to be shared uses host directories."
},
{
"title": "Environment",
"body": "Internal endpoints, shared secrets and service variables are written. The application reaches its dependencies at their reserved private addresses."
},
{
"title": "Record",
"body": "Each member records its native configuration, the rootfs changes that updates have to reproduce and its relation to the stack."
}
]
}
}
]
},
{
"id": "checks",
"title": "3. Start and check each container",
"intro": "A created LXC does not mean a ready service. Dependencies start in order and each one passes a check specific to its service.",
"blocks": [
{
"table": {
"headers": [
"Service",
"Check",
"What it shows"
],
"rows": [
[
"PostgreSQL",
"<code>pg_isready</code> in the CT with the expected host, user and database",
"The server accepts connections for the configured database"
],
[
"Redis / Valkey",
"<code>redis-cli</code> or <code>valkey-cli</code> <code>PING</code> against its private address",
"The broker listens and answers"
],
[
"Immich machine learning",
"HTTP <code>GET /ping</code>, plus a check of the selected GPU runtime",
"The service answers and the requested acceleration has not fallen back to CPU"
],
[
"Nextcloud",
"<code>GET /status.php</code> with <code>installed=true</code>, <code>maintenance=false</code> and <code>needsDbUpgrade=false</code>",
"Initialisation finished with no pending migration"
],
[
"Paperless-ngx / Tandoor",
"HTTP on the LAN address and real port of the service",
"The frontend and its dependencies serve the application"
],
[
"Main application",
"The endpoint of its template, for example <code>/api/server/ping</code> in Immich",
"The whole stack works through the application that uses the dependencies"
]
]
}
},
{
"calloutWarning": {
"title": "running is not healthy",
"body": "The running state only says that the LXC process exists. Where the service offers a better check, an exec or HTTP check with a timeout is used. If a member stops or fails its check, the stack is not declared installed."
}
}
]
},
{
"id": "hook",
"title": "4. The dependency hook",
"blocks": [
{
"p": "The hook is set only on the main container of a dependent stack. Proxmox VE keeps the script as a snippet and runs it as the <code>hookscript</code> of that CT. The stack recipe is not written in the script: it lives in a separate private contract."
},
{
"codeGrid": {
"items": [
{
"title": "Main CT configuration",
"code": "hookscript: local:snippets/proxmenux-stack-dependencies.sh"
},
{
"title": "Private contract (example)",
"code": "/etc/pve/priv/proxmenux-stack-VMID.json\n{\n \"schema\": 1,\n \"stack\": \"immich\",\n \"dependencies\": [\n {\"vmid\": 107, \"label\": \"PostgreSQL\", \"healthcheck\": {...}},\n {\"vmid\": 108, \"label\": \"Valkey\", \"healthcheck\": {...}},\n {\"vmid\": 106, \"label\": \"Machine Learning\", \"healthcheck\": {...}}\n ]\n}"
}
]
}
},
{
"snippet": {
"summary": "Complete source of proxmenux-stack-dependencies.sh",
"pathCode": "local:snippets/proxmenux-stack-dependencies.sh",
"snippetCode": "stackDependencyHook"
}
},
{
"p": "The script is the same for every stack. VMIDs, names, check types and timeouts come from the private contract <code>/etc/pve/priv/proxmenux-stack-VMID.json</code> of each stack."
},
{
"steps": {
"items": [
{
"title": "Proxmox VE calls pre-start",
"body": "Before the main CT starts, the hook runs with its VMID and the lifecycle phase."
},
{
"title": "A lock per stack",
"body": "<code>flock</code> on <code>/run/lock/proxmenux-stack-VMID.lock</code> prevents two start sequences at the same time."
},
{
"title": "The contract is read and validated",
"body": "A known schema, numeric dependencies, labels, an exec, http or running check and a positive timeout are required."
},
{
"title": "Dependencies in order",
"body": "Each CT must exist; a stopped one is started and one already running is not restarted."
},
{
"title": "Wait for health",
"body": "The check runs every two seconds, and the CT is also confirmed to be still running."
},
{
"title": "The main CT starts",
"body": "When every dependency is ready, pre-start ends and Proxmox VE starts the application."
}
]
}
},
{
"calloutInfo": {
"title": "The hook does not stop dependencies",
"body": "The post-start, pre-stop and post-stop phases do nothing. Stopping the main CT leaves PostgreSQL, Redis, Valkey or machine learning running. The hook orders the start; it does not turn several LXCs into one process."
}
},
{
"table": {
"headers": [
"Type",
"Example",
"Later starts"
],
"rows": [
[
"Dependent stack",
"Immich, Nextcloud, application with PostgreSQL",
"The hook of the main CT starts the dependencies and waits for them"
],
[
"Application suite",
"Arr suite",
"No main member: each LXC follows its own <code>onboot</code>"
],
[
"Single application",
"Jellyfin",
"Proxmox VE starts that LXC directly"
]
]
}
}
]
},
{
"id": "stack-checks",
"title": "5. Checks on the stack as a whole",
"blocks": [
{
"list": {
"items": [
"Every VMID exists, is unique and keeps the expected instance identity.",
"Every contract belongs to the same stack, keeps its role and its recorded native configuration.",
"The main member is last in <code>start_order</code> and first in <code>stop_order</code>.",
"No member, volume or rootfs adaptation is missing.",
"The hook points to the official snippet, its content is unchanged and its contract matches the stack recipe.",
"The observed images and digests match the prepared OCI archives.",
"Devices, GPU profiles, mounts, secrets and endpoints still match the contracts.",
"After every dependency passes, the endpoint of the main application checks the integration between members."
]
}
}
]
},
{
"id": "manage",
"title": "Managing an installed stack",
"intro": "In <strong>Manage installed OCI applications</strong>, any member leads to the whole stack. The menu of a stack offers <strong>Update every container of the application</strong> and <strong>Remove: delete the application and its containers</strong>.",
"blocks": [
{
"steps": {
"items": [
{
"title": "Any member",
"body": "The contract of the member names the main VMID and the complete list of members."
},
{
"title": "The stack is reproducible",
"body": "Identities, contracts, hook, adaptations and a coordinated replay for that recipe are validated."
},
{
"title": "Images first",
"body": "The application is not stopped until every digest has been resolved, downloaded and verified."
},
{
"title": "Stop and back up the set",
"body": "Verified native backups are taken with the stack stopped, so application and databases belong to the same moment."
},
{
"title": "Update and check each member",
"body": "Adaptation, network, mounts, secrets and devices are applied again before the health check of each member."
},
{
"title": "Publish or recover everything",
"body": "The new contracts are published only when every member passes. If one fails, every member is restored."
}
]
}
},
{
"calloutWarning": {
"title": "A stack without a coordinated replay is not updated",
"body": "If the preparation of a stack cannot be reproduced, the update is refused before the stack is stopped. The application keeps running as it is."
}
}
]
},
{
"id": "failure",
"title": "6. When something fails",
"blocks": [
{
"p": "During an installation, an error stops and removes the incomplete containers that operation created, and the private bridge if the operation created it. A stack is not published as valid until the whole sequence finishes."
},
{
"p": "An update is transactional: all images are prepared first, then the stack stops, each member is backed up and the backup verified, and only then is each rootfs replaced. Members start one by one with their health check; if one fails, every member is restored from the same set of backups, so the database and the application never belong to different moments."
},
{
"mermaid": {
"chartCode": "flowchart LR\n P[\"{{prepare}}\"] --> S[\"{{stop}}\"]\n S --> B[\"{{backup}}\"]\n B --> R[\"{{replace}}\"]\n R --> H{\"{{healthy}}\"}\n H -- \"{{yes}}\" --> C[\"{{commit}}\"]\n H -- \"{{no}}\" --> X[\"{{rollback}}\"]",
"labels": {
"prepare": "Prepare every image",
"stop": "Stop, main member first",
"backup": "Verified backup of every CT",
"replace": "Replace rootfs",
"healthy": "All healthy?",
"yes": "Yes",
"no": "No",
"commit": "Publish contracts",
"rollback": "Restore every member"
}
}
}
]
},
{
"id": "not-assumed",
"title": "What a stack does not include",
"blocks": [
{
"list": {
"items": [
"A suite installed in one flow, such as the Arr suite, is not a dependent stack: its containers have no start order between them.",
"The private network does not replace authentication, TLS or the configuration of each application.",
"The backup of one LXC does not contain the bridge, the hook or the contracts of the rest of the stack.",
"Prowlarr, Sonarr or Radarr receive no indexers, profiles or providers from ProxMenux."
]
}
}
]
}
]
}
@@ -0,0 +1,190 @@
{
"meta": {
"title": "Data, paths and networking | ProxMenux",
"description": "How OCI manager Apps keeps the data of an OCI container: container disks, host directories, Rclone mounts, addresses and private networks."
},
"header": {
"title": "Data, paths and networking",
"description": "What lives in the rootfs, what survives its replacement, how data is shared between containers and how each container gets its address.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "intro",
"blocks": [
{
"calloutInfo": {
"title": "Persistence is decided before the LXC exists",
"body": "The volumes published by the image and by its Compose file are read before the container is created. Every path that has to survive an update becomes a mount point independent of the rootfs, so the rootfs only holds what belongs to the image and can be replaced."
}
}
]
},
{
"id": "questions",
"title": "What the installer asks",
"intro": "The template supplies the paths the application needs. Each one is placed on a container disk or on a host directory, and more paths can be added before the summary.",
"blocks": [
{
"steps": {
"items": [
{ "title": "Required paths", "body": "<code>/config</code>, <code>/data</code>, libraries, downloads and every volume the application declares are listed." },
{ "title": "Location", "body": "Each path is placed on a disk of the container or on an existing host directory." },
{ "title": "Storage and size", "body": "A container disk is created on a Proxmox VE storage, <code>local-lvm</code> by default, with the size given. It appears as <code>vm-VMID-disk-N</code> and is attached as <code>mpN</code>." },
{ "title": "Host directory", "body": "A host directory is given by its path. A directory that does not exist is created, owned by the user the container maps." },
{ "title": "Additional paths", "body": "More pairs of host path or volume and container path can be added before installing." },
{ "title": "Summary", "body": "The complete mapping is shown before the CT is created and is stored in its instance contract." }
]
}
}
]
},
{
"id": "options",
"title": "The two persistent locations",
"blocks": [
{
"table": {
"headers": ["Property", "Container disk", "Host directory"],
"rows": [
["In the configuration", "<code>mpN: STORAGE:vm-VMID-disk-N,mp=/config,backup=1,size=16G</code>", "<code>mpN: /mnt/oci-shared/media,mp=/data/media</code>"],
["Container backup (vzdump)", "Included, with <code>backup=1</code>", "Not included"],
["Size", "Fixed; grown with a resize of the mount point", "The free space of the host filesystem or dataset"],
["Other containers", "Mounted only by its own container", "The same directory can be mounted in several containers"],
["Snapshots and restore", "Managed by Proxmox VE together with the CT", "Managed on the host storage"],
["Removing the application", "Deleted with the container", "Kept, with its content"],
["Moving the CT to another node", "Moves with the CT", "The same path has to exist on the other node"]
]
}
},
{
"p": "The rootfs is reserved for the binaries and the content of the image. An update or a recreation replaces it without touching either kind of mount point."
}
]
},
{
"id": "example",
"title": "Example: a container with both locations",
"blocks": [
{
"code": {
"title": "Jellyfin installed as CT 151 on local-lvm (excerpt)",
"code": "rootfs: local-lvm:vm-151-disk-0,size=8G\n# Container disk, part of the CT backup\nmp0: local-lvm:vm-151-disk-1,mp=/config,backup=1,size=16G\n\n# Host directory, outside the CT backup\nmp1: /mnt/oci-shared/media,mp=/data/media"
}
},
{
"p": "An update or a recreation replaces only the rootfs: <code>mp0</code> keeps users, libraries and settings, and <code>mp1</code> keeps showing the same media. Restoring the CT backup brings back <code>/config</code>; the media directory is restored, if needed, from the backup of the host storage."
},
{
"flow": {
"nodes": [
{ "label": "Contract", "detail": "/config" },
{ "label": "Location", "detail": "container disk\nor host directory" },
{ "label": "LXC", "detail": "always /config\nfor the application" }
],
"caption": "The application sees the path the image publishes; only where it is stored changes."
}
}
]
},
{
"id": "shared",
"title": "One host directory, several containers",
"blocks": [
{
"mermaid": {
"chartCode": "flowchart TB\n H[\"{{host}}<br/>/mnt/oci-shared/media\"]\n H --> Q[\"qBittorrent<br/>/data\"]\n H --> J[\"Jellyfin<br/>/data\"]\n H --> R[\"Radarr / Sonarr<br/>/data\"]\n Q -. \"{{config}}\" .-> QV[(\"/config mpN\")]\n J -. \"{{config}}\" .-> JV[(\"/config mpN\")]\n R -. \"{{config}}\" .-> RV[(\"/config mpN\")]",
"labels": { "host": "Host directory", "config": "own configuration" }
}
},
{
"p": "Each container keeps its configuration on its own disk. The library or the downloads are one host directory mounted at the same internal path in every container, so a path that one application writes is the same path another one reads."
}
]
},
{
"id": "rclone",
"title": "Cloud storage through the Rclone application",
"intro": "The Rclone application of the catalog offers, besides its installation, <strong>Enable a mount on an existing Rclone OCI container</strong>. It mounts a remote already created and authorised in the Rclone web UI and publishes it on the host, where other containers can use it as a host directory.",
"blocks": [
{
"steps": {
"items": [
{ "title": "Container and remote", "body": "The VMID of the Rclone container, the exact name of the remote and, optionally, a path inside it." },
{ "title": "Mount name and cache", "body": "The name of the mount and the VFS cache mode: <code>off</code>, <code>minimal</code>, <code>writes</code> or <code>full</code> (default)." },
{ "title": "Published views", "body": "A common root, <code>/mnt/oci-shared</code> by default, holds a read/write view in <code>/mnt/oci-shared/remotes/NAME</code> and a read-only view in <code>/mnt/oci-shared/remotes-ro/NAME</code>." },
{ "title": "Activation", "body": "After a confirmation, the CT is stopped, its start command and a Proxmox VE hookscript are set, and it is started again. The operation waits until both views are mounted on the host." }
]
}
},
{
"calloutInfo": {
"title": "If the mount does not come up",
"body": "The previous configuration of the container is restored and it is started again, so a failed activation leaves Rclone as it was."
}
}
]
},
{
"id": "network",
"title": "Addresses and networks",
"intro": "A single application needs an address. A multi-container application also needs a stable network between its members.",
"blocks": [
{
"cards": {
"items": [
{ "icon": "network", "title": "Single application", "body": "Bridge and DHCP or a fixed CIDR address are chosen. The LXC has an address of its own and the summary shows the complete URLs of the services." },
{ "icon": "waypoints", "title": "Multi-container application", "body": "A free subnet is found, a persistent private bridge is created and each member (application, database, cache) gets a fixed address on it." }
]
}
},
{
"flow": {
"nodes": [
{ "label": "LAN", "detail": "reachable address\nmain service only" },
{ "label": "Main LXC", "detail": "web / API\nLAN + private network" },
{ "label": "Private network", "detail": "PostgreSQL · Valkey · ML\nfixed addresses" }
],
"caption": "Dependencies talk over the private network and have no address on the LAN."
}
},
{
"p": "The stack contract stores bridge, subnet, addresses and the relations between services. Updating or recreating a member reuses the same topology. ProxMenux Monitor opens the main container at its LAN address, not at its address on the private network."
}
]
},
{
"id": "ownership",
"title": "Ownership",
"blocks": [
{
"list": {
"items": [
"New directories are created with the UID and GID the unprivileged LXC maps.",
"Existing directories are not re-owned recursively.",
"Sockets, system files and sensitive paths are not offered as generic host directories."
]
}
}
]
},
{
"id": "backup",
"title": "What a container backup contains",
"blocks": [
{
"table": {
"headers": ["Element", "In the CT vzdump", "Where it is kept"],
"rows": [
["OCI rootfs", "Yes", "The CT backup; it can also be rebuilt from the image and the contract"],
["Container disk with <code>backup=1</code>", "Yes", "The CT backup"],
["Host directory", "No", "The backup of the host storage"],
["Instance contract", "No", "<code>/usr/local/share/proxmenux/oci/instances/VMID/</code> on the host"],
["Multi-container application", "Each member in its own backup", "The backups of every member, plus the stack contract and its bridge on the host"]
]
}
}
]
}
]
}