Skip to content

Quadlet Remove: Removing a single .app file causes multiple removal attempts of unit and app files #27653

Description

@kavishgr

Issue Description

Quadlet related files within the .app file are attempted to be removed multiple times, not just once.

Steps to reproduce the issue

1 - Install a quadlet:

> ./bin/podman quadlet install ../homelab/quadlets/nginx-test/
/home/kavish/.config/containers/systemd/nginx-test-2.container
/home/kavish/.config/containers/systemd/nginx-test.container

Content of quadlet files:

> cat /home/kavish/.config/containers/systemd/nginx-test.container
[Container]
Image=nginx:mainline-alpine
ContainerName=nginx-test
PublishPort=8282:80

[Service]
Restart=always

[Install]
WantedBy=default.target

> cat /home/kavish/.config/containers/systemd/nginx-test-2.container
[Container]
Image=nginx:mainline-alpine
ContainerName=nginx-test-2
PublishPort=8383:80

[Service]
Restart=always

[Install]
WantedBy=default.target

3 - I added print statements at key locations in the QuadletRemove function to debug the process. When running podman quadlet rm .nginx-test.app, the output showed that the same quadlet and .app files were being scheduled for removal multiple times. This confirmed that the repeated removal attempts were happening internally, even though the user does not see these duplicates in the normal output. But it does lead to redundant operations:

> ./bin/podman quadlet rm .nginx-test.app
Entering QuadletRemove
QUADLETS ARGUMENTS: []string{".nginx-test.app"}
Found .app file: .nginx-test.app; contains files: [nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container]; found in map: true
Processing .app file: .nginx-test.app
Files included in .app: [nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container nginx-test-2.container nginx-test.container]
quadlets after All option: []string{"nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container", "nginx-test-2.container", "nginx-test.container"}
allQuadletPaths has 36 files: [/home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container /home/kavish/.config/containers/systemd/nginx-test-2.container /home/kavish/.config/containers/systemd/nginx-test.container]
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
before range removeList files: [.nginx-test.app]
inside range removelist: Removing .app file (or asset): .nginx-test.app
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container
nginx-test-2.container
nginx-test.container

4 - To fix the issue, I introduced a map[string]struct{} to keep track of which files had already been scheduled for removal. Each time we process a quadlet or app file, we check if it's present in the map before adding it to the removal list. This way, every file is only deleted once, preventing duplicate attempts and making the removal logic much cleaner, without cluttering up the logs(if configured).

I have a fix ready and will open a PR soon. I’ll update this issue once the PR is up.

Describe the results you received

The removal logic for .app expansion does not deduplicate quadlet and related files before removal, causing repeated attempts on the same files. The repeated removal attempts are not visible to the user; they occur internally during the removal process. The removal operations do not cause user facing errors, but they result in unnecessary deletion logic during quadlet removal.

Describe the results you expected

Each quadlet unit and related files grouped under the .app file should be deleted once, not multiple times.

podman info output

host:
  arch: arm64
  buildahVersion: 1.42.0
  cgroupControllers:
  - cpu
  - memory
  - pids
  cgroupManager: cgroupfs
  cgroupVersion: v2
  conmon:
    package: conmon-2.1.13-2.fc43.aarch64
    path: /usr/bin/conmon
    version: 'conmon version 2.1.13, commit: '
  cpuUtilization:
    idlePercent: 95.86
    systemPercent: 0.92
    userPercent: 3.22
  cpus: 2
  databaseBackend: sqlite
  distribution:
    distribution: fedora
    variant: server
    version: "43"
  emulatedArchitectures:
  - linux/386
  - linux/amd64
  - linux/arm64be
  - linux/loong64
  - linux/mips
  - linux/mips64
  - linux/ppc
  - linux/ppc64
  - linux/ppc64le
  - linux/riscv32
  - linux/riscv64
  - linux/s390x
  eventLogger: file
  freeLocks: 2047
  hostname: homelab-testing
  idMappings:
    gidmap:
    - container_id: 0
      host_id: 1000
      size: 1
    - container_id: 1
      host_id: 524288
      size: 65536
    uidmap:
    - container_id: 0
      host_id: 1000
      size: 1
    - container_id: 1
      host_id: 524288
      size: 65536
  kernel: 6.17.8-300.fc43.aarch64
  linkmode: dynamic
  logDriver: journald
  memFree: 564088832
  memTotal: 2036322304
  networkBackend: netavark
  networkBackendInfo:
    backend: netavark
    default_network: podman
    dns:
      package: aardvark-dns-1.17.0-1.fc43.aarch64
      path: /usr/libexec/podman/aardvark-dns
      version: aardvark-dns 1.17.0
    package: netavark-1.17.0-1.fc43.aarch64
    path: /usr/libexec/podman/netavark
    version: netavark 1.17.0
  ociRuntime:
    name: crun
    package: crun-1.25-1.fc43.aarch64
    path: /usr/bin/crun
    version: |-
      crun version 1.25
      commit: d9a0adce065c7747ab88ea6ccc42b15a626e08e1
      rundir: /run/user/1000/crun
      spec: 1.0.0
      +SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +CRIU +LIBKRUN +WASM:wasmedge +YAJL
  os: linux
  pasta:
    executable: /usr/bin/pasta
    package: passt-0^20250919.g623dbf6-1.fc43.aarch64
    version: |
      pasta 0^20250919.g623dbf6-1.fc43.aarch64-pasta
      Copyright Red Hat
      GNU General Public License, version 2 or later
        <https://www.gnu.org/licenses/old-licenses/gpl-2.0.html>
      This is free software: you are free to change and redistribute it.
      There is NO WARRANTY, to the extent permitted by law.
  remoteSocket:
    exists: true
    path: /run/user/1000/podman/podman.sock
  rootlessNetworkCmd: pasta
  security:
    apparmorEnabled: false
    capabilities: CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNER,CAP_FSETID,CAP_KILL,CAP_NET_BIND_SERVICE,CAP_SETFCAP,CAP_SETGID,CAP_SETPCAP,CAP_SETUID,CAP_SYS_CHROOT
    rootless: true
    seccompEnabled: true
    seccompProfilePath: /usr/share/containers/seccomp.json
    selinuxEnabled: true
  serviceIsRemote: false
  slirp4netns:
    executable: ""
    package: ""
    version: ""
  swapFree: 1963204608
  swapTotal: 2035281920
  uptime: 2h 5m 57.00s (Approximately 0.08 days)
  variant: v8
plugins:
  authorization: null
  log:
  - k8s-file
  - none
  - passthrough
  network:
  - bridge
  - macvlan
  - ipvlan
  volume:
  - local
registries:
  search:
  - registry.fedoraproject.org
  - registry.access.redhat.com
  - docker.io
store:
  configFile: /home/kavish/.config/containers/storage.conf
  containerStore:
    number: 1
    paused: 0
    running: 0
    stopped: 1
  graphDriverName: overlay
  graphOptions: {}
  graphRoot: /home/kavish/.local/share/containers/storage
  graphRootAllocated: 16039018496
  graphRootUsed: 8429662208
  graphStatus:
    Backing Filesystem: xfs
    Native Overlay Diff: "true"
    Supports d_type: "true"
    Supports shifting: "false"
    Supports volatile: "true"
    Using metacopy: "false"
  imageCopyTmpDir: /var/tmp
  imageStore:
    number: 2
  runRoot: /run/user/1000/containers
  transientStore: false
  volumePath: /home/kavish/.local/share/containers/storage/volumes
version:
  APIVersion: 6.0.0-dev
  Built: 1764654652
  BuiltTime: Tue Dec  2 09:50:52 2025
  GitCommit: 3681055601c5c2a882fc6773a2fc72ef6eb1dadf-dirty
  GoVersion: go1.25.4 X:nodwarf5
  Os: linux
  OsArch: linux/arm64
  Version: 6.0.0-dev

Podman in a container

No

Privileged Or Rootless

Rootless

Upstream Latest Release

Yes

Additional environment details

Additional environment details

Additional information

Additional information like issue happens only occasionally or issue happens with a particular architecture or on a particular setting

Metadata

Metadata

Assignees

Labels

kind/bugCategorizes issue or PR as related to a bug.quadletstale-issuetriagedIssue has been triaged

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions