Skip to content

refactor: podman quadlet sub-command#28335

Merged
Luap99 merged 1 commit into
podman-container-tools:mainfrom
axel7083:refactor/quadlet-applications
Jun 5, 2026
Merged

refactor: podman quadlet sub-command#28335
Luap99 merged 1 commit into
podman-container-tools:mainfrom
axel7083:refactor/quadlet-applications

Conversation

@axel7083

@axel7083 axel7083 commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Checklist

Ensure you have completed the following checklist for your pull request to be reviewed:

  • Certify you wrote the patch or otherwise have the right to pass it on as an open-source patch by signing all
    commits. (git commit -s). (If needed, use git commit -s --amend). The author email must match
    the sign-off email address. See CONTRIBUTING.md
    for more information.
  • Referenced issues using Fixes: #00000 in commit message (if applicable)
  • Tests have been added/updated (or no tests are needed)
  • Documentation has been updated (or no documentation changes are needed)
  • All commits pass make validatepr (format/lint checks)
  • Release note entered in the section below (or None if no user-facing changes)

Does this PR introduce a user-facing change?

refactor the podman quadlet command

While working on #28117 and the comment from @Luap99 (#28117 (comment)) I had a lot of issues working around the .app and .asset file, a suggestion made was to get rid of those and consider an application as a folder.

Here is a POC / proposal of how it could works

podman quadlet install

When trying to install from a directory, you will need to specify --application as we want to avoid dumping all the content of the directory at the root.

Installing from a directory (support recursive)

$: podman quadlet install --application flask-redis ./flask-redis
$: ree /home/axel7083/.config/containers/systemd
/home/axel7083/.config/containers/systemd
└── flask-redis
    ├── app
    │   ├── app.py
    │   ├── Containerfile
    │   └── requirements.txt
    ├── flask.kube
    ├── play.yaml
    └── README.md

podman quadlet ls --format=json

Following flask-redis installed above we get

$: podman quadlet ls --format=json
[
  {
    "Name": "flask.kube",
    "UnitName": "flask.service",
    "Path": "/home/axel7083/.config/containers/systemd/flask-redis/flask.kube",
    "Status": "inactive/dead",
    "App": "flask-redis" <-- application name is the name of the sub directory
  },
]

Now let's add an nginx.image

$: podman quadlet install ./nginx.image
$: podman quadlet ls --format=json
[
  {
    "Name": "flask.kube",
    "UnitName": "flask.service",
    "Path": "/home/axel7083/.config/containers/systemd/flask-redis/flask.kube",
    "Status": "inactive/dead",
    "App": "flask-redis"
  },
  {
    "Name": "nginx.image",
    "UnitName": "nginx-image.service",
    "Path": "/home/axel7083/.config/containers/systemd/nginx.image",
    "Status": "inactive/dead",
    "App": "" <-- No application name as it is at the root
  }
]

⚠️ this is only taking the first directory, if we have /home/axel7083/.config/containers/systemd/flask-redis/nested/flask.kube" it will still be flask-redis.

podman quadlet rm

I added a --recursive option, the rm support two input, a quadlet file (E.g. foo.image) or an application name. However when trying t o delete an application it will throw an error

$: /bin/podman quadlet rm flask-redis
Error: unable to remove Quadlet: cannot find application "flask-redis"

You need to specify the --recursive flag to delete the quadlets in the application

I did not in this POC deleted the full application directory, I was not sure, open to suggestion

@github-actions github-actions Bot added the kind/api-change Change to remote API; merits scrutiny label Mar 20, 2026

@Luap99 Luap99 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only a short high level look I am definitely in favour of this directory approach.

Question do we care about backwards compatibility? We still have 3-4 weeks till podman6 so we can break if wanted. And given quadlet install is a rather new command maybe reworking that now without worrying about compatibility is the easiest.

cc @mheon

@mheon

mheon commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Ummm. Given how new these commands are, it would not be unprecedented to break folks, but it's still a very bad user experience...

Our migration options are also not particularly pretty though. I suppose we could detect legacy project files and convert to a directory and remove the file on finding them?

@Luap99

Luap99 commented Mar 24, 2026

Copy link
Copy Markdown
Member

Our migration options are also not particularly pretty though. I suppose we could detect legacy project files and convert to a directory and remove the file on finding them?

Convert when? "inside quadlet"? when running "podman quadlet list/install/remove"? For install/remove it is not clear to me we should migrate other files. And on list it seems strange to do a rewrite of files, i.e. there is no sane way to make this in a atomic fashion so we risk leaving the files in a bad state when getting killed.

