Virtual machine migrations often look successful after the first simple checks.
The VM boots.
The IP address responds.
SSH works.
DNS works.
The default gateway is correct.
The Docker container is running and even reports healthy.
And yet the application is not reachable.
This is exactly the kind of problem where it is easy to spend time on the wrong diagnostic layer.
In this case, an Ubuntu virtual server was moved from Hyper-V to Proxmox. After the migration, the network adapter name changed from the original eth0 to ens18. The static IP configuration was adjusted, and the operating-system network worked correctly.
But the Docker application still did not respond on the expected LAN ports.
The key lesson was simple:
A working VM IP address does not mean that the Docker runtime restored the container network correctly.
And the even more important one:
A healthy container does not mean that the service is reachable over the network.
The first suspect: eth0 changed to ens18
After moving a Linux VM between hypervisors, a network interface name change is common.
On Hyper-V, the system may use something like:
eth0
After booting on Proxmox, the interface may appear as:
ens18
That is not a problem by itself. It is a consequence of different virtual hardware and predictable Linux network-interface naming.
The correct first step is to verify the operating-system network:
ip -br link
ip -br addr
ip route
resolvectl status
networkctl status ens18
In this case, everything was correct:
ens18was active,- the VM had the correct static IP address,
- the default gateway worked,
- DNS resolution worked,
- SSH access was available,
- the interface state was routable/online.
At this stage, it is important not to change Netplan, the gateway or DNS blindly just because the application still does not respond.
If the VM network itself works, the problem may be one layer higher.
The misleading symptom: the container is healthy
Docker showed that the application container was running.
A typical status looks reassuring:
container running
health: healthy
restart policy active
That can easily lead to the conclusion that the application is fine and that the problem must be in Linux networking, firewalling or Proxmox.
But Docker health checks are an internal view. Their meaning depends on how they are defined.
A health check may verify the service inside the container. It can succeed even when the container is not correctly attached to the Docker bridge network or when its ports are not published on the host.
That is why it is not enough to look only at:
docker ps
You also need to verify Docker runtime networking:
docker port <container>
docker inspect <container>
docker network inspect <network>
In this incident, the container was running and healthy, but Docker had not restored its network state correctly.
The real cause: the container lost Docker runtime networking
The problem was not the change from eth0 to ens18.
The problem was not Ubuntu static IP configuration either.
The real issue was Docker runtime state after the migration and restart.
The container started automatically, but it was not correctly attached to its original Docker bridge network. At the same time, it had no active port mappings.
The diagnostics showed something like this:
docker port <container>
returned no published ports.
And docker inspect showed empty or incorrect runtime sections such as:
NetworkSettings.Networks
NetworkSettings.Ports
HostConfig.PortBindings
That means the application could work inside the container, but the expected path did not exist:
VM LAN IP → published host port → Docker bridge → container
The result was this combination:
VM IP works
SSH works
container is running/healthy
application does not respond on the LAN port
docker port is empty
Docker bridge network does not have the container attached
This is exactly the kind of state where almost everything looks correct, but one runtime layer is broken.
Diagnostics must follow the layers
For problems like this, the worst approach is to make random changes.
The correct approach is to move layer by layer.
1. Verify the operating-system network
First, confirm that the Linux VM network is correct:
ip -br link
ip -br addr
ip route
resolvectl status
networkctl status ens18
Check:
- the active interface name,
- IP address and mask,
- default gateway,
- DNS,
- routing,
- interface state.
If this works and SSH is available, Netplan is probably not the root cause.
2. Verify Docker runtime, not only container status
Then move to Docker:
docker ps
docker port <container>
docker inspect <container>
docker network inspect <network>
The important fields are:
NetworkSettings.Networks
NetworkSettings.Ports
HostConfig.PortBindings
If the container is healthy but has no network attachment or published ports, the issue is in the Docker runtime layer.
The important distinction is this:
healthy inside container
is not the same as:
reachable from LAN
3. Verify host ports on the correct IP address
Another common mistake is testing only 127.0.0.1.
If a service is published only on a specific LAN IP address, it may not respond on loopback. That is not necessarily an error.
Test the real VM LAN IP:
curl -fsS http://<LAN-IP>:15000/health
curl -fsS http://<LAN-IP>:21121/api/health
ss -lntup
If ss does not show a listener on the expected IP and port, the problem is not the client. The host is not publishing the service as expected.
Safe repair: recreate only the affected service
If the Compose files, runtime .env file and Docker volumes are preserved, the safest fix is not to reinstall Docker or change the entire VM network.
The safer step is to recreate only the affected service.
Before doing that, check:
- that the Compose files exist;
- that the runtime env file exists;
- that the env file does not contain placeholder values;
- which named volumes the container uses;
- that you are not restarting or deleting the entire stack without a reason.
A controlled recreate may look like this:
docker compose \
-p <compose-project> \
-f <base-compose.yml> \
-f <override-compose.yml> \
--env-file <runtime.env> \
up -d --force-recreate --no-deps <service>
Two options matter here:
--force-recreate
creates a new container with a correct Docker network sandbox.
--no-deps
limits the change to the specific service and avoids restarting dependencies unnecessarily.
If the application uses named volumes, the data remains preserved. That is important for databases, application keys and persistent runtime state.
Avoid this unless you fully understand the impact:
docker compose down -v
The -v flag may delete persistent volumes. On a production or important VM, that is a dangerous operation if you do not know exactly what will be removed.
Verification after repair
After the recreate, it is not enough to see that the container is running again.
Verify every layer:
docker inspect <container>
docker port <container>
docker ps
docker network inspect <network>
curl -fsS http://<LAN-IP>:<dashboard-port>/health
curl -fsS http://<LAN-IP>:<api-port>/api/health
ss -lntup
The repair is complete only when all of this is true:
- the container is
running, - the health check is
healthy, - the container has an IP address in the Docker bridge network,
docker portshows the expected host bindings,- the host listens on the intended LAN IP and ports,
- health endpoints respond through the LAN,
- the service survives the next restart according to its restart policy.
Only then is the Docker application actually available, not merely alive internally.
Why this problem is easy to misdiagnose
This incident is deceptive because the first checks look good.
At the VM level:
IP works
gateway works
DNS works
SSH works
At the Docker level:
container running
container healthy
But application reachability still fails.
That creates the false impression that the problem must be in Proxmox, hypervisor bridge configuration, Linux routing or Netplan.
In reality, the VM network can be completely correct while Docker has failed to restore port publishing and bridge membership for one container after the migration.
That is why it is important to separate four different states:
VM network connectivity
Docker container process state
Docker network attachment and port publishing
Application reachability from LAN
Each of them can be correct or broken independently.
Preventive checklist before VM migration
Before moving a Linux VM with Docker applications, save the current state:
ip addr
ip route
docker ps
docker inspect <container>
docker network ls
docker network inspect <network>
docker volume ls
docker compose ls
For an important service, also export the relevant outputs:
docker port <container>
docker inspect <container> > container-before-migration.json
docker network inspect <network> > network-before-migration.json
After the migration, you have something concrete to compare against.
Preventive checklist after the first boot on Proxmox
After the first boot on Proxmox, do not stop at ping or SSH.
Verify:
ip -br addr
ip route
docker ps
docker port <container>
docker network inspect <network>
ss -lntup
curl -fsS http://<LAN-IP>:<port>/health
The service should be tested from the LAN, not only locally on the host.
If the VM network is correct, do not change Netplan blindly. Check Docker networking first.
Main lesson
During a virtual-server migration, the operating system may correctly move to a new adapter name and have working IP connectivity, while a Docker container remains detached from its bridge network or loses published ports after restart.
That means it is not enough to verify that the VM pings and the container is healthy.
The diagnostic path must follow the layers:
VM network
↓
Docker runtime state
↓
Docker bridge membership
↓
Port publishing
↓
Application health endpoint over LAN
Only when all those layers are correct can we say that the service migration succeeded.
The most important sentence from the whole incident is this:
A healthy container is not proof that the service is reachable over the network.
For production migrations, that is the difference between a server that merely looks started and a service that actually works.
{"research-hyper-v":{"title":"Hyper-V","summary":"Technical profile of Microsoft Hyper-V as a Windows virtualization technology and migration source platform, with focus on guest hardware assumptions, backups, drivers, restore planning and operational risk.","url":"/research/hyper-v/","links":[{"title":"Hyper-V virtualization in Windows Server and Windows","type":"official-documentation","url":"https://learn.microsoft.com/en-us/windows-server/virtualization/hyper-v/hyper-v-technology-overview"},{"title":"Install Hyper-V in Windows and Windows Server","type":"official-documentation","url":"https://learn.microsoft.com/en-us/windows-server/virtualization/hyper-v/get-started/install-the-hyper-v-role-on-windows-server"},{"title":"Plan for Hyper-V on Windows Server","type":"official-documentation","url":"https://learn.microsoft.com/en-us/windows-server/virtualization/hyper-v/plan/plan-hyper-v-on-windows-server"},{"title":"Hyper-V PowerShell module","type":"official-documentation","url":"https://learn.microsoft.com/en-us/powershell/module/hyper-v/"},{"title":"Migrating a Windows VM from Hyper-V to Proxmox with Veeam Community Edition","type":"my-blog","url":"/blog/migrating-windows-vm-from-hyper-v-to-proxmox-with-veeam-community-edition/"}]},"research-proxmox-ve":{"title":"Proxmox VE","summary":"Technical profile of Proxmox VE as an open-source virtualization platform for KVM virtual machines and containers, with focus on migration, storage, networking, backup, restore and operational risk.","url":"/research/proxmox-ve/","links":[{"title":"Proxmox VE official overview","type":"official-website","url":"https://www.proxmox.com/en/proxmox-virtual-environment/overview"},{"title":"Proxmox VE Documentation Index","type":"official-documentation","url":"https://pve.proxmox.com/pve-docs/"},{"title":"Proxmox VE Administration Guide","type":"official-documentation","url":"https://pve.proxmox.com/pve-docs/pve-admin-guide.html"},{"title":"Proxmox VE source repository - pve-manager","type":"source-code","url":"https://git.proxmox.com/?p=pve-manager.git"},{"title":"Migrating a Windows VM from Hyper-V to Proxmox with Veeam Community Edition","type":"my-blog","url":"/blog/migrating-windows-vm-from-hyper-v-to-proxmox-with-veeam-community-edition/"}]}}