Migrating Docker from RHEL 8 to Debian 13 — Complete guide

Step-by-step guide to migrating a complete Docker stack from RHEL 8 to Debian 13 — backing up volumes, transferring data, recreating containers and switching the IP.

Migrating Docker from RHEL 8 to Debian 13 — Complete guide Migrating Docker from RHEL 8 to Debian 13 — Complete guide
Table of Contents

Docker dropped support for RHEL 8 starting with version 29. If your infrastructure still runs Docker on RHEL 8, here is how to migrate cleanly to Debian 13 without a long outage.

Why migrate?

  • Docker 28 is the last version supported on RHEL 8
  • Docker 29+ requires RHEL 9 or another distribution
  • Debian 13 (Trixie) supports Docker 29+ natively
  • Fewer licensing constraints than RHEL

Inventory before migrating

# List the containers
docker ps -a --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"

# List the volumes
docker volume ls

# Find bind mounts (data outside Docker volumes)
docker inspect $(docker ps -aq) \
  --format '{{.Name}}: {{range .Mounts}}{{.Type}}:{{.Source}}→{{.Destination}} {{end}}'

Example stack to migrate:

Container Persistent data Method
portainer portainer_data volume tar export
app-custom Local build + .env Copy sources + rebuild
stateless-tools None Re-pull the image
website Bind mount /root/site/ Copy within the home archive

Step 1 — Back up

Home archive (bind mounts included)

# Leave out large, regenerable caches
tar czf /tmp/home-backup.tar.gz \
  --exclude=/root/jellyfin/cache \
  --exclude=/root/.cache \
  /root

ls -lh /tmp/home-backup.tar.gz

Export the Docker volumes

# For each named volume
for vol in $(docker volume ls -q); do
  echo "Exporting $vol..."
  docker run --rm \
    -v "${vol}:/data" \
    -v /tmp:/backup \
    alpine tar czf "/backup/${vol}.tar.gz" /data
done

ls -lh /tmp/*.tar.gz

Step 2 — Prepare the Debian 13 VM

Create a new VM (Proxmox or other) with Debian 13 netinstall and a 50 GB disk.

# System update
apt update && apt upgrade -y

# Essential tools
apt install -y curl wget git vim sudo openssh-server netcat-openbsd

# Docker via the official script
curl -fsSL https://get.docker.com | sh

# Check
docker version
# → Client/Server: Docker Engine 29.x.x

Step 3 — Transfer the data

# From the Debian VM — fetch the archives
scp root@OLD-IP:/tmp/home-backup.tar.gz /tmp/
scp root@OLD-IP:/tmp/portainer_data.tar.gz /tmp/

# Extract the home directory
tar xzf /tmp/home-backup.tar.gz -C /

# Import the Portainer volume
docker volume create portainer_data
docker run --rm \
  -v portainer_data:/data \
  -v /tmp:/backup \
  alpine tar xzf /backup/portainer_data.tar.gz -C /

# Check
docker run --rm -v portainer_data:/data alpine ls /data
# → backups bin certs portainer.db ...
root@debian13:~# docker run --rm -v portainer_data:/data alpine ls /data
backups bin certs portainer.db ...
The Portainer volume is back, with its database

Step 4 — Recreate the containers

Portainer (without Docker Swarm)

# /root/portainer-compose.yml
services:
  portainer:
    image: portainer/portainer-ee:latest
    container_name: portainer
    restart: always
    ports:
      - "8000:8000"
      - "9443:9443"
    volumes:
      - portainer_data:/data
      - /var/run/docker.sock:/var/run/docker.sock

  portainer_agent:
    image: portainer/agent:latest
    container_name: portainer_agent
    restart: always
    ports:
      - "9001:9001"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /var/lib/docker/volumes:/var/lib/docker/volumes

volumes:
  portainer_data:
    external: true
docker compose -f /root/portainer-compose.yml up -d

Stateless containers

# No data to migrate — just re-pull and recreate
docker run -d --name it-tools --restart always -p 8080:80 corentinth/it-tools
docker run -d --name srv-kms  --restart always -p 1688:1688 mikolatero/vlmcsd

Container with a local build

cd /root/my-app
docker compose up -d --build

Step 5 — Switch the IP

Once every container has been checked on the new VM:

# 1. Shut down the old VM (don't delete it right away)
# 2. Give the new VM the same IP (static DHCP lease or network config)

# On Debian 13 — change the IP
nano /etc/network/interfaces
# Change the IP address

systemctl restart networking

# 3. Check connectivity
ip a
ping 8.8.8.8

Step 6 — Update HAProxy if needed

If backends point to an IP that changed:

# haproxy.cfg — update the backend IPs
backend my_backend
    server app NEW-IP:PORT check
haproxy -c -f /etc/haproxy/haproxy.cfg && systemctl reload haproxy

Final check

# Every container must be Up
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

# Test each service
curl -sk https://my-service.domain.com -o /dev/null -w "%{http_code}"
# → 200
user@laptop:~$ curl -sk https://my-service.domain.com -o /dev/null -w "%{http_code}"
200

Rollback

# If something goes wrong — power the old VM back on
# (keep it shut down for 48 h before deleting it for good)
# Put the IP back on the old VM
# The new VM gets its original IP back
Zero downtime

By setting up the new VM on a temporary IP and switching the IP only once the migration is validated, the downtime shrinks to the network restart (~30 seconds).

Conclusion

A RHEL 8 → Debian 13 migration of a standard stack takes less than 2 hours. The key: back up the Docker volumes properly, and only delete the old VM after 48 hours of validation in production.

Comments