@simonbrauner

Copy link
Copy Markdown
Contributor

I also like using directories instead of the .app files, introducing the --application option instead depending on directory name, as well as removing the If a quadlet is part of an application, removing that specific quadlet will remove the entire application. functionality.

Service naming conflicts

But this introduces the possibility of having multiple services of the same name.

$ podman quadlet install postgres-17-test --application=a
/home/sbrauner/.config/containers/systemd/a/A.container
/home/sbrauner/.config/containers/systemd/a/B.container
/home/sbrauner/.config/containers/systemd/a/README.md
$ podman quadlet install postgres-17-test --application=b
/home/sbrauner/.config/containers/systemd/b/A.container
/home/sbrauner/.config/containers/systemd/b/B.container
/home/sbrauner/.config/containers/systemd/b/README.md
$ podman quadlet list
ERRO[0000] Unexpected unit returned by systemd - was not searching for A.service 
ERRO[0000] Unexpected unit returned by systemd - was not searching for B.service 
NAME         UNIT NAME   PATH ON DISK                                             STATUS         APPLICATION
A.container  A.service   /home/sbrauner/.config/containers/systemd/b/A.container  inactive/dead  b
B.container  B.service   /home/sbrauner/.config/containers/systemd/b/B.container  inactive/dead  b
             A.service                                                            inactive/dead  
             B.service                                                            inactive/dead  

So I suppose it should either check for naming conflicts, or make the application name part of the service name, so that multiple quadlets with the same application-local name can exist independently.

Implicit merge of applications

I am also thinking that maybe it would be slightly more convenient for the user if they were notified that they are adding quadlets to an existing application. Now it happens implicitly, so it can hypothetically happen that the user:

creates application `webserver`, with containers `A` and `B`
forgets about it
creates different application and decides to also call it `webserver`, with containers `C` and `D`
removes `webserver`, thinking they are removing `C` and `D`, but `A` and `B` would also get removed.

@axel7083 axel7083 force-pushed the refactor/quadlet-applications branch 6 times, most recently from df7607c to 42c6321 Compare April 13, 2026 12:29
@axel7083 axel7083 changed the title refactor: podman quadlet sub-command WIP / POC refactor: podman quadlet sub-command Apr 13, 2026
@axel7083 axel7083 marked this pull request as ready for review April 13, 2026 13:09
@axel7083 axel7083 requested a review from Luap99 April 13, 2026 13:09
@Luap99 Luap99 added the 6.0 Breaking changes for Podman 6.0 label Apr 21, 2026
@simonbrauner

Copy link
Copy Markdown
Contributor

Also took a mostly high level look, it looks nice overall.

It does not look like it's ready to be merged, there are TODOs.

My previous comment should probably be addressed, as installing multiple quadlets with the same name breaks.

Removing the whole application keeps non-quadlet files and the directory itself.

The word refactor does not seem right, as this changes functionality.

@axel7083 axel7083 force-pushed the refactor/quadlet-applications branch from 42c6321 to 5d12165 Compare April 28, 2026 13:14
@axel7083

Copy link
Copy Markdown
Contributor Author

Hey @simonbrauner !

It does not look like it's ready to be merged, there are TODOs.

I removed the TODOs

My previous comment should probably be addressed, as installing multiple quadlets with the same name breaks.

This is something already possible with the current, so not sure how this could been addressed? Maybe we need a dedicated issue to try to address that?

Removing the whole application keeps non-quadlet files and the directory itself.

It does if there is no errors while removing each quadlet in the application, the directory will be removed 👍

https://github.com/containers/podman/blob/5d121652e8dc28158bb64ec98fc92c46fd621e9f/pkg/domain/infra/abi/quadlet.go#L626-L637

@axel7083 axel7083 force-pushed the refactor/quadlet-applications branch from 5d12165 to af3ed8d Compare April 28, 2026 13:25
@packit-as-a-service

Copy link
Copy Markdown

[NON-BLOCKING] Packit jobs failed. @containers/packit-build please check. Everyone else, feel free to ignore.

@simonbrauner

simonbrauner commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Hello @axel7083.

I removed the TODOs

There is still one about validation of application name https://github.com/containers/podman/pull/28335/changes#diff-98a6e1ecc741f449262cca173c6e557c64dbf2706e5b062967b07b76018b62adR10

This is something already possible with the current, so not sure how this could been addressed? Maybe we need a dedicated issue to try to address that?

I would say this is introduced by the possibility of having multiple quadlets of the same name across different applications.

