| Tested on | Red Hat Enterprise Linux 10.2 (Coughlan) |
|---|---|
| Package | podman-5.8.2-5.el10_2.x86_64criu-4.2 |
| Applies to | Linux hosts with rootful Podman and CRIU where you need to freeze and resume container process state or migrate execution to a compatible host |
| Privilege | Root only — CRIU checkpoint and restore is not supported rootless on Podman 5.8.2 |
| Scope | podman container checkpoint and podman container restore, default stop-after-checkpoint behavior, --leave-running, --export and --import with --name, --create-image, --tcp-established and --tcp-close on restore, --file-locks, --ignore-volumes, --ignore-static-ip, --ignore-static-mac, --print-stats, --keep, CRIU and host compatibility, volume and bind-mount limits, cross-host migration workflow, and checkpoint versus podman save. Does not cover ordinary stop or start, image save or load tutorials, VM live migration, or CRIU build from source. |
| Related guides | Podman remote hosts Podman storage location Podman architecture |
sudo podman. Rootless checkpoint attempts fail with Error: checkpointing a container requires root.
A normal podman stop ends the process. podman start launches a fresh process from the image entrypoint. Checkpointing is different: CRIU dumps process memory, open file descriptors, and namespace state so the workload can resume where it left off.
This guide proves that continuation with a counting loop, then covers export for host migration, the --create-image registry workflow, TCP options, and the failures you see when prerequisites do not match.
What is Podman checkpoint and restore?
Ordinary lifecycle:
stop → process exits
start → application starts from the beginningCheckpoint lifecycle:
running process
│
▼
CRIU dumps process state, memory, and namespaces
│
▼
restore
│
▼
process resumes from the checkpointed stateThe counter demo below is the proof: after restore, logs continue from the last number before checkpoint instead of resetting to 0.
Check CRIU prerequisites
Confirm CRIU is installed:
criu --versionSample output on the lab host:
Version: 4.2Verify the host environment CRIU expects:
sudo criu checkSample output:
Looks good.Read Podman host details:
sudo podman info --format 'Cgroups={{.Host.CgroupsVersion}} CgroupManager={{.Host.CgroupManager}} Kernel={{.Host.Kernel}} Arch={{.Host.Arch}}'Sample output:
Cgroups=v2 CgroupManager=systemd Kernel=6.12.0-211.47.1.el10_2.x86_64 Arch=amd64Rootless checkpoint is not supported on Podman 5.8.2 — every command below uses sudo podman. A rootless attempt fails with Error: checkpointing a container requires root.
Install CRIU from your distribution if criu --version fails. On RHEL, CRIU is available as the criu package. If it is missing, install it with DNF before using checkpoint and restore:
sudo dnf install criuCheckpoint and restore a running container
Start a container that prints an incrementing counter every second:
sudo podman run -d --name checkpoint-demo registry.access.redhat.com/ubi9/ubi-minimal:latest sh -c 'i=0; while true; do echo "$i"; i=$((i+1)); sleep 1; done'Watch the counter climb:
sudo podman logs --tail 5 checkpoint-demoSample output:
1
2
3
4
5Note the last number before you checkpoint — you will compare it after restore.
Write the checkpoint to disk:
sudo podman container checkpoint checkpoint-demoPodman prints the container ID on success. Default behavior stops the container after the dump:
sudo podman ps -a --filter name=checkpoint-demoSample output:
CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
e26a0136a22f registry.access.redhat.com/ubi9/ubi-minimal:latest sh -c i=0; while ... 10 seconds ago Exited (0) Less than a second ago checkpoint-demoExited (0) means the process was frozen and stopped cleanly — not a crash.
Resume from the on-disk checkpoint:
sudo podman container restore checkpoint-demoRead logs after a few seconds:
sudo podman logs --tail 10 checkpoint-demoSample output:
5
6
7
8
9
10
11
12
13
14The last line before checkpoint was 5. After restore counting resumes at 5 and continues — it does not restart at 0. That is the central proof that CRIU preserved process state.
Keep the container running with --leave-running
Write a checkpoint without stopping the original:
sudo podman container checkpoint --leave-running checkpoint-demoConfirm it stayed up:
sudo podman ps --filter name=checkpoint-demoSample output:
CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
e26a0136a22f registry.access.redhat.com/ubi9/ubi-minimal:latest sh -c i=0; while ... 24 seconds ago Up 24 seconds checkpoint-demoUse this for snapshot-style workflows or to prepare a migration archive with minimal interruption. A restored copy reflects state at checkpoint time — not changes the still-running original made afterward.
Export and import a checkpoint
Package checkpoint data for transfer. Podman 5.8.2 compresses exports with zstd by default, so use a .tar.zst filename that matches the format:
sudo podman container checkpoint --export /tmp/checkpoint-demo.tar.zst checkpoint-demoList the archive:
ls -lh /tmp/checkpoint-demo.tar.zstSample output:
-rw-------. 1 root root 82K Aug 23 09:48 /tmp/checkpoint-demo.tar.zstThe checkpoint archive contains CRIU process state plus Podman container and runtime metadata. Pass it to podman container restore --import — do not use podman load.
If you specifically want gzip compression, set the format explicitly:
sudo podman container checkpoint --compress=gzip --export /tmp/checkpoint-demo.tar.gz checkpoint-demoDefault export still stops the container unless you combine --export with --leave-running.
Remove the original if you want a clean import test:
sudo podman rm -f checkpoint-demoRestore under a new name:
sudo podman container restore --import /tmp/checkpoint-demo.tar.zst --name restored-demoRead logs:
sudo podman logs --tail 8 restored-demoSample output:
23
24
25
26
27
28The counter reflects state captured at export time — not a fresh start at 0. Use --name when importing because the original container name may already exist or you want a second copy.
Restore flags for duplicate network settings:
--ignore-static-ip— skip a fixed IP already in use on this host--ignore-static-mac— skip a MAC collision when restoring another copy
Create a checkpoint image with --create-image
Podman can package a checkpoint as a standard OCI image instead of a standalone archive. Push that image through a normal registry when you need to move state to another host. At this point in the walkthrough, restored-demo is the running container from the export/import section:
sudo podman container checkpoint --create-image checkpoint-demo-image restored-demoInspect the checkpoint image:
sudo podman image inspect checkpoint-demo-image --format '{{.Id}} {{.Created}}'Replace quay.io/your-user/checkpoint-demo with a repository you can push to, then tag and push the checkpoint image:
sudo podman tag checkpoint-demo-image quay.io/your-user/checkpoint-demo:latest
sudo podman push quay.io/your-user/checkpoint-demo:latestOn the destination host, pull the checkpoint image:
sudo podman pull quay.io/your-user/checkpoint-demo:latestRestore the container from the checkpoint image:
sudo podman container restore quay.io/your-user/checkpoint-demo:latestThe checkpoint image workflow suits registry-based migration without manually transferring a tarball. The same host compatibility requirements apply on the destination.
Migrate a checkpoint to another host
Same-host export and import validates the archive format. Full migration is supported when source and destination system configurations match. Red Hat documents migration as requiring aligned:
| Requirement | Why it matters |
|---|---|
| Podman version | Checkpoint metadata targets a specific Podman release |
| OCI runtime (crun/runc) and compatible version | Restore selects the runtime used at checkpoint time and aborts on mismatch |
| Network stack | Recorded interfaces and routes must be reproducible on the destination |
| cgroup version | Mixed v1 and v2 hosts often fail restore |
| Kernel version | CRIU feature bits and dump format depend on kernel capabilities |
| CPU features | Architecture and CPU capabilities must match |
| Image access on destination | Process state is not a substitute for image layers |
Use matching Podman versions for a supported migration workflow. Treat a successful local --import as proof the archive is well-formed — not as proof an unlike destination host will accept it.
Source host — export while the workload is quiesced or use --leave-running if brief downtime is unacceptable:
sudo podman container checkpoint --export /tmp/app-checkpoint.tar.zst appTransfer the archive:
scp /tmp/app-checkpoint.tar.zst destination:/tmp/Destination host — restore:
sudo podman container restore --import /tmp/app-checkpoint.tar.zst --name appBefore cutover, you can perform a test restore on the destination under a different name when the workload allows two copies to run safely (--name app-migrated-test) and compare logs or health checks.
The destination must have access to the original container image. If the image is already local, Podman can use it. If the image is available from a registry, Podman can retrieve it automatically when it is missing locally; otherwise transfer it with podman save and podman load. See Save, load, export and import images for image transfer options.
As an optional check before restore:
sudo podman image exists registry.access.redhat.com/ubi9/ubi-minimal:latest && echo "image present"Restore recreates the process from CRIU pages but still references the original image ID in container metadata.
Handle volumes and bind mounts
Exported checkpoints include the contents of associated Podman volumes by default. Use --ignore-volumes when you do not want volume content included. Syntax example:
sudo podman container restore --ignore-volumes --import /tmp/checkpoint.tar.zst --name NEW_CONTAINER--ignore-volumes skips volume data from the archive. External bind mounts and host-managed paths still need the same directories and files on the destination. Checkpointing process memory does not migrate arbitrary host infrastructure you mounted from outside Podman.
For bind mounts, recreate the host path on the destination before restore:
sudo mkdir -p /srv/app-dataRecreate the bind-mount source with the ownership, permissions, SELinux context, and contents required by the application.
If the source container used -v /srv/app-data:/data, the destination must expose the same path (or you adjust the container spec before checkpoint).
Handle TCP connections
Default checkpointing fails when the container has established TCP connections unless you enable TCP connection checkpointing. CRIU logs include:
Error (criu/sk-inet.c:200): inet: Connected TCP socket, consider using --tcp-established option.
CRIU checkpointing failed: -52: Invalid exchangePreserve established connections on checkpoint and restore:
sudo podman container checkpoint --tcp-established --export /tmp/app.tar.zst appRestore with the same TCP flag on import:
sudo podman container restore --tcp-established --import /tmp/app.tar.zstThe imported container retains its checkpointed name. When preserving established TCP connections, do not use --name — Podman does not allow --name together with --tcp-established because renaming an imported checkpoint changes networking behavior and IP assignment.
Cross-host TCP restore only works when addressing, routing, and NAT topology allow the connection to survive. Do not assume it works across arbitrary networks.
If a checkpoint contains TCP state but you want connections closed during restore so the application can reconnect, use --tcp-close when restoring:
sudo podman container restore --tcp-close --import /tmp/app.tar.zst --name app| Option | When to use |
|---|---|
--tcp-established on checkpoint and restore |
Freeze open connection state in the checkpoint |
--tcp-close on restore |
Close connections during restore; application accepts new clients |
Pick the option that matches whether your workload must keep existing sessions alive.
Preserve file locks
Applications that use POSIX file locks need matching flags on both sides. Syntax examples:
sudo podman container checkpoint --file-locks CONTAINERPass the same flag on restore:
sudo podman container restore --file-locks CONTAINEROmit the flag when the workload does not use file locking — it adds complexity to the dump.
Checkpoint statistics and troubleshooting
Print timing and CRIU metrics on checkpoint:
sudo podman container checkpoint --print-stats CONTAINERSample output (trimmed, from the counter demo on the lab host):
{
"podman_checkpoint_duration": 576265,
"container_statistics": [{
"runtime_checkpoint_duration": 239708,
"criu_statistics": {
"freezing_time": 102583,
"frozen_time": 80978,
"memdump_time": 6410,
"pages_scanned": 80,
"pages_written": 47
}
}]
}On restore:
sudo podman container restore --print-stats CONTAINERSample output (trimmed, from the counter demo on the lab host):
{
"podman_restore_duration": 506243,
"container_statistics": [{
"runtime_restore_duration": 227330,
"criu_statistics": {
"restore_time": 85812,
"pages_restored": 47
}
}]
}Durations are in microseconds. Use --print-stats when tuning checkpoint size or diagnosing slow dumps.
When restore fails, retain temporary CRIU artifacts:
sudo podman container restore --keep --import /tmp/checkpoint.tar.zst --name NEW_CONTAINER--keep applies to checkpoint as well. --keep retains CRIU logs and checkpoint files for debugging. When restoring a checkpoint stored with an existing container, it also prevents Podman from consuming that stored checkpoint so it can be restored again. An external archive supplied with --import remains on disk independently.
| Symptom | Likely cause | Direction |
|---|---|---|
checkpointing a container requires root |
Rootless session | Run as root or use non-CRIU patterns |
criu: command not found or very old CRIU |
Missing or outdated package | Install criu 4.x; run criu check |
Connected TCP socket in CRIU log |
Established TCP connection without --tcp-established |
Add TCP flag or close clients first |
| Runtime metadata mismatch on restore | Different Podman, OCI runtime, cgroup, or kernel | Align system configurations on both hosts |
| Static IP already in use | Restoring second copy on same bridge | --ignore-static-ip |
| MAC address collision | Duplicate --mac-address |
--ignore-static-mac |
| Bind mount path missing on destination | Host path not recreated | Create mount source or adjust spec |
podman load on checkpoint archive |
Wrong tool | Use podman container restore --import |
Checkpoint vs podman save
| Checkpoint | Image save | |
|---|---|---|
| Saves | Running process state and memory | Image layers and metadata |
| Requires CRIU | Yes | No |
| Resumes same process | Yes | No — starts new process from image |
| Rootful only on 5.8.2 | Yes | Rootless supported |
| Migration use | Execution state to another host | Image distribution |
| Typical command | podman container checkpoint --export |
podman save |
Confusing checkpoint archives with image tarballs is a common mistake. Checkpoint answers “resume this exact process”; image save answers “ship this filesystem template.”
References
- podman-container-checkpoint(1) — checkpoint flags, export, and
--create-image - podman-container-restore(1) — import,
--tcp-close, and ignore flags - CRIU documentation — checkpoint format and requirements
- Red Hat Enterprise Linux 9 — Creating and restoring container checkpoints — migration prerequisites
Summary
podman container checkpoint and podman container restore freeze and resume rootful container process state through CRIU — not through ordinary stop and start. The counter demo proves it: logs continue from the last number before checkpoint instead of resetting.
Default checkpoint stops the container; --leave-running keeps the original up while writing a snapshot. --export and --import move state between hosts when Podman version, OCI runtime, network stack, cgroup version, kernel version, and CPU features match on both sides. --create-image packages the checkpoint as an OCI image you can push through a registry.
Watch TCP sockets — default checkpoint fails on established connections unless you pass --tcp-established, and cross-host TCP restore needs controlled topology. Use --tcp-close on restore when clients can reconnect. Use --print-stats for timing data and --keep when CRIU restore fails or you need to reuse a checkpoint. For image transfer without execution state, use podman save instead.

