{"id":47352938,"url":"https://github.com/mikkovihonen/quadletman","last_synced_at":"2026-04-12T20:00:43.870Z","repository":{"id":343965607,"uuid":"1179905053","full_name":"mikkovihonen/quadletman","owner":"mikkovihonen","description":"Web application for managing rootless podman containers with quadlets and systemd","archived":false,"fork":false,"pushed_at":"2026-03-25T18:51:16.000Z","size":231816,"stargazers_count":22,"open_issues_count":7,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-03-25T23:52:43.495Z","etag":null,"topics":["linux","podman","podman-systemd","quadlet","quadlets","rootless-container","rootless-podman","self-hosted","systemd"],"latest_commit_sha":null,"homepage":"https://mikkovihonen.github.io/quadletman/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mikkovihonen.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":"docs/governance.md","roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-12T13:57:37.000Z","updated_at":"2026-03-25T18:42:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mikkovihonen/quadletman","commit_stats":null,"previous_names":["mikkovihonen/quadletman"],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/mikkovihonen/quadletman","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikkovihonen%2Fquadletman","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikkovihonen%2Fquadletman/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikkovihonen%2Fquadletman/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikkovihonen%2Fquadletman/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mikkovihonen","download_url":"https://codeload.github.com/mikkovihonen/quadletman/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikkovihonen%2Fquadletman/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31292637,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T21:15:39.731Z","status":"ssl_error","status_checked_at":"2026-04-01T21:15:34.046Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["linux","podman","podman-systemd","quadlet","quadlets","rootless-container","rootless-podman","self-hosted","systemd"],"created_at":"2026-03-18T01:11:59.801Z","updated_at":"2026-04-01T22:14:57.241Z","avatar_url":"https://github.com/mikkovihonen.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# quadletman\n\n[![CI](https://github.com/mikkovihonen/quadletman/actions/workflows/ci.yml/badge.svg)](https://github.com/mikkovihonen/quadletman/actions/workflows/ci.yml)\n[![Release](https://github.com/mikkovihonen/quadletman/actions/workflows/release.yml/badge.svg)](https://github.com/mikkovihonen/quadletman/actions/workflows/release.yml)\n[![Latest release](https://img.shields.io/github/v/release/mikkovihonen/quadletman)](https://github.com/mikkovihonen/quadletman/releases/latest)\n[![License](https://img.shields.io/github/license/mikkovihonen/quadletman)](LICENSE)\n[![Python](https://img.shields.io/badge/python-3.12%2B-blue)](pyproject.toml)\n[![codecov](https://codecov.io/gh/mikkovihonen/quadletman/branch/main/graph/badge.svg?token=6W1ZKPHBD4)](https://codecov.io/gh/mikkovihonen/quadletman)\n\nquadletman is a browser-based admin UI for running Podman containers on a headless Linux\nserver. Instead of talking to the Podman socket at runtime, it generates and manages\n**Quadlet unit files** — the systemd-native way to declare containers as persistent\nservices. Each group of containers lives in a **compartment**: an isolated environment\nbacked by a dedicated Linux system user, its own volume storage, and its own Podman secret\nand registry-credential store.\n\nYou point a browser at the server, log in with your existing OS credentials, and get a\nfull lifecycle UI: create compartments, define containers and pods, manage volumes and\nsecrets, schedule timers, watch live logs, and monitor resource usage. All without\ntouching the command line.\n\n![quadletman screenshot](docs/assets/screenshot.jpeg)\n\nSee **[docs/features.md](docs/features.md)** for a full feature breakdown.\n\n\u003e\n\u003e **⚠ Beta software — not for production use.**\n\u003e\n\u003e quadletman is in development and is not suitable for production use.\n\u003e \n\u003e It is provided here for evaluation purposes \n\u003e\n\n## Comparison with Similar Tools\n\nquadletman targets a specific gap: a **headless server-side web UI** that manages containers\nat the **systemd unit file level** rather than via the Podman socket API.\n\n| Tool | Interface | Creates/edits Quadlet unit files | Per-service OS user isolation | Server-side web UI |\n|---|---|---|---|---|\n| **quadletman** | Web (HTMX) | **Yes** | **Yes** | **Yes** |\n| [cockpit-podman](https://github.com/cockpit-project/cockpit-podman) | Web (Cockpit) | No — shows running containers only | No | Yes |\n| [Podman Desktop](https://github.com/podman-desktop/podman-desktop) | Desktop app (Electron) | Yes (via extension) | No | No |\n| [Portainer](https://github.com/portainer/portainer) | Web | No | No | Yes |\n| [Dockge](https://github.com/louislam/dockge) | Web | No — Docker Compose only | No | Yes |\n| [podman-tui](https://github.com/containers/podman-tui) | Terminal (TUI) | No | No | No |\n\n**cockpit-podman** is the closest server-side alternative. It shows Podman containers (including\nones already started by Quadlet units) but does not create or edit unit files, manage system\nusers, or handle volumes with SELinux labels. It is a read/run UI, not a provisioning tool.\n\n**Podman Desktop** is the only other tool that actually generates and edits Quadlet unit files\nthrough a form interface, but it is a developer desktop application requiring an installed GUI\nenvironment — not a tool for administering a headless Linux server remotely.\n\n**Portainer** and **Dockge** are Docker-centric and treat Podman as a drop-in Docker socket\nreplacement. Neither has any concept of Quadlet unit files, systemd user services, or\nper-service Linux user isolation.\n\nquadletman's distinctive combination — generating Quadlet unit files, running each service\ngroup under its own isolated Linux user, managing host volumes with SELinux contexts, and\ndoing all of this from a browser against a headless server — is not covered by any existing\ntool.\n\n## Requirements\n\n- Linux on x86_64 or ARM64 (aarch64)\n- Python 3.12+\n- Podman with Quadlet support (Podman 4.4+; see [docs/governance.md](docs/governance.md) for the full supported-versions table)\n- systemd (with `loginctl` and `machinectl`)\n- Linux PAM development headers (`pam-devel` / `libpam0g-dev`)\n- Optional: SELinux tools (`policycoreutils-python-utils`) for context management\n- Optional: `keyutils` for kernel keyring credential isolation (credentials stored in kernel\n  memory instead of process memory; auto-detected at startup)\n\n## Installation\n\n### From the package repository (recommended)\n\nPre-built packages are available from the [package repository](https://mikkovihonen.github.io/quadletman/packages/unstable/).\nFollow the install instructions on the landing page for your distribution.\n\n### Build from source\n\n**Fedora / RHEL / AlmaLinux / Rocky Linux (RPM):**\n\n```bash\nbash packaging/build-rpm.sh\nsudo dnf install ~/rpmbuild/RPMS/*/quadletman-*.rpm\n```\n\n**Ubuntu / Debian (DEB):**\n\n```bash\nbash packaging/build-deb.sh\nsudo apt install ./quadletman_*.deb\n```\n\n**Generic (any systemd Linux):**\n\n```bash\nsudo bash scripts/install.sh\n```\n\nSee **[docs/packaging.md](docs/packaging.md)** for build prerequisites, how packages are\nstructured, upgrade instructions, and smoke-test VM setup.\nSee **[docs/runbook.md](docs/runbook.md)** for first-time setup, configuration, and day-to-day operations.\n\nWith the default configuration, the web UI will be available at `http://\u003chost\u003e:8080`.\n\n## Configuration\n\nSee **[docs/runbook.md — Configuration](docs/runbook.md#configuration)** for all\n`QUADLETMAN_*` environment variables and how to set them via `/etc/quadletman/quadletman.env`.\n\n## Further Reading\n\n| Document | Contents |\n|---|---|\n| [docs/runbook.md](docs/runbook.md) | Post-install setup, configuration, day-to-day operations, troubleshooting, upgrade, and uninstall |\n| [docs/features.md](docs/features.md) | Full feature breakdown — compartments, containers, volumes, scheduling, monitoring, process and connection monitors |\n| [docs/architecture.md](docs/architecture.md) | Compartment roots, helper users, UID/GID mapping, Quadlet files, volumes |\n| [docs/development.md](docs/development.md) | Dev setup, running locally, WSL2 (incl. connection monitor limitations), contributing, migrations |\n| [docs/packaging.md](docs/packaging.md) | Build prerequisites, package structure, upgrade instructions, smoke-test VMs |\n| [docs/testing.md](docs/testing.md) | Unit/integration tests |\n| [docs/ways-of-working.md](docs/ways-of-working.md) | Branch strategy, PR process, CI pipeline, versioning scheme, release process |\n| [docs/ui-development.md](docs/ui-development.md) | UI state management, Alpine/HTMX patterns, macros, button styles, modals |\n| [docs/localization.md](docs/localization.md) | Localization workflow, Finnish vocabulary, adding new languages |\n| [docs/governance.md](docs/governance.md) | Upstream Podman alignment, VersionSpan model, release monitoring, supported versions |\n| [docs/upstream_monitoring.md](docs/upstream_monitoring.md) | Podman release monitor workflow and feature-check script |\n| [CLAUDE.md](CLAUDE.md) | AI/contributor conventions — code patterns, security checklist, version gating |\n\n## Security Notes\n\n\u003e\n\u003e NOTE! This is a side project of a single independent developer.\n\u003e \n\u003e Bug reports are appreciated, but no reaction or fix times can be given. Use on your own risk.\n\u003e\n\u003e For security details, see: [SECURITY](https://github.com/mikkovihonen/quadletman/SECURITY.md)\n\u003e\n\n### Network exposure\n\nquadletman runs as a dedicated `quadletman` system user and should **never** be exposed directly to the internet.\nTwo recommended deployment patterns:\n\n**Reverse proxy with HTTPS** — put quadletman behind nginx or Caddy, terminate TLS\nat the proxy, and set `QUADLETMAN_SECURE_COOKIES=true`:\n\n```\nInternet → nginx (HTTPS :443) → quadletman (HTTP :8080 on localhost)\n```\n\n**Unix socket over SSH tunnel** — bind quadletman to a Unix socket instead of a TCP\nport and access it through an SSH tunnel.  This avoids opening any network port:\n\n```bash\n# Server: /etc/quadletman/quadletman.env\nQUADLETMAN_UNIX_SOCKET=/run/quadletman/quadletman.sock\n\n# Client: forward local port to the remote socket\nssh -L 8080:/run/quadletman/quadletman.sock user@server\n# Then open http://localhost:8080 in a browser\n```\n\nWhen `QUADLETMAN_UNIX_SOCKET` is set, the `host` and `port` settings are ignored.\nThis is the most restrictive option — no TCP listener exists on the server at all.\n\n### Authentication and sessions\n\n- Authentication uses the host's PAM stack — no separate user database\n- Session credentials for privilege escalation are stored in the Linux kernel keyring\n  (process-scoped, isolated from application memory) when `libkeyutils` is available;\n  falls back to Fernet-encrypted in-memory storage otherwise\n- Admin operations escalate via the authenticated user's sudo credentials; monitoring runs via per-user agents without sudo\n- Only users in `sudo`/`wheel` groups are authorized, matching OS admin conventions\n- Session cookies: HTTPOnly, SameSite=Strict; set `QUADLETMAN_SECURE_COOKIES=true` for the\n  Secure flag (required when serving over HTTPS)\n- `QUADLETMAN_TEST_AUTH_USER` bypasses PAM entirely — **never set this in production**; it\n  exists solely for Playwright E2E tests running against a dev server\n\n### Request protection\n\n- CSRF protection: double-submit cookie pattern — every mutating request must include an\n  `X-CSRF-Token` header matching the `qm_csrf` cookie\n- Security headers on every response: `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`,\n  Content Security Policy, `Referrer-Policy: same-origin` (HSTS when `secure_cookies=True`)\n\n### Input validation and host hardening\n\n- Container image references and bind-mount paths are validated server-side; sensitive host\n  directories (`/etc`, `/proc`, `/sys`, etc.) cannot be bind-mounted into containers\n- File writes use `O_NOFOLLOW` to prevent symlink-swap (TOCTOU) attacks inside volume directories\n- All user-supplied strings pass through branded-type validation before reaching service\n  functions or the filesystem\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikkovihonen%2Fquadletman","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmikkovihonen%2Fquadletman","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikkovihonen%2Fquadletman/lists"}