[PR #6] [MERGED] Generic daemon: safe GC, per-container config, tag ownership, ZFS-only templates #9

Closed
opened 2026-10-15 12:51:15 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/games-on-whales/LXC2Docker/pull/6
Author: @JBailes
Created: 4/17/2026
Status: ✅ Merged
Merged: 4/17/2026
Merged by: @JBailes

Base: docker-lxc-daemon ← Head: aimee/session/ce19f10a


📝 Commits (4)

  • 83d076e Genericize: safe ephemeral GC, per-container storage/bridge, vendor-neutral labels
  • bd0b705 Default to permanent PVE CTs; route apps through OCI in PVE mode
  • 5565559 Tag-based ownership: ManagedTag identifies daemon-visible PVE CTs
  • 744e374 ZFS-only OCI templates; stop polluting the PVE UI

📊 Changes

11 files changed (+833 additions, -255 deletions)

View changed files

📝 Makefile (+0 -3)
📝 cmd/docker-lxc-daemon/main.go (+92 -10)
📝 internal/api/containers.go (+109 -6)
📝 internal/api/system.go (+4 -3)
📝 internal/api/types.go (+4 -0)
📝 internal/image/resolver.go (+29 -19)
📝 internal/lxc/config.go (+58 -8)
📝 internal/lxc/manager.go (+515 -183)
📝 internal/store/store.go (+21 -10)
📝 systemd/docker-lxc-daemon.service (+1 -1)
➖ systemd/wolf-run-dirs.conf (+0 -12)

📄 Description

Summary

Five-step refactor turning the daemon into a vendor-neutral, Docker-API-compatible front-end for LXC on Proxmox, validated end-to-end against Portainer.

1. Safe ephemeral GC

Positive identification only. The GC reaps a container only when all of these are true:

  • store record Ephemeral == true
  • VMID == 0
  • on-disk LXC config contains # docker-lxc-daemon: ephemeral
  • StartedAt != nil (never reap a container that hasn't started)
  • state is exited

Drops the previous Wolf-specific orphan-detection (PulseAudio/Wolf-UI naming heuristics).

2. Per-container storage / bridge / ISOs

  • Labels: dld.storage, dld.bridge, dld.iso=storage:volid[:dest] (comma-separated)
  • New repeatable --bridge name=prefix/subnet:gateway flag for multi-bridge daemons
  • Legacy --lan-bridge/--lan-prefix/etc. retained as backwards-compat default

3. Vendor-neutral labels

  • Primary: dld.pve, dld.lan, dld.ephemeral
  • Legacy gow.pve / gow.lan accepted with a one-time deprecation log per process
  • Drops systemd/wolf-run-dirs.conf tmpfiles drop-in and Wolf-specific comments

4. Permanent default + tag-based ownership

  • Containers default to permanent PVE CTs in PVE mode (matches Docker --rm-off semantics)
  • Ephemeral is opt-in via Docker's --rm (HostConfig.AutoRemove) or dld.ephemeral=true
  • Every daemon-managed permanent CT gets the Proxmox tag dld-managed
  • Operators adopt pre-existing PVE CTs by adding the tag in the PVE UI; daemon discovers them on every list and exposes them through the Docker API as adopted records (synthesized hex IDs, deterministic across restarts)
  • reconcile() backfills the tag on daemon-created CTs that pre-date this change
  • Untagged CTs are invisible — bounded blast radius for any mass-delete from Portainer
  • Built-in app shortcut registry (nginx → alpine + nginx package) is bypassed in PVE mode so standard Docker image names get standard Docker semantics (real OCI pull, eligible to be permanent CTs)

5. ZFS-only OCI templates

OCI image pulls no longer register a Proxmox CT template (which would clutter the PVE UI). Templates live as raw ZFS datasets at <storage>/dld-templates/<name> with an @tmpl snapshot. Permanent CT creation uses zfs clone <snap> → subvol-<vmid>-disk-0 + direct <vmid>.conf write. Existing legacy-template records keep working until the operator re-pulls.

Test plan (executed on real Proxmox host with ~50 unrelated PVE CTs)

  • Build clean (go build, go vet, make build)
  • Daemon starts cleanly, all pre-existing PVE CTs untouched
  • Permanent CT default: docker create nginx:alpine (no labels) → PVE CT in UI, GC ignores it across multiple intervals
  • Ephemeral opt-in: docker create --label dld.ephemeral=true → invisible in PVE UI, GC reaps within 30s of exited
  • Adoption: operator adds dld-managed tag to a PVE CT → surfaces in Portainer immediately, lifecycle ops route through pct
  • Tag backfill on daemon-created CTs from before this change (verified with original Portainer CT VMID 103)
  • OCI route in PVE mode: nginx:alpine becomes a real Docker image (not the legacy app template)
  • No template VMIDs visible in PVE UI after re-pull (pct list clean of tmpl-* entries)
  • Portainer-driven full lifecycle: pull, create, start, stop, restart, delete, inspect, port forwarding

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/games-on-whales/LXC2Docker/pull/6 **Author:** [@JBailes](https://github.com/JBailes) **Created:** 4/17/2026 **Status:** ✅ Merged **Merged:** 4/17/2026 **Merged by:** [@JBailes](https://github.com/JBailes) **Base:** `docker-lxc-daemon` ← **Head:** `aimee/session/ce19f10a` --- ### 📝 Commits (4) - [`83d076e`](https://github.com/games-on-whales/LXC2Docker/commit/83d076eb829ef53a8e0842d2c815606f0d5aab2b) Genericize: safe ephemeral GC, per-container storage/bridge, vendor-neutral labels - [`bd0b705`](https://github.com/games-on-whales/LXC2Docker/commit/bd0b7054d9a73cff50061d2036bf0de53fb5aaf7) Default to permanent PVE CTs; route apps through OCI in PVE mode - [`5565559`](https://github.com/games-on-whales/LXC2Docker/commit/5565559955a616cd6a02a8c17cf7057befb83884) Tag-based ownership: ManagedTag identifies daemon-visible PVE CTs - [`744e374`](https://github.com/games-on-whales/LXC2Docker/commit/744e374cbab36b0f90b6c4cb686677f69911881a) ZFS-only OCI templates; stop polluting the PVE UI ### 📊 Changes **11 files changed** (+833 additions, -255 deletions) <details> <summary>View changed files</summary> 📝 `Makefile` (+0 -3) 📝 `cmd/docker-lxc-daemon/main.go` (+92 -10) 📝 `internal/api/containers.go` (+109 -6) 📝 `internal/api/system.go` (+4 -3) 📝 `internal/api/types.go` (+4 -0) 📝 `internal/image/resolver.go` (+29 -19) 📝 `internal/lxc/config.go` (+58 -8) 📝 `internal/lxc/manager.go` (+515 -183) 📝 `internal/store/store.go` (+21 -10) 📝 `systemd/docker-lxc-daemon.service` (+1 -1) ➖ `systemd/wolf-run-dirs.conf` (+0 -12) </details> ### 📄 Description ## Summary Five-step refactor turning the daemon into a vendor-neutral, Docker-API-compatible front-end for LXC on Proxmox, validated end-to-end against Portainer. ### 1. Safe ephemeral GC Positive identification only. The GC reaps a container only when **all** of these are true: - store record `Ephemeral == true` - `VMID == 0` - on-disk LXC config contains `# docker-lxc-daemon: ephemeral` - `StartedAt != nil` (never reap a container that hasn't started) - state is `exited` Drops the previous Wolf-specific orphan-detection (PulseAudio/Wolf-UI naming heuristics). ### 2. Per-container storage / bridge / ISOs - Labels: `dld.storage`, `dld.bridge`, `dld.iso=storage:volid[:dest]` (comma-separated) - New repeatable `--bridge name=prefix/subnet:gateway` flag for multi-bridge daemons - Legacy `--lan-bridge`/`--lan-prefix`/etc. retained as backwards-compat default ### 3. Vendor-neutral labels - Primary: `dld.pve`, `dld.lan`, `dld.ephemeral` - Legacy `gow.pve` / `gow.lan` accepted with a one-time deprecation log per process - Drops `systemd/wolf-run-dirs.conf` tmpfiles drop-in and Wolf-specific comments ### 4. Permanent default + tag-based ownership - Containers default to **permanent** PVE CTs in PVE mode (matches Docker `--rm`-off semantics) - Ephemeral is opt-in via Docker's `--rm` (`HostConfig.AutoRemove`) or `dld.ephemeral=true` - Every daemon-managed permanent CT gets the Proxmox tag `dld-managed` - Operators adopt pre-existing PVE CTs by adding the tag in the PVE UI; daemon discovers them on every list and exposes them through the Docker API as adopted records (synthesized hex IDs, deterministic across restarts) - `reconcile()` backfills the tag on daemon-created CTs that pre-date this change - Untagged CTs are invisible — bounded blast radius for any mass-delete from Portainer - Built-in app shortcut registry (`nginx` → alpine + nginx package) is bypassed in PVE mode so standard Docker image names get standard Docker semantics (real OCI pull, eligible to be permanent CTs) ### 5. ZFS-only OCI templates OCI image pulls no longer register a Proxmox CT template (which would clutter the PVE UI). Templates live as raw ZFS datasets at `<storage>/dld-templates/<name>` with an `@tmpl` snapshot. Permanent CT creation uses `zfs clone <snap> → subvol-<vmid>-disk-0` + direct `<vmid>.conf` write. Existing legacy-template records keep working until the operator re-pulls. ## Test plan (executed on real Proxmox host with ~50 unrelated PVE CTs) - [x] Build clean (`go build`, `go vet`, `make build`) - [x] Daemon starts cleanly, all pre-existing PVE CTs untouched - [x] Permanent CT default: `docker create nginx:alpine` (no labels) → PVE CT in UI, GC ignores it across multiple intervals - [x] Ephemeral opt-in: `docker create --label dld.ephemeral=true` → invisible in PVE UI, GC reaps within 30s of `exited` - [x] Adoption: operator adds `dld-managed` tag to a PVE CT → surfaces in Portainer immediately, lifecycle ops route through `pct` - [x] Tag backfill on daemon-created CTs from before this change (verified with original Portainer CT VMID 103) - [x] OCI route in PVE mode: `nginx:alpine` becomes a real Docker image (not the legacy app template) - [x] No template VMIDs visible in PVE UI after re-pull (`pct list` clean of `tmpl-*` entries) - [x] Portainer-driven full lifecycle: pull, create, start, stop, restart, delete, inspect, port forwarding --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-15 12:51:15 +00:00
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
games-on-whales/LXC2Docker#9
No description provided.