compat API: POST /containers/{id}/update resets RestartPolicy to `no` when the body omits it
Issue Description
The Docker-compat POST /containers/{id}/update endpoint resets a container's restart policy to no whenever the request body does not carry RestartPolicy. Docker Engine's endpoint keeps the stored policy when the field is omitted and only changes it when RestartPolicy.Name is set. A resources-only update body, which is what docker update --memory ...-style clients send, therefore silently turns unless-stopped / always into no: the container no longer restarts after a crash and does not come back at boot.
Cause: UpdateContainer in pkg/api/handlers/compat/containers.go always hands ctr.Update a restart-policy pointer, even when the body had none:
// Restart policy
localPolicy := string(options.RestartPolicy.Name)
restartPolicy := &localPolicyWith the field omitted, options.RestartPolicy.Name is "", define.ValidateRestartPolicy("") accepts it (empty means none), and libpod's update stores it, so inspect then reports no. The same lines are present in v5.4.2, v5.6.0, v6.1.0 and current main (main around lines 878–880).
Suggested fix: pass restartPolicy (and restartRetries) only when options.RestartPolicy.Name != "", so an omitted field leaves the stored policy alone as Docker does.
Steps to reproduce the issue
$ SOCK=$XDG_RUNTIME_DIR/podman/podman.sock
$ curl -s --unix-socket $SOCK -X POST "http://d/v1.41/containers/create?name=t" \
-H 'Content-Type: application/json' \
-d '{"Image":"docker.io/library/alpine:3.19","Cmd":["sleep","3600"],"HostConfig":{"RestartPolicy":{"Name":"unless-stopped"}}}'
$ curl -s --unix-socket $SOCK -X POST "http://d/v1.41/containers/t/start"
$ podman inspect t --format '{{.HostConfig.RestartPolicy.Name}}'
unless-stopped
$ curl -s --unix-socket $SOCK -X POST "http://d/v1.41/containers/t/update" \
-H 'Content-Type: application/json' -d '{"Memory":67108864}'
{"Warnings":null}
$ podman inspect t --format '{{.HostConfig.RestartPolicy.Name}}'
noSending {"Memory":67108864,"RestartPolicy":{"Name":"unless-stopped"}} instead keeps (or sets) the policy, so clients can work around it by always naming the policy in every update body.
Describe the results you received
After an update body carrying only Memory, HostConfig.RestartPolicy.Name changes from unless-stopped to no. The memory limit itself is applied.
Describe the results you expected
The restart policy is unchanged when the update body omits RestartPolicy. That is what Docker Engine does: the same two requests against Docker Engine 29.8.0 leave the policy at unless-stopped, and an explicit RestartPolicy in the body is applied on both engines.
podman info output
podman infohost:
arch: amd64
buildahVersion: 1.39.3
cgroupControllers:
- cpuset
- cpu
- io
- memory
- pids
cgroupManager: systemd
cgroupVersion: v2
conmon:
package: conmon_2.1.12-4_amd64
path: /usr/bin/conmon
version: 'conmon version 2.1.12, commit: unknown'
cpuUtilization:
idlePercent: 91.05
systemPercent: 1.61
userPercent: 7.34
cpus: 4
databaseBackend: sqlite
distribution:
codename: trixie
distribution: debian
version: "13"
eventLogger: journald
freeLocks: 2028
hostname: vmsignalk
idMappings:
gidmap:
- container_id: 0
host_id: 1000
size: 1
- container_id: 1
host_id: 100000
size: 65536
uidmap:
- container_id: 0
host_id: 1000
size: 1
- container_id: 1
host_id: 100000
size: 65536
kernel: 6.12.107+deb13-amd64
linkmode: dynamic
logDriver: journald
memFree: 1002446848
memTotal: 16800845824
networkBackend: netavark
networkBackendInfo:
backend: netavark
dns:
package: aardvark-dns_1.14.0-3_amd64
path: /usr/lib/podman/aardvark-dns
version: aardvark-dns 1.14.0
package: netavark_1.14.0-2_amd64
path: /usr/lib/podman/netavark
version: netavark 1.14.0
ociRuntime:
name: crun
package: crun_1.21-1_amd64
path: /usr/bin/crun
version: |-
crun version 1.21
commit: 10269840aa07fb7e6b7e1acff6198692d8ff5c88
rundir: /run/user/1000/crun
spec: 1.0.0
+SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +CRIU +WASM:wasmedge +YAJL
os: linux
pasta:
executable: /usr/bin/pasta
package: passt_0.0~git20250503.587980c-2+deb13u1_amd64
version: ""
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: false
serviceIsRemote: false
slirp4netns:
executable: /usr/bin/slirp4netns
package: slirp4netns_1.2.1-1.1_amd64
version: |-
slirp4netns version 1.2.1
commit: 09e31e92fa3d2a1d3ca261adaeb012c8d75a8194
libslirp: 4.8.0
SLIRP_CONFIG_VERSION_MAX: 5
libseccomp: 2.6.0
swapFree: 6722920448
swapTotal: 8588881920
uptime: 71h 32m 34.00s (Approximately 2.96 days)
variant: ""
plugins:
authorization: null
log:
- k8s-file
- none
- passthrough
- journald
network:
- bridge
- macvlan
- ipvlan
volume:
- local
registries: {}
store:
configFile: /home/dirk/.config/containers/storage.conf
containerStore:
number: 13
paused: 0
running: 12
stopped: 1
graphDriverName: overlay
graphOptions: {}
graphRoot: /home/dirk/.local/share/containers/storage
graphRootAllocated: 307423244288
graphRootUsed: 212082479104
graphStatus:
Backing Filesystem: extfs
Native Overlay Diff: "true"
Supports d_type: "true"
Supports shifting: "false"
Supports volatile: "true"
Using metacopy: "false"
imageCopyTmpDir: /var/tmp
imageStore:
number: 29
runRoot: /run/user/1000/containers
transientStore: false
volumePath: /home/dirk/.local/share/containers/storage/volumes
version:
APIVersion: 5.4.2
BuildOrigin: Debian
Built: 1766335321
BuiltTime: Mon Dec 22 04:42:01 2025
GitCommit: ""
GoVersion: go1.24.4
Os: linux
OsArch: linux/amd64
Version: 5.4.2
Podman in a container
No
Privileged Or Rootless
Rootless
Upstream Latest Release
No — measured on 5.4.2 (Debian 13 package). The handler code is identical in v6.1.0 and on main, so I expect the same result there, but the reproducer above was only run on 5.4.2.
Additional environment details
Debian 13 (trixie), x86_64, rootless, cgroup v2, crun. In normal operation the endpoint is reached through dockerode (a Node Docker API client); the curl sequence above is the minimal form.
Additional information
Found via signalk-container, a Signal K plugin that manages containers through the compat API. A periodic resource update that omitted the field left every managed container with RestartPolicy: no (reported and root-caused by humppafreak in dirkwa/signalk-container#289; worked around in dirkwa/signalk-container#290 by always sending the policy).
Source: podman-container-tools/podman