With the original approach, installing multiple quadlets of the same name fails:

$ podman quadlet install postgres-17-test
/home/sbrauner/.config/containers/systemd/README.md
/home/sbrauner/.config/containers/systemd/A.container
/home/sbrauner/.config/containers/systemd/B.container

$ podman/bin/podman quadlet install postgres-17-test-2
Error: quadlet "postgres-17-test-2/A.container" failed to install: a Quadlet with name A.container already exists, refusing to overwrite
Error: quadlet "postgres-17-test-2/B.container" failed to install: a Quadlet with name B.container already exists, refusing to overwrite
Error: quadlet "postgres-17-test-2/README.md" failed to install: a Quadlet with name README.md already exists, refusing to overwrite
Error: errors occurred installing some Quadlets

So I am thinking, the two ideas would be:

  1. Check if quadlet of that name exists anywhere regardless of the application and fail if it does.
  2. Add some prefix to the unit name, so that systemd is not confused, for example a/A.service and b/A.service would be two separate services which just happen to have the same file name of a corresponding quadlet, but can coexist independently.

But I am not saying this is something that has to be done as part of this PR, I was just thinking of the consequences of your changes and this is what I thought of.

It does if there is no errors while removing each quadlet in the application, the directory will be removed 👍

👍

@axel7083

Copy link
Copy Markdown
Contributor Author

So I am thinking, the two ideas would be:

  1. Check if quadlet of that name exists anywhere regardless of the application and fail if it does.

I will try to add this tomorrow morning 👍

@axel7083

Copy link
Copy Markdown
Contributor Author

@simonbrauner I reviewed the problem, and the current behavior that you are seeing is not preventing "duplicate" quadlets with the same service name, but rather preventing you to override, the logic is technically still here.

If you try to podman quadlet install ./foo.image twice with the change you will get the same error.

https://github.com/containers/podman/blob/af3ed8dd3c6bd4e4d91391963e08b0b04a9eff2d/pkg/domain/infra/abi/quadlet.go#L285-L287

The problem of duplicates service name is a bit more annoying to solve, let me explain. A service name is either define by its name or by its Service=<name> key.

The quadlet systemd generator is reading and parsing the unit file, and lookup for the Service key in the [Unit] group as shown bellow

https://github.com/containers/podman/blob/2cc3be73323e9dfbeaa9d603cac7f359bb8b8f05/pkg/systemd/quadlet/quadlet.go#L1580-L1590

But for us this is a little bit a challenge, as when should we fails? If the user is installing foo.image and bar.image and they both have Service=hello.service this is a problem, and currently one will be randomly picked (I reported the problem in #28118).


So my question would be, when should we fails?

If the user already have multiple quadlets with the same service name, if we try to install another should we fail?

Does the quadlet generator should fail if multiple quadlets have the same service name? Or just a warning in the stderr?

I think we can parse every quadlets we try to install and check that in the list of quadlets we try to install (when passing a directory or a tar) we could read and parse them, but what if they are invalid? currently I don't think we are checking if the quadlets files are valid 🤔 .

Anyway, I think this is going a bit out of scope of this PR, and as it is already big enough, I think we may skip the service name issue, wdyt?

@axel7083 axel7083 force-pushed the refactor/quadlet-applications branch 3 times, most recently from 2fd9762 to 97fcbb6 Compare April 30, 2026 07:18
@simonbrauner

Copy link
Copy Markdown
Contributor

Anyway, I think this is going a bit out of scope of this PR, and as it is already big enough, I think we may skip the service name issue, wdyt?

Yes, sure. Initially I thought that this is strange behavior introduced by this PR, but if it's just something that was there already (just less likely to happen), then I have nothing against considering it out of scope.

@axel7083 axel7083 force-pushed the refactor/quadlet-applications branch from 97fcbb6 to c026ffc Compare May 4, 2026 08:38
@packit-as-a-service

Copy link
Copy Markdown

Cockpit tests failed for commit c026ffc. @martinpitt, @jelly, @mvollmer please check.

@Luap99

Luap99 commented May 18, 2026

Copy link
Copy Markdown
Member

This makes sense, when removing a quadlet, there is no ambiguity (because of the .container extension). The myapp/ prefix is not even necessary as podman can find the quadlet.

It is technically possible to have the same file names in many search directories so I think having some way to say this application does make sense.

I am not sure how people plan to use a application to me there are one unit, added/removed as one.
If you allow removing a single file then you need to handle all the resulting special cases, how are the data files installed along with the app removed then? What happens if you removed all quadlet files, does it automatically remove data files and the directory or do we end up with an empty application dir without quadlets? The later is bad because that would no longer show up in podman quadlet ls.

I think the require --recursive and delete the whole app as one is simple and logical to document, adding special cases makes this more complicated than it needs to be I think.

@mheon

mheon commented May 18, 2026

Copy link
Copy Markdown
Contributor

I don't fully agree, but it is a reasonable starting point. We can always add the additional edge cases of removing individual quadlets out of an app, and similar things, in future versions.

@l0rd

l0rd commented May 19, 2026

Copy link
Copy Markdown
Contributor

I agree with @Luap99, removing an application's single quadlet introduces new problems, and we should avoid that.

But quadlet rm --recursive app/backend.volume is also a weird way to remove an app. So my proposal is to fail in such a case:

$ podman quadlet rm --recursive app/backend.volume
Error: quadlet "backend.volume" is part of the application "app" and can't be individually removed. Remove the application instead.

@simonbrauner

Copy link
Copy Markdown
Contributor

I am not sure how people plan to use a application to me there are one unit, added/removed as one.
If you allow removing a single file then you need to handle all the resulting special cases, how are the data files installed along with the app removed then? What happens if you removed all quadlet files, does it automatically remove data files and the directory or do we end up with an empty application dir without quadlets? The later is bad because that would no longer show up in podman quadlet ls.

It depends on what an application actually is.

If it's something atomic which does not make sense unless it's complete, then yes, allowing partial removal only introduces problems.

If it's just a group of quadlets then partial removal makes sense to me and it has clear semantics. If a user truly wants to remove a quadlet then I think they should be allowed to do so, and it's their responsibility that it may cause unused directory/broken dependency.

replacing --recursive with --application has a side effect: --recursive=true <app1> <app2> becomes --application=<app1> --application=<app2>.

I don't think this is that tedious to write. I think it would be common to remove more quadlets at once, but many applications at once? But yes, I didn't think about it and it surely is something to consider.

@l0rd

l0rd commented May 19, 2026

Copy link
Copy Markdown
Contributor

If it's just a group of quadlets then partial removal makes sense to me and it has clear semantics. If a user truly wants to remove a quadlet then I think they should be allowed to do so, and it's their responsibility that it may cause unused directory/broken dependency.

I have ideas on how people should use applications (see compose files and kubernetes deployments) and it's not a group of independent services. No, that's not what an application should be.

@simonbrauner

Copy link
Copy Markdown
Contributor

I have ideas on how people should use applications (see compose files and kubernetes deployments) and it's not a group of independent services. No, that's not what an application should be.

Then it's good that we are making it clearer now. I probably have this mindset from the current implementation because quadlet install directory made me think that it's just a group of quadlets which happens to be in the same directory, so they are coupled in some sense, but not necessarily tightly

@l0rd l0rd force-pushed the refactor/quadlet-applications branch 2 times, most recently from ddc0ecb to f680fee Compare May 26, 2026 16:43
@l0rd

l0rd commented May 26, 2026

Copy link
Copy Markdown
Contributor

During last Thursday's community call, we decided that:

  • --recursive is required when removing an application
  • the full application is deleted when removing a quadlet that is part of it (--recursive is required)

Code, docs and tests have been updated accordingly.

@mheon @Luap99 @simonbrauner @inknos @Honny1 PTAL

@simonbrauner simonbrauner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found inconsistency in install output, other than that, LGTM

installReport.QuadletErrors[toInstall] = err
continue
}
installReport.InstalledQuadlets[toInstall] = installedPath

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$ tree postgres-17-test
postgres-17-test
├── a
│   ├── E.container
│   └── README.md
├── A.container
├── B.container
└── README.md


$ podman quadlet install postgres-17-test --application 3 
/home/sbrauner/.config/containers/systemd/3/README.md
postgres-17-test/a
/home/sbrauner/.config/containers/systemd/3/A.container
/home/sbrauner/.config/containers/systemd/3/B.container

The output of install does not seem to be recursive, it prints a folder name, in a different format even. I would expect:

/home/sbrauner/.config/containers/systemd/3/README.md
/home/sbrauner/.config/containers/systemd/3/A.container
/home/sbrauner/.config/containers/systemd/3/B.container
/home/sbrauner/.config/containers/systemd/3/a/E.container
/home/sbrauner/.config/containers/systemd/3/a/README.md

I'd propose that the installlQuadlet function could return a list of paths and collect them recursively, so that the output lists all quadlets, not only those that are in the topmost directory, to make it consistent with rm.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review. This is indeed a bug. The output of the command doesn't include the nested files.

I had a look at the problem, and the fix isn't trivial. Considering that this is not critical, I would rather address it in a separate PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have opened a draft PR with the fix for this issue #28860

@l0rd l0rd force-pushed the refactor/quadlet-applications branch from f680fee to 9911b41 Compare June 2, 2026 09:29
Fixes: podman-container-tools#28118
Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>
@l0rd l0rd force-pushed the refactor/quadlet-applications branch from 9911b41 to 496646f Compare June 2, 2026 09:52

@mheon mheon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Let's get this in so we have it in RC1.

l0rd added a commit to l0rd/podman that referenced this pull request Jun 4, 2026
Cleanup the code to install quadlets and fix the output of the command
`podman quadlet install` when a folder is provided as argument.

See the comment:
podman-container-tools#28335 (comment)

Signed-off-by: Mario Loriedo <mario.loriedo@gmail.com>
l0rd added a commit to l0rd/podman that referenced this pull request Jun 5, 2026
Cleanup the code to install quadlets and fix the output of the command
`podman quadlet install` when a folder is provided as argument.

See the comment:
podman-container-tools#28335 (comment)

Signed-off-by: Mario Loriedo <mario.loriedo@gmail.com>

@Luap99 Luap99 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Luap99 Luap99 merged commit 486bacd into podman-container-tools:main Jun 5, 2026
117 of 120 checks passed
l0rd added a commit to l0rd/podman that referenced this pull request Jun 8, 2026
- Cleanup the code to install quadlets
- Fix `podman quadlet install` output message (see podman-container-tools#28335 (comment))
- Update libpod quadlet endpoint documentation

Signed-off-by: Mario Loriedo <mario.loriedo@gmail.com>
l0rd added a commit to l0rd/podman that referenced this pull request Jun 8, 2026
- Cleanup the code to install quadlets
- Fix `podman quadlet install` output message (see podman-container-tools#28335 (comment))
- Update libpod quadlet endpoint documentation

Signed-off-by: Mario Loriedo <mario.loriedo@gmail.com>
l0rd added a commit to l0rd/podman that referenced this pull request Jun 8, 2026
- Cleanup the code to install quadlets
- Fix `podman quadlet install` output message (see podman-container-tools#28335 (comment))
- Update libpod quadlet endpoint documentation

Signed-off-by: Mario Loriedo <mario.loriedo@gmail.com>
l0rd added a commit to l0rd/podman that referenced this pull request Jun 8, 2026
- Cleanup the code to install quadlets
- Fix `podman quadlet install` output message (see podman-container-tools#28335 (comment))
- Update libpod quadlet endpoint documentation

Signed-off-by: Mario Loriedo <mario.loriedo@gmail.com>
l0rd added a commit to l0rd/podman that referenced this pull request Jun 11, 2026
- Cleanup the code to install quadlets
- Fix `podman quadlet install` output message (see podman-container-tools#28335 (comment))
- Update libpod quadlet endpoint documentation

Signed-off-by: Mario Loriedo <mario.loriedo@gmail.com>
l0rd added a commit to l0rd/podman that referenced this pull request Jun 16, 2026
- Cleanup the code to install quadlets
- Fix `podman quadlet install` output message (see podman-container-tools#28335 (comment))
- Update libpod quadlet endpoint documentation

Signed-off-by: Mario Loriedo <mario.loriedo@gmail.com>
@gmipf

gmipf commented Jun 21, 2026

Copy link
Copy Markdown

Thanks for the directory-as-application refactor — dropping the marker files is a genuine simplification.

Adding a feature suggestion for consideration: the "first directory = application" rule doesn't cover grouping apps under a parent directory (by team, owner or tenant):

~/.config/containers/systemd/
├── team-a/
│   ├── web/   (web.container …)
│   └── db/    (db.container …)
└── team-b/
    └── api/   (api.container …)

Here the natural application is each leaf (web, db, api), but App resolves to team-a / team-b, so distinct apps collapse into one. Unit generation is unaffected — it's purely the application attribution.

Could an opt-in to set the application boundary (e.g. an install-time flag, or a small per-directory hint, rather than reintroducing .app files) be in scope? It would let grouped / multi-tenant layouts keep one application per leaf directory while preserving the marker-free default.

Generated with Claude Opus 4.8.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

6.0 Breaking changes for Podman 6.0 kind/api-change Change to remote API; merits scrutiny

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants