Containers vs Virtual Machines: A Practical Guide to Running Containers

Containers are lightweight, run-time instances of images that behave very differently from virtual machines, even though both aim to isolate and run applications. This article compares the two models and walks through starting, inspecting, debugging, and applying restart policies to containers with Docker.

Docker ContainersContainers vs VMsRestart Policies

~5 min read · Updated Sep 5, 2026

Introduction

A container is a run-time instance of an image, and multiple containers can be started from the same image. While the shared image is read-only, each container gets its own writable layer, making containers fast, lightweight, and disposable compared to traditional virtual machines.

Containers vs Virtual Machines

Containers and virtual machines (VMs) both virtualize resources to run applications, but in fundamentally different ways. VMs virtualize hardware: a hypervisor claims physical resources like CPU and RAM and carves them into virtual machines that each require their own full operating system. Containers, on the other hand, virtualize the operating system itself, sharing the host's kernel while packaging application code into isolated, lightweight units.

Because every VM needs its own OS, VMs consume more resources and take longer to boot. Containers avoid this overhead entirely, which means:

  • Containers are smaller and more portable
  • More containers can run on the same infrastructure compared to VMs
  • Containers start significantly faster
  • Fewer operating systems need to be patched and managed
  • Containers present a smaller attack surface

One historical concern with containers is the shared-kernel model, since a kernel vulnerability could theoretically affect every container on a host. Modern container platforms mitigate this with security technologies such as SELinux, AppArmor, seccomp, and image vulnerability scanning.

Images and Containers

When multiple containers are started from a single image, Docker gives each container its own thin read-write (R/W) layer on top of the shared, read-only image. Changes made inside a container are written to this layer. Stopping a container preserves this layer, but deleting a container removes it permanently.

Starting a Container

The docker run command is the most common way to start a new container:

docker run -d --name webserver -p 5005:8080 nigelpoulton/ddd-book:web0.1

Here, -d runs the container in the background, --name assigns it a name, and -p maps a host port to a container port. If the image isn't available locally, Docker automatically pulls it from Docker Hub before starting the container.

How Containers Start Applications

Docker determines what to run inside a container in one of three ways: an Entrypoint instruction baked into the image, a Cmd instruction in the image, or a command passed directly on the CLI. Entrypoint instructions cannot be overridden from the command line, while Cmd instructions can.

Connecting to a Running Container

The docker exec command allows commands to be run inside a running container, either interactively or as a one-off remote execution:

docker exec -it webserver sh

Since most containers are stripped down to essential tools, many familiar commands may not be available inside them.

Container Processes and Inspection

Most containers run a single main process, always assigned PID 1. If this process is killed, the entire container stops, since containers only exist while their main process is running. The docker inspect command provides detailed configuration and runtime information about a container, including its state, port bindings, and entrypoint.

Persisting and Losing Changes

Changes made to a running container's filesystem are stored in its writable layer and survive stop and restart operations. However, once a container is deleted with docker rm, those changes are permanently lost, and starting a new container from the same image won't include them. Modifying live containers directly is generally considered an anti-pattern; the recommended approach is to build a new image with the required changes and replace the container.

Debugging Slim Containers with Docker Debug

Docker Debug (available with a Pro, Team, or Business subscription) solves the common problem of debugging minimal images that lack a shell or troubleshooting tools. It attaches a temporary toolbox to a container or image, allowing tools like ping, vim, or nslookup to be used even if they aren't installed. Changes made while debugging a running container persist, but changes made while debugging a stopped container or an image are discarded once the session ends.

Self-Healing with Restart Policies

Docker supports four restart policies that control how containers recover from failure:

  • no (the default) — never automatically restarts
  • on-failure — restarts only on a non-zero exit code
  • always — always restarts, including after a daemon restart
  • unless-stopped — always restarts, unless manually stopped

A restart policy is applied when starting a container, for example:

docker run --name neversaydie -it --restart always alpine sh

Conclusion

Containers offer a fundamentally more efficient way of virtualizing applications compared to VMs, trading dedicated operating systems for shared-kernel isolation. Understanding how containers start, persist changes, can be debugged, and recover from failure through restart policies is essential for working with them reliably in real-world environments.

Written & researched by Dr. Shahin Siami

Related Articles

How Docker Uses Linux Security Technologies: Namespaces, Cgroups, and More

Docker's security model is built on layered defense, combining well-established Linux kernel technologies with sensible defaults out of the box. This article explains how namespaces, control groups, capabilities, mandatory access control, and seccomp work together to isolate and secure containers.

Continue

Managing Persistent Data in Docker with Volumes

Containers get a temporary writable layer by default, which disappears the moment a container is deleted — fine for scratch data, but risky for anything worth keeping. This article explains how Docker volumes decouple persistent data from container lifecycles, and walks through creating, using, and sharing them safely.

Continue

How Docker Overlay Networks Work: VXLAN Under the Hood

Overlay networks let containers on different hosts communicate as if they shared a single flat network, forming the backbone of most cloud-native microservices apps. This article covers how to build, encrypt, and test a Docker overlay network across a Swarm cluster, and explains the VXLAN tunneling technology powering it behind the scenes.

Continue

Connecting Docker Containers to VLANs and Load Balancing with Swarm

Beyond basic bridge networks, Docker can connect containers directly to existing physical VLANs, resolve service names automatically, and distribute traffic across a Swarm cluster. This article covers the macvlan driver, Docker's built-in service discovery, and Swarm's ingress load balancing mesh.

Continue

Understanding Docker Networking: CNM, Libnetwork, and Bridge Networks

Docker networking is built on an open design called the Container Network Model, implemented through libnetwork and extended by pluggable drivers. This article covers the theory behind Docker networking and walks through creating and testing single-host bridge networks, including name resolution and port mapping.

Continue

Deploying and Managing Multi-Node Clusters with Docker Swarm

Docker Swarm turns a group of Docker nodes into a secure, highly available cluster with built-in application orchestration. This article covers building a multi-node swarm, deploying a microservices app declaratively, and performing rolling updates without losing desired-state consistency.

Continue