Checkpoint and Restore Podman Containers with CRIU

Tested on Red Hat Enterprise Linux 10.2 (Coughlan)
Package podman-5.8.2-5.el10_2.x86_64
criu-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
IMPORTANT
Podman container checkpoint and restore requires root on Podman 5.8.2. Every command in this guide uses 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:

text
stop  → process exits
start → application starts from the beginning

Checkpoint lifecycle:

text
running process
CRIU dumps process state, memory, and namespaces
restore
process resumes from the checkpointed state

The 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:

bash
criu --version

Sample output on the lab host:

output
Version: 4.2

Verify the host environment CRIU expects:

bash
sudo criu check

Sample output:

output
Looks good.

Read Podman host details:

bash
sudo podman info --format 'Cgroups={{.Host.CgroupsVersion}} CgroupManager={{.Host.CgroupManager}} Kernel={{.Host.Kernel}} Arch={{.Host.Arch}}'

Sample output:

output
Cgroups=v2 CgroupManager=systemd Kernel=6.12.0-211.47.1.el10_2.x86_64 Arch=amd64

Rootless 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:

bash
sudo dnf install criu

Checkpoint and restore a running container

Start a container that prints an incrementing counter every second:

bash
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:

bash
sudo podman logs --tail 5 checkpoint-demo

Sample output:

output
1
2
3
4
5

Note the last number before you checkpoint — you will compare it after restore.

Write the checkpoint to disk:

bash
sudo podman container checkpoint checkpoint-demo

Podman prints the container ID on success. Default behavior stops the container after the dump:

bash
sudo podman ps -a --filter name=checkpoint-demo

Sample output:

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-demo

Exited (0) means the process was frozen and stopped cleanly — not a crash.

Resume from the on-disk checkpoint:

bash
sudo podman container restore checkpoint-demo

Read logs after a few seconds:

bash
sudo podman logs --tail 10 checkpoint-demo

Sample output:

output
5
6
7
8
9
10
11
12
13
14

The 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:

bash
sudo podman container checkpoint --leave-running checkpoint-demo

Confirm it stayed up:

bash
sudo podman ps --filter name=checkpoint-demo

Sample output:

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-demo

Use 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:

bash
sudo podman container checkpoint --export /tmp/checkpoint-demo.tar.zst checkpoint-demo

List the archive:

bash
ls -lh /tmp/checkpoint-demo.tar.zst

Sample output:

output
-rw-------. 1 root root 82K Aug 23 09:48 /tmp/checkpoint-demo.tar.zst

The 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:

bash
sudo podman container checkpoint --compress=gzip --export /tmp/checkpoint-demo.tar.gz checkpoint-demo

Default export still stops the container unless you combine --export with --leave-running.

Remove the original if you want a clean import test:

bash
sudo podman rm -f checkpoint-demo

Restore under a new name:

bash
sudo podman container restore --import /tmp/checkpoint-demo.tar.zst --name restored-demo

Read logs:

bash
sudo podman logs --tail 8 restored-demo

Sample output:

output
23
24
25
26
27
28

The 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:

bash
sudo podman container checkpoint --create-image checkpoint-demo-image restored-demo

Inspect the checkpoint image:

bash
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:

bash
sudo podman tag checkpoint-demo-image quay.io/your-user/checkpoint-demo:latest
sudo podman push quay.io/your-user/checkpoint-demo:latest

On the destination host, pull the checkpoint image:

bash
sudo podman pull quay.io/your-user/checkpoint-demo:latest

Restore the container from the checkpoint image:

bash
sudo podman container restore quay.io/your-user/checkpoint-demo:latest

The 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:

bash
sudo podman container checkpoint --export /tmp/app-checkpoint.tar.zst app

Transfer the archive:

bash
scp /tmp/app-checkpoint.tar.zst destination:/tmp/

Destination host — restore:

bash
sudo podman container restore --import /tmp/app-checkpoint.tar.zst --name app

Before 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:

bash
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:

bash
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:

bash
sudo mkdir -p /srv/app-data

Recreate 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:

output
Error (criu/sk-inet.c:200): inet: Connected TCP socket, consider using --tcp-established option.
CRIU checkpointing failed: -52: Invalid exchange

Preserve established connections on checkpoint and restore:

bash
sudo podman container checkpoint --tcp-established --export /tmp/app.tar.zst app

Restore with the same TCP flag on import:

bash
sudo podman container restore --tcp-established --import /tmp/app.tar.zst

The 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:

bash
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:

bash
sudo podman container checkpoint --file-locks CONTAINER

Pass the same flag on restore:

bash
sudo podman container restore --file-locks CONTAINER

Omit 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:

bash
sudo podman container checkpoint --print-stats CONTAINER

Sample output (trimmed, from the counter demo on the lab host):

output
{
    "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:

bash
sudo podman container restore --print-stats CONTAINER

Sample output (trimmed, from the counter demo on the lab host):

output
{
    "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:

bash
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


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.


Frequently Asked Questions

1. Is podman container checkpoint rootless?

No. On Podman 5.8.2 checkpointing a container requires root. Rootless podman container checkpoint fails with Error checkpointing a container requires root. Use ordinary stop and start, application-level state, or other patterns for rootless workloads.

2. What is the difference between podman checkpoint and podman save?

podman container checkpoint uses CRIU to freeze running process memory and state so the workload can resume later. podman save exports image layers for distribution. Checkpoint archives carry execution state and need CRIU; image save does not resume a running process.

3. Does podman container checkpoint stop the container?

By default yes. After CRIU writes the checkpoint, the container exits. Pass --leave-running to write a checkpoint while the original keeps running. A restored copy reflects state at checkpoint time, not later changes on the still-running original.

4. Why does CRIU fail with Connected TCP socket?

Default checkpoint does not preserve established TCP connections. Add --tcp-established to checkpoint and restore when you need open sockets frozen. Cross-host TCP restore only works under strict network topology constraints; use --tcp-close on restore when the application can reconnect after restore.

5. Can I migrate a Podman container to another host with checkpoint export?

Yes when both hosts share matching Podman version, OCI runtime, network stack, cgroup version, kernel version, and CPU features, and the destination can access the original container image. Export with podman container checkpoint --export, transfer the archive, and restore with podman container restore --import. Alternatively, use --create-image to push a checkpoint image through a registry.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)