Atomic / bootc (OSTree) image for Proxmox VE, based on Debian 13 (Trixie), built on top of debian-bootc.
DaemonCores-VE delivers a complete, transactional Proxmox VE deployment that inherits the bootc/OSTree infrastructure from debian-bootc and layers the Proxmox hypervisor stack on top. The entire OS is managed as an OCI container image: build, push, deploy, rollback — no manual package management on the host.
- Why this exists
- Relationship with debian-bootc
- Is this repository abandoned?
- Technical stack
- CI/CD pipeline
- APT repository
- Secure Boot
- Required secrets
- Quick start
- Default root password
- No-subscription popup removal
- License
Proxmox VE is an excellent open-source hypervisor platform, but it is traditionally installed via an ISO or apt and then managed manually on each host. There is no official atomic/OSTree deployment path, which means updates are applied in-place with no atomic rollback capability.
This project solves that by building Proxmox VE as a bootc image:
- Proxmox VE is installed on top of a bootc-compliant Debian base — the same atomic, rollback-capable OS model that powers Fedora CoreOS and RHEL Image Mode.
- The entire image is built in a standard container pipeline, pushed to GHCR, and applied atomically using ostree as the on-disk storage engine.
- Installer ISOs (both online and offline) are generated automatically via the CI pipeline, providing a single-boot deployment path for bare-metal and VM hosts.
- First-boot user setup handles hostname, locale, user accounts, and root password via a TUI wizard, so the image is usable out of the box without manual post-install configuration.
DaemonCores-VE is a layer on top of debian-bootc, not a fork. The base image (ghcr.io/daemoncores/debian-bootc:latest) provides the full bootc/OSTree infrastructure, and this repository adds only the Proxmox VE layer.
- bootc — atomic OS management via OCI container images
- ostree — content-addressed filesystem with atomic deployments and rollback
- composefs — fs-verity integrity protection for the deployed OS tree
- bootupd — EFI System Partition management independent of ostree
- GRUB — Fedora rhboot fork with BLS (Boot Loader Specification) support
- dracut — initramfs with
bootc,lvm, andostreemodules - ifupdown2 — repacked from Proxmox sources with systemd unit ordering patches
- systemd-timesyncd — repacked with
After=network-online.targetdrop-in - firstboot-user-setup — TUI wizard for hostname, locale, user creation, root password, sudo, and SSH policy
- Anaconda + Kickstart — online and offline ISO installer generation with LVM on XFS
- cosign / Sigstore — keyless container image signing via GitHub Actions OIDC
- Secure Boot — MOK-enrolled GRUB with debian-bootc signing key
- APT repository — signed APT repo on GitHub Pages for all custom packages
- Proxmox VE 9 — the hypervisor stack (kernel, pve-manager, corosync, etc.)
- Proxmox VE kernel (
proxmox-default-kernel) replaces the generic Debian kernel - chrony — NTP client, replacing systemd-timesyncd (disabled in the Proxmox VE stack)
- dnsmasq — lightweight DNS forwarder and DHCP server
- systemd-zram-generator — compressed swap via zram
- pve-manager (repacked) — Proxmox VE management stack, patched to suppress the "No valid subscription" web UI dialog
- libtemplate-perl (repacked) — Template Toolkit, patched to fix the ostree
mtime=0bug that otherwise makes the web UI return HTTP 500 - proxmox-firstboot.service — detects the WAN interface and injects it into
/etc/network/interfaces - pve-domain-set.service — sets the host FQDN in
/etc/hostsfrom the detected IP - fanctl — generic, self-calibrating temperature-driven fan controller (IPMI for any vendor, plus an hwmon backend for standard PCs)
- powerctl — measured CPU performance-per-watt operating point plus safe peripheral power management (SATA ALPM, PCI runtime PM, GPU cap, ZFS-aware disk spin-down, load-driven swappiness)
No. The repository may appear inactive between Debian or Proxmox releases by design.
The CI pipeline runs automatically on the first of every month and rebuilds the full distribution image from scratch with --no-cache, incorporating all upstream Debian and Proxmox security updates as they land. All custom .deb packages are rebuilt from source on the same schedule by the underlying debian-bootc pipeline.
The current target is Debian 13 Trixie with Proxmox VE 9. A new release cycle will begin when Debian 14 is published. Between now and then, the only expected changes are:
- Monthly automated security rebuilds (triggered by CI schedule).
- Version bumps for upstream components (bootc, ostree, bootupd, composefs, GRUB) when new releases are available.
- Proxmox VE version bumps when Proxmox publishes new releases.
The absence of frequent commits is a sign of stability, not abandonment.
bootc treats the entire operating system as an OCI container image. Rather than managing packages individually on a running system, the OS is built in a standard container pipeline, pushed to a registry, and applied atomically to the host using ostree as the on-disk storage engine. Updates are transactional and fully rollback-capable from the bootloader.
Why: Brings GitOps-style OS management — the same model that powers Fedora CoreOS and RHEL Image Mode — to Debian, with the stability and package ecosystem that Debian provides.
OSTree is the filesystem layer underneath bootc. It stores OS trees in a content-addressed object store modelled after Git, deploys them via hard links for storage efficiency, and makes every deployment atomic. It manages /usr, /etc, and /boot while delegating /var and /home to normal mutable storage — which is why /home, /root, /srv, /mnt, and /opt are symlinked into /var in this image.
This build inherits composefs support, dracut integration, and read-only sysroot configuration from debian-bootc.
composefs provides integrity protection for ostree deployments using fs-verity. Every file in the deployed OS tree is verified against a cryptographic hash at read time, making it impossible to tamper with the system at rest without detection.
Enabled in prepare-root.conf by the base image.
bootupd manages the EFI System Partition independently of the ostree-managed root filesystem. In a bootc system the EFI binaries (shim, GRUB) live outside the ostree tree and cannot be updated through the normal container image update path. bootupd bridges this gap by tracking and updating EFI binaries as a separate managed component.
Inherited from debian-bootc.
The standard Debian grub-efi-amd64-signed package does not include the blscfg and blsuki modules required by ostree and bootc for BLS (Boot Loader Specification) kernel entry management. The base image compiles GRUB from the Fedora rhboot/grub2 fork at a pinned commit, producing a grubx64.efi with full BLS support.
dracut generates the initramfs embedded in the deployed image. It is configured with the bootc, lvm, and ostree modules, zstd compression, and hostonly=no so the initramfs works on any hardware. The initramfs is built inside the container during the bootc package post-install hook (bootc-finalize), so the deployed image is fully self-contained.
Inherited from debian-bootc.
ifupdown2 is sourced from the Proxmox repository and repacked with targeted patches:
ifupdown2-pre.serviceis orderedAfter=ostree-remount.serviceto ensure the ostree read-only root is mounted before networking attempts to start.- An
ifupdown2-autoconfhelper writes/etc/network/interfaceson first boot (if not yet customised): it detects the WAN interface, configures IPv4 DHCP, and probes for a DHCPv6 server — theinet6 autostanza is only added when one actually answers. This avoids adhclient -6that would otherwise hang 30s and failnetworking.serviceon networks whose Router Advertisement requests DHCPv6 with no server; kernel SLAAC still provides IPv6 addressing regardless.
Proxmox VE is the hypervisor layer added by this repository. It provides:
- KVM-based virtualisation with a web-based management interface
- LXC container support
- Ceph and ZFS storage integration
- High-availability clustering via corosync
- Integrated backup and restore
The image ships the proxmox-ve metapackage, the Proxmox kernel (proxmox-default-kernel), postfix, and open-iscsi. The generic Debian kernel is removed during build.
chrony is the NTP client. It replaces systemd-timesyncd because the Proxmox VE stack disables timesyncd in favour of chrony for better time synchronisation in clustered environments. It is repacked with a chrony.service.d/wait-network.conf drop-in (After=/Wants=network-online.target) so chronyd does not start before the network is up and fail its initial time sync on a fast boot.
dnsmasq provides lightweight DNS forwarding and DHCP services. It is configured in /etc/dnsmasq.conf and enabled by default.
systemd-zram-generator creates a compressed swap device using zram. On systems with limited RAM, this provides swap-like functionality without the wear and latency of disk-based swap.
A TUI wizard modelled after the Raspberry Pi OS userconfig service. Runs on the first boot before the login prompt and guides through:
- Hostname (validated against RFC 952)
- System locale (
dpkg-reconfigure locales) - Keyboard layout (
dpkg-reconfigure keyboard-configuration) - Primary user account — username, full name, password (8 chars minimum)
- Root password
- Sudo privileges
- SSH root login policy
Runs as ExecStartPre on getty@tty1.service and writes /var/lib/firstboot-user-setup.done on completion to prevent re-execution.
Inherited from debian-bootc.
A lightweight bash daemon that regulates server fan speed from temperature over the local IPMI interface (/dev/ipmi0). It self-calibrates: it discovers the temperature sensors and their own Upper-Critical thresholds, measures the real fan/temperature response, and holds the hottest controlling sensor near a target, recalibrating on sustained drift. Fan writing uses an auto-detected vendor profile (Dell iDRAC, Supermicro, …); an hwmon backend (/sys/class/hwmon/*/pwm*) covers standard PCs without IPMI, and any unsupported platform falls back to read-only monitoring. Safety is temperature-based: a single failed fan is tolerated, and only a near-critical temperature forces maximum cooling. Configured in /etc/fanctl/fanctl.conf; calibration persists in /var/lib/fanctl/.
A bash daemon that keeps the CPU at its measured performance-per-watt efficiency knee. It sweeps the factory frequency steps under load, reads real package power from RAPL, picks the knee, and floats scaling_max_freq between the knee and the factory maximum in real time — driven by workload compute-boundedness (perf IPC) so frequency is only spent when it buys work. Everything stays inside the factory range (no undervolt, no overclock). It also applies safe peripheral power management, each knob auto-disabling when unsupported: governor/EPP, SATA link power (ALPM), PCI runtime PM, PCIe ASPM, GPU power cap (NVIDIA + AMD), C-state re-enabling, ZFS-aware idle-disk spin-down, and load-driven vm.swappiness. Configured in /etc/powerctl/powerctl.conf; calibration persists in /var/lib/powerctl/.
The installer ISOs are built from the Fedora Server netinstall ISO with Anaconda as the installation engine. Two Kickstart templates are provided:
| ISO | Source | Use case |
|---|---|---|
online |
Pulls ghcr.io/<repo>:latest from the registry at install time |
Networked install, always latest image |
offline |
OCI archive embedded in the ISO | Air-gapped install, pinned image version |
Both templates configure LVM on XFS, delegate user setup to firstboot-user-setup, and set a temporary root password that is replaced on first boot.
The ISO branding (sidebar, topbar, header, product name) and Anaconda module configuration are injected into the squashfs installer environment by scripts/inject-iso.sh.
The container image is signed with cosign via keyless Sigstore signing using the GitHub Actions OIDC identity. The signature is stored in the same GHCR namespace as the image.
Verify a pulled image:
cosign verify ghcr.io/DaemonCores/DaemonCores-VE:latest \
--certificate-identity-regexp \
"https://github.com/DaemonCores/DaemonCores-CI/.github/workflows/bootc-build.yml@refs/heads/main" \
--certificate-oidc-issuer \
"https://token.actions.githubusercontent.com"┌─────────────────────┐ ┌──────────────┐ ┌───────────────────┐
│ bootc-debs-builder │───▶│ bootc-build │───▶│ iso │
│ │ │ │ │ │
│ Compile from src: │ │ Build OCI │ │ Download Fedora │
│ - libcomposefs │ │ image from │ │ netinstall ISO │
│ - libostree │ │ Containerfile│ │ Inject branding │
│ - bootupd │ │ │ │ Render kickstart │
│ - grub-efi-signed │ │ Push to │ │ Build online ISO │
│ - bootc │ │ GHCR │ │ Build offline ISO│
│ - firstboot-setup │ │ │ │ │
│ - ifupdown2 repack │ │ Sign with │ │ │
│ - timesyncd repack │ │ cosign │ │ Upload to │
│ │ │ │ │ GitHub Releases │
│ Publish APT repo │ │ Smoke test: │ │ │
│ to GitHub Pages │ │ bootc lint │ │ │
└─────────────────────┘ └──────────────┘ └───────────────────┘
The Full Pipeline workflow (pipeline.yml) orchestrates all three stages with optional per-stage toggles, useful for rebuilding only the component that changed without running the full 30+ minute pipeline.
The DaemonCores-VE layer runs in the second and third stages: the OCI image build (bootc-build.yml) and the ISO generation (install-iso.yml). The first stage (bootc-debs-builder.yml) is inherited from debian-bootc and builds all custom packages.
Pinning actions to commit SHAs provides supply-chain immutability against tag mutation, but shifts the entire maintenance burden onto the repository owner: every dependency update requires a manual SHA rotation. In practice this leads to perpetually outdated pins — which provide false security rather than real security.
This repository instead relies on Dependabot (.github/dependabot.yml) for weekly automated pull requests covering both GitHub Actions and the Docker base image. Updates are reviewed and merged explicitly, providing full auditability without manual tracking overhead. All actions used are from well-established, high-visibility namespaces (actions/*, sigstore/*, morph027/*) where tag mutation would be immediately detected by the community.
The custom packages (bootc, ostree, composefs, bootupd, GRUB, firstboot-user-setup, ifupdown2, timesyncd) are published to a signed APT repository on GitHub Pages by the debian-bootc pipeline. DaemonCores-VE adds its own packages (pve-manager, proxmox kernel, etc.) from the official Proxmox repositories.
The signing key SHA-256 is hardcoded in the Containerfile and verified at build time before the key is trusted.
Add to an existing Debian Trixie system:
wget -O /usr/share/keyrings/daemoncores-ve-keyring.gpg \
https://daemoncores.github.io/DaemonCores-VE/gpg.key
# Optionally verify the key fingerprint before trusting it:
sha256sum /usr/share/keyrings/daemoncores-ve-keyring.gpg
cat > /etc/apt/sources.list.d/daemoncores-ve.sources << 'EOF'
Types: deb
URIs: https://daemoncores.github.io/DaemonCores-VE/
Suites: trixie
Components: main
Enabled: yes
Signed-By: /usr/share/keyrings/daemoncores-ve-keyring.gpg
EOF
apt updateThis image supports UEFI Secure Boot via the standard MOK (Machine Owner Key) mechanism provided by shim-signed.
UEFI firmware → shim-signed (Microsoft-signed) → grubx64.efi (debian-bootc-signed) → kernel
The grub-efi-amd64-signed package includes:
- A GRUB EFI binary signed with the debian-bootc Secure Boot signing key.
- The signing certificate at
/usr/share/debian-bootc/sb_signing.crt. - A
postinstscript that queues MOK enrollment automatically on package install.
MOK enrollment is queued automatically. On the first reboot after installation, the firmware will launch the blue MokManager screen:
- Select Enroll MOK
- Select Continue
- Select Yes
- Enter the enrollment password when prompted
- Select Reboot
The signing key is then enrolled permanently. All subsequent boots are fully verified end-to-end without any further action.
mokutil --sb-state # confirm Secure Boot is active
mokutil --list-enrolled # confirm the debian-bootc key is present| Secret | Workflow | Purpose |
|---|---|---|
PAT_PKG |
bootc-build.yml |
Authenticate Podman and Docker to push to GHCR |
APT_GPG_KEY |
bootc-debs-builder.yml |
Sign the APT repository published to GitHub Pages |
COSIGN_PRIVATE_KEY |
bootc-build.yml |
Private key for container image cosign signing |
COSIGN_PASSWORD |
bootc-build.yml |
Password for the cosign private key |
SB_SIGNING_KEY |
bootc-debs-builder.yml |
Private key for GRUB EFI Secure Boot signing |
SB_SIGNING_CERT |
bootc-debs-builder.yml |
Certificate for GRUB EFI Secure Boot signing |
- Fork this repository.
- Add
PAT_PKG,APT_GPG_KEY,COSIGN_PRIVATE_KEY,COSIGN_PASSWORD,SB_SIGNING_KEY, andSB_SIGNING_CERTin Settings → Secrets → Actions. - Run Actions → Full Pipeline with all three stages enabled.
- Download the produced ISO from the
install-isorelease. - Boot the ISO on the target machine and follow the first-boot wizard.
For monthly automated rebuilds, the pipeline.yml schedule (0 4 1 * *) will trigger automatically once the repository is active.
The kickstart installer sets a temporary default root password BootcDebug@0. This is a deliberate fallback: if the first-boot user-setup wizard fails to run or is interrupted, the system remains accessible via root login so you are not locked out of your own machine. The password is replaced by the wizard on first successful boot, and the root account is forced to change password via chage -d 0.
This default is identical to debian-bootc. The root password is never meant to survive first boot.
The Proxmox VE web interface displays a "You do not have a valid subscription for this server" dialog on every login when no Enterprise subscription is present. This is expected behaviour for the no-subscription repository tier.
The subscription check is patched inside a repacked pve-manager package: the pve-manager shipped by Proxmox is downloaded, /usr/share/perl5/PVE/API2/Subscription.pm is edited so the check reports status => "active" instead of status => "notfound", and the result is republished (+bootc1) to the DaemonCores-VE APT repository. apt then installs the patched package at image-build time — the same mechanism used for the other repacked packages (ifupdown2, chrony, openipmi). This replaces the former standalone removepvepopup script and its apt hook, which are no longer shipped.
The DaemonCores-VE image ships without a Proxmox Enterprise subscription. Without this patch, every login to the web UI would trigger a modal dialog that the user must dismiss manually before doing anything useful.
This is not an unsupported hack. The change is maintained by the author of DaemonCores-VE and has been running in production for over four years on the author's own servers and on those of friends and collaborators, without a single failure — including across every Proxmox VE update applied in that period. The modification is deliberately minimal and targeted: it flips a single boolean in the Perl code of Proxmox VE (status => "notfound" → status => "active"), nothing else. Because it is folded into the repacked package, each new image build reapplies it automatically.
Because the patch lives in the repacked pve-manager published to the DaemonCores-VE APT repository, apt prefers the +bootc1 version over the stock Proxmox package and reapplies it automatically on every image build. The change only touches a single, stable boolean field, so it has re-applied cleanly across versions.
Buying a Proxmox VE Support Subscription is a valid choice if you want a legitimate subscription key, access to the Enterprise repository, and vendor support — and it removes the popup without any local patch. Either path is acceptable; pick the one that fits your deployment. Revert this patch if you switch to a paid subscription.
On a freshly deployed atomic image, the Proxmox VE web UI could return HTTP 500 with index.html.tpl: not found, even though the template is present on disk.
ostree canonicalizes every /usr file mtime to 0 (epoch) when it deploys the image. Template Toolkit (libtemplate-perl) uses a template's mtime as its "does this template exist?" truth value; a zero mtime is falsy, so every template shipped under /usr is treated as missing. This is intrinsic to the ostree deployment layer — it appears only after deployment, not in the OCI image (which still carries real mtimes), so it is not a Containerfile misconfiguration.
libtemplate-perl is repacked (+bootc1) with a small patch to _template_modified (Template/Provider.pm): a real-but-zero mtime is mapped to 1, while genuinely missing files still return undef. This restores correct template existence detection on ostree without disturbing the freshness cache, and fixes the web UI globally — the correction applies to every Template Toolkit template, not just the Proxmox UI.