Repository: containers/podman
Pull request #25675: Add support for Retry= and RetryDelay= to Podman Quadlet.
Author: login: jankaluza
Labels: name: lgtm
description: Indicates that a PR is ready to be merged.
name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
name: release-note
name: locked - please file new issue/PR
description: Assist humans wanting to comment on an old issue or PR with locked comments.
Created: 2025-03-25T11:54:50Z | Merged: 2025-03-26T11:19:03Z

This commit adds new Retry= and RetryDelay= options to quadlet.go which result in --retry and --retry-delay usage in podman run command.

This allows configuring the retry logic in .container systemd files.

Fixes: #25109

#### Does this PR introduce a user-facing change?

```release-note
The Retry= and RetryDelay= options are now supported in the systemd .container files.
```



Diff:
diff --git a/docs/source/markdown/podman-systemd.unit.5.md b/docs/source/markdown/podman-systemd.unit.5.md
index 57329845a32..b5dfd8970e9 100644
--- a/docs/source/markdown/podman-systemd.unit.5.md
+++ b/docs/source/markdown/podman-systemd.unit.5.md
@@ -332,6 +332,8 @@ Valid options for `[Container]` are listed below:
 | Pull=never                           | --pull never                                         |
 | ReadOnly=true                        | --read-only                                          |
 | ReadOnlyTmpfs=true                   | --read-only-tmpfs                                    |
+| Retry=5                              | --retry=5                                            |
+| RetryDelay=5s                        | --retry-delay=5s                                     |
 | Rootfs=/var/lib/rootfs               | --rootfs /var/lib/rootfs                             |
 | RunInit=true                         | --init                                               |
 | SeccompProfile=/tmp/s.json           | --security-opt seccomp=/tmp/s.json                   |
@@ -781,6 +783,14 @@ If enabled, makes the image read-only.
 
 If ReadOnly is set to `true`, mount a read-write tmpfs on /dev, /dev/shm, /run, /tmp, and /var/tmp.
 
+### `Retry=`
+
+Number of times to retry the image pull when a HTTP error occurs. Equivalent to the Podman `--retry` option.
+
+### `RetryDelay=`
+
+Delay between retries. Equivalent to the Podman `--retry-delay` option.
+
 ### `Rootfs=`
 
 The rootfs to use for the container. Rootfs points to a directory on the system that contains the content to be run within the container. This option conflicts with the `Image` option.
@@ -1611,6 +1621,8 @@ Valid options for `[Build]` are listed below:
 | Network=host                        | --network=host                              |
 | PodmanArgs=--pull never             | --pull never                                |
 | Pull=never                          | --pull never                                |
+| Retry=5                             | --retry=5                                   |
+| RetryDelay=10s                      | --retry-delay=10s                           |
 | Secret=secret                       | --secret=id=mysecret,src=path               |
 | SetWorkingDirectory=unit            | Set `WorkingDirectory` of systemd unit file |
 | Target=my-app                       | --target=my-app                             |
@@ -1757,6 +1769,14 @@ Set the image pull policy.
 
 This is equivalent to the `--pull` option of `podman build`.
 
+### `Retry=`
+
+Number of times to retry the image pull when a HTTP error occurs. Equivalent to the Podman `--retry` option.
+
+### `RetryDelay=`
+
+Delay between retries. Equivalent to the Podman `--retry-delay` option.
+
 ### `Secret=`
 
 Pass secret information used in Containerfile build stages in a safe way.
@@ -1843,6 +1863,8 @@ Valid options for `[Image]` are listed below:
 | ImageTag=quay\.io/centos/centos:latest | Use this name when resolving `.image` references |
 | OS=windows                             | --os=windows                                     |
 | PodmanArgs=--os=linux                  | --os=linux                                       |
+| Retry=5                                | --retry=5                                        |
+| RetryDelay=10s                         | --retry-delay=10s                                |
 | TLSVerify=false                        | --tls-verify=false                               |
 | Variant=arm/v7                         | --variant=arm/v7                                 |
 
@@ -1941,6 +1963,14 @@ escaped to allow inclusion of whitespace and other control characters.
 
 This key can be listed multiple times.
 
+### `Retry=`
+
+Number of times to retry the image pull when a HTTP error occurs. Equivalent to the Podman `--retry` option.
+
+### `RetryDelay=`
+
+Delay between retries. Equivalent to the Podman `--retry-delay` option.
+
 ### `TLSVerify=`
 
 Require HTTPS and verification of certificates when contacting registries.
diff --git a/pkg/systemd/quadlet/quadlet.go b/pkg/systemd/quadlet/quadlet.go
index 3a51b22aba2..e08550f4a8d 100644
--- a/pkg/systemd/quadlet/quadlet.go
+++ b/pkg/systemd/quadlet/quadlet.go
@@ -143,6 +143,8 @@ const (
 	KeyRemapUid              = "RemapUid"     //nolint:stylecheck // deprecated
 	KeyRemapUidSize          = "RemapUidSize" //nolint:stylecheck // deprecated
 	KeyRemapUsers            = "RemapUsers"   // deprecated
+	KeyRetry                 = "Retry"
+	KeyRetryDelay            = "RetryDelay"
 	KeyRootfs                = "Rootfs"
 	KeyRunInit               = "RunInit"
 	KeySeccompProfile        = "SeccompProfile"
@@ -258,6 +260,8 @@ var (
 		KeyRemapUid:              true,
 		KeyRemapUidSize:          true,
 		KeyRemapUsers:            true,
+		KeyRetry:                 true,
+		KeyRetryDelay:            true,
 		KeyRootfs:                true,
 		KeyRunInit:               true,
 		KeySeccompProfile:        true,
@@ -362,6 +366,8 @@ var (
 		KeyImageTag:             true,
 		KeyOS:                   true,
 		KeyPodmanArgs:           true,
+		KeyRetry:                true,
+		KeyRetryDelay:           true,
 		KeyServiceName:          true,
 		KeyTLSVerify:            true,
 		KeyVariant:              true,
@@ -386,6 +392,8 @@ var (
 		KeyNetwork:              true,
 		KeyPodmanArgs:           true,
 		KeyPull:                 true,
+		KeyRetry:                true,
+		KeyRetryDelay:           true,
 		KeySecret:               true,
 		KeyServiceName:          true,
 		KeySetWorkingDirectory:  true,
@@ -641,6 +649,8 @@ func ConvertContainer(container *parser.UnitFile, isUser bool, unitsInfoMap map[
 		KeyStopTimeout: "--stop-timeout",
 		KeyPull:        "--pull",
 		KeyMemory:      "--memory",
+		KeyRetry:       "--retry",
+		KeyRetryDelay:  "--retry-delay",
 	}
 	lookupAndAddString(container, ContainerGroup, stringKeys, podman)
 
@@ -1365,6 +1375,8 @@ func ConvertImage(image *parser.UnitFile, unitsInfoMap map[string]*UnitInfo, isU
 		KeyDecryptionKey: "--decryption-key",
 		KeyOS:            "--os",
 		KeyVariant:       "--variant",
+		KeyRetry:         "--retry",
+		KeyRetryDelay:    "--retry-delay",
 	}
 	lookupAndAddString(image, ImageGroup, stringKeys, podman)
 
@@ -1437,10 +1449,12 @@ func ConvertBuild(build *parser.UnitFile, unitsInfoMap map[string]*UnitInfo, isU
 	}
 
 	stringKeys := map[string]string{
-		KeyArch:     "--arch",
-		KeyAuthFile: "--authfile",
-		KeyTarget:   "--target",
-		KeyVariant:  "--variant",
+		KeyArch:       "--arch",
+		KeyAuthFile:   "--authfile",
+		KeyTarget:     "--target",
+		KeyVariant:    "--variant",
+		KeyRetry:      "--retry",
+		KeyRetryDelay: "--retry-delay",
 	}
 	lookupAndAddString(build, BuildGroup, stringKeys, podman)
 
diff --git a/test/e2e/quadlet/retry.build b/test/e2e/quadlet/retry.build
new file mode 100644
index 00000000000..958fa270267
--- /dev/null
+++ b/test/e2e/quadlet/retry.build
@@ -0,0 +1,8 @@
+## assert-podman-args "--retry" "5"
+## assert-podman-args "--retry-delay" "10s"
+
+[Build]
+ImageTag=localhost/imagename
+SetWorkingDirectory=unit
+Retry=5
+RetryDelay=10s
diff --git a/test/e2e/quadlet/retry.container b/test/e2e/quadlet/retry.container
new file mode 100644
index 00000000000..3b90f096c9e
--- /dev/null
+++ b/test/e2e/quadlet/retry.container
@@ -0,0 +1,7 @@
+## assert-podman-args "--retry" "5"
+## assert-podman-args "--retry-delay" "10s"
+
+[Container]
+Image=localhost/imagename
+Retry=5
+RetryDelay=10s
diff --git a/test/e2e/quadlet/retry.image b/test/e2e/quadlet/retry.image
new file mode 100644
index 00000000000..87068cc1cdf
--- /dev/null
+++ b/test/e2e/quadlet/retry.image
@@ -0,0 +1,7 @@
+## assert-podman-args "--retry" "5"
+## assert-podman-args "--retry-delay" "10s"
+
+[Image]
+Image=localhost/imagename
+Retry=5
+RetryDelay=10s
diff --git a/test/e2e/quadlet_test.go b/test/e2e/quadlet_test.go
index c7a541a1430..b716441c6c2 100644
--- a/test/e2e/quadlet_test.go
+++ b/test/e2e/quadlet_test.go
@@ -925,6 +925,7 @@ BOGUS=foo
 		Entry("NetworkAlias", "network-alias.container"),
 		Entry("CgroupMode", "cgroups-mode.container"),
 		Entry("Container - No Default Dependencies", "no_deps.container"),
+		Entry("retry.container", "retry.container"),
 
 		Entry("basic.volume", "basic.volume"),
 		Entry("device-copy.volume", "device-copy.volume"),
@@ -994,6 +995,7 @@ BOGUS=foo
 		Entry("Image - Containers Conf Modules", "containersconfmodule.image"),
 		Entry("Image - Unit After Override", "unit-after-override.image"),
 		Entry("Image - No Default Dependencies", "no_deps.image"),
+		Entry("Image - Retry", "retry.image"),
 
 		Entry("Build - Basic", "basic.build"),
 		Entry("Build - Annotation Key", "annotation.build"),
@@ -1028,6 +1030,7 @@ BOGUS=foo
 		Entry("Build - TLSVerify Key", "tls-verify.build"),
 		Entry("Build - Variant Key", "variant.build"),
 		Entry("Build - No Default Dependencies", "no_deps.build"),
+		Entry("Build - Retry", "retry.build"),
 
 		Entry("Pod - Basic", "basic.pod"),
 		Entry("Pod - DNS", "dns.pod"),


Reviews:
user: login: Luap99
body: image and build units also should accept these options for consistency.

```
$ podman build --help | grep retry
      --retry int                                    number of times to retry in case of failure when performing push/pull (default 3)
      --retry-delay string                           delay between retries in case of push/pull failures
$ podman pull --help | grep retry
      --retry uint                   number of times to retry in case of failure when performing pull (default 3)
      --retry-delay string           delay between retries in case of pull failures

```
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-25T13:19:36Z
user: login: ygalblum
body: Small doc nit
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T13:36:44Z
user: login: ygalblum
body: Code LGTM, just missing the last doc part
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T19:16:27Z
user: login: Luap99
body: /lgtm
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-26T11:16:30Z

Review comments:
diff_hunk: @@ -616,6 +620,13 @@ func ConvertContainer(container *parser.UnitFile, isUser bool, unitsInfoMap map[
 		"--rm",
 	)
 
+	if retry, ok := container.Lookup(ContainerGroup, KeyRetry); ok {
+		podman.add("--retry", retry)
+	}
+	if retryDelay, ok := container.Lookup(ContainerGroup, KeyRetryDelay); ok {
+		podman.add("--retry-delay", retryDelay)
+	}
path: pkg/systemd/quadlet/quadlet.go
user: login: Luap99
body: This should be added to the stringKeys map below to avoid the boiler plate.
created_at: 2025-03-25T13:16:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,26 @@
+## assert-podman-final-args localhost/imagename
+## assert-podman-args "--name" "systemd-%N"
+## assert-podman-args "--cidfile=%t/%N.cid"
+## assert-podman-args "--rm"
+## assert-podman-args "--replace"
+## assert-podman-args "--retry" "5"
+## assert-podman-args "--retry-delay" "10s"
+## assert-podman-args "-d"
+## assert-podman-args "--cgroups=split"
+## assert-podman-args "--sdnotify=conmon"
+## assert-key-is "Unit" "RequiresMountsFor" "%t/containers"
+## assert-key-is "Service" "KillMode" "mixed"
+## assert-key-is "Service" "Delegate" "yes"
+## assert-key-is "Service" "Type" "notify"
+## assert-key-is "Service" "NotifyAccess" "all"
+## assert-key-is "Service" "SyslogIdentifier" "%N"
+## assert-key-is-regex "Service" "ExecStopPost" "-[/S].*/podman rm -v -f -i --cidfile=%t/%N.cid"
+## assert-key-is-regex "Service" "ExecStop" ".*/podman rm -v -f -i --cidfile=%t/%N.cid"
+## assert-key-is "Service" "Environment" "PODMAN_SYSTEMD_UNIT=%n"
+## assert-key-is-regex "Unit" "After" "network-online.target|podman-user-wait-network-online.service"
+## assert-key-is-regex "Unit" "Wants" "network-online.target|podman-user-wait-network-online.service"
path: test/e2e/quadlet/retry.container
user: login: Luap99
body: we properly don't have to test all of that again here, just checking the --retry* args seems good enough to me.
created_at: 2025-03-25T13:17:47Z
author_association: MEMBER
diff_hunk: @@ -781,6 +783,14 @@ If enabled, makes the image read-only.
 
 If ReadOnly is set to `true`, mount a read-write tmpfs on /dev, /dev/shm, /run, /tmp, and /var/tmp.
 
+### `Retry=`
+
+Number of times to retry the image pull when a HTTP error occurs. Passed as `--retry` to podman.
path: docs/source/markdown/podman-systemd.unit.5.md
user: login: ygalblum
body: Just to align the wording, instead of "Passed as `XXX`" please use "Equivalent to the Podman `XXX` option"
created_at: 2025-03-25T13:35:28Z
author_association: CONTRIBUTOR

PR comments:
user: login: jankaluza
created_at: 2025-03-25T13:56:36Z
body: > image and build units also should accept these options for consistency.
> 
> ```
> $ podman build --help | grep retry
>       --retry int                                    number of times to retry in case of failure when performing push/pull (default 3)
>       --retry-delay string                           delay between retries in case of push/pull failures
> $ podman pull --help | grep retry
>       --retry uint                   number of times to retry in case of failure when performing pull (default 3)
>       --retry-delay string           delay between retries in case of pull failures
> ```

Thanks, that's a good point. I've added them in the latest update :-).
author_association: MEMBER
user: login: jankaluza
created_at: 2025-03-25T14:02:21Z
body: > > image and build units also should accept these options for consistency.
> > ```
> > $ podman build --help | grep retry
> >       --retry int                                    number of times to retry in case of failure when performing push/pull (default 3)
> >       --retry-delay string                           delay between retries in case of push/pull failures
> > $ podman pull --help | grep retry
> >       --retry uint                   number of times to retry in case of failure when performing pull (default 3)
> >       --retry-delay string           delay between retries in case of pull failures
> > ```
> 
> Thanks, that's a good point. I've added them in the latest update :-).

But checking that PR once more, I did not add documentation for them, will add it :-).
author_association: MEMBER
user: login: jankaluza
created_at: 2025-03-26T08:13:24Z
body: Added the documentation
author_association: MEMBER
user: login: openshift-ci[bot]
created_at: 2025-03-26T11:16:35Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/containers/podman/pull/25675#" title="Author self-approved">jankaluza</a>*, *<a href="https://github.com/containers/podman/pull/25675#pullrequestreview-2716777618" title="LGTM">Luap99</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=containers%2Fpodman).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/containers/podman/blob/main/OWNERS)~~ [Luap99]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR