DockerContainersDevOps

Docker for Beginners: Containers Are Not Tiny Virtual Machines

Learn what containers really are, how they differ from VMs, and why you'd use Docker—with a simple working example.

WebPrims Team27 September 20267 min read
Docker for Beginners: Containers Are Not Tiny Virtual Machines

A student in our June batch asked me, "Why not just install Python on my laptop and run the script?" He had a point. His script worked fine on his machine. It broke on his teammate's Windows box, then again on the university server. The error messages were different each time. That's the problem containers solve—not "running code" but "running code in the same environment every single time."

We'll strip away the hype. A container is not a virtual machine. They look similar from the outside—you start one, it runs, you stop it. But internally they're different species. Virtual machines emulate hardware. They each carry a full operating system, which is why a VM image can be several gigabytes. Containers share the kernel of the host machine. They don't boot their own OS. They just isolate processes and file systems. That means they start in milliseconds, not minutes. And they're light—a container image might be 50 megabytes, while a VM image is often 5 gigabytes.

The core idea is isolation. We want our application to see only what it needs: a specific version of Python, a certain library, its own filesystem. It shouldn't matter what's installed on the host laptop. Docker gives us that. But how?

What a container actually is

Under the hood, a Linux container uses three operating system features. Namespaces give each container its own view of processes, network interfaces, and file system mounts. Control groups (cgroups) limit how much CPU, memory, and disk I/O a container can use. And union file systems let Docker stack layers of files on top of each other, forming one coherent filesystem.

You don't need to master these to use Docker. But it helps to know why a container isn't magic. It's a set of ordinary Linux processes, just walled off from their neighbors.

Images versus containers

This confuses everyone for the first week. An image is a read-only template. It's like a class in object-oriented programming. A container is a running instance of that image. It's like an object. You can have three containers running from the same image, each with its own writable layer. That writable layer holds anything the process changes—a new file, a modified config. When you delete the container, that layer goes away. The image stays untouched.

The Dockerfile: Your recipe

You write a Dockerfile to define an image. It's a plain text file with instructions. Each instruction creates a new layer in the image. Layers are cached, so if you only change your source code, Docker doesn't rebuild the entire image—it only rebuilds the layers that changed. That's why build feels fast in day-to-day work.

Let's look at a minimal example. Say we have a Python script that reads a CSV file and prints a summary. We want to run it anywhere without installing pandas on the host. Here's a Dockerfile:

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "summarize.py"]

That's six lines. The FROM line pulls a base image—here, a slim Debian with Python 3.11 installed. WORKDIR sets the current directory inside the container. COPY moves files from our project into the image. RUN executes a command during build—here, installing dependencies. The second COPY puts our script and data in. Finally, CMD tells Docker what command to run when we start a container from this image. Note that CMD is not executed at build time. It's the default command for the running container.

We tell students: don't memorize Dockerfile instructions. Learn FROM, COPY, RUN, CMD, and maybe ENV andEXPOSE. The rest you look up when needed. Every month someone asks about ENTRYPOINT versus CMD. We tell them: ENTRYPOINT is the fixed part of the command, and CMD provides the default arguments. If you only need one, use CMD. If you need to override arguments easily, use ENTRYPOINT. That's enough for beginners.

Building and running your first container

Assume our project folder has summarize.py, requirements.txt (containing pandas), and a data.csv. We build the image:

docker build -t summarize .

The -t tags the image with a name, summarize. The dot is the build context—the folder Docker sends to the daemon. Then we run:

docker run --rm -v "$PWD:/app/data" summarize

We're passing two flags here. --rm tells Docker to delete the container after it exits. Without it, every run leaves a stopped container behind, cluttering your disk. The -v flag mounts a volume. It maps our current directory's data folder to /app/data inside the container. Why do we need that? Because our image already has a data.csv baked in from the COPY . . step. But we want to test with a new file without rebuilding the image. The volume overrides that path. This is how you get host files into a container without rebuilding.

When you run that command, Docker checks if the image summarize exists locally. If not, it pulls it from a registry. Then it creates a container from the image, runs the command, prints output to your terminal, and exits. That's it. You've just run a container.

Why this matters in real life

The student's problem—different Python versions, missing libraries—vanishes. The Dockerfile becomes the single source of truth for the environment. Your colleague pulls your repo, runs docker build -t summarize . and docker run ..., and gets the exact same result you do. On their Mac. On the production server. On the Windows machine that hates us all.

But we also tell students what Docker does not fix. It doesn't fix data persistence. By default, anything written inside a container disappears when the container is deleted. You need volumes for that, as we saw above. It doesn't fix networking complexities—you'll need to learn port mapping (-p 8080:80) and compose files for multi-container apps. And it doesn't make your code bug-free. A container that runs your buggy script still prints the buggy output. We've seen people blame Docker for a logic error that existed before.

The one thing to remember

If you take nothing else: the image is the environment. Your application runs inside a frozen snapshot of that environment. Any change to that environment—a new dependency, a config file—must happen in the Dockerfile, not by logging into a running container and apt-get installing stuff. That's the hard lesson from week two. When you break it, you end up with a running container that works, but you can't reproduce it because the image doesn't contain those changes. You'll rebuild from scratch and wonder why the bug comes back. Make your changes in the Dockerfile. Always. Then rebuild the image. Then run a fresh container.

We don't claim Docker is the solution to every deployment problem. For tiny projects, a virtual environment or a simple script might be enough. But once you have more than one service, or you need to share an environment with a team, or you're deploying to a server you don't control, Docker saves you hours per week. That's what we tell students who ask "when do I actually need this?"—when the phrase "works on my machine" starts appearing in your team's discussions, it's time to containerize.

Common questions

Can I run a GUI application inside a Docker container?

Yes, but it's awkward. You need to share the host's display socket and set environment variables, and it only works on Linux or with special tools on Mac/Windows. For GUI apps, a virtual machine is usually simpler. We'd only containerize a GUI app if you're already containerizing the backend and want dev/prod parity.

How do I get a shell inside a running container?

Use docker exec -it <container-name> /bin/bash. That gives you an interactive bash session inside the container. It's useful for debugging, but remember: any changes you make there are lost when the container stops, unless you've mounted a volume. For a one-off check, it's fine. For permanent changes, edit the Dockerfile.

What's the difference between an image tag and an image ID?

A tag is a human-readable label like python:3.11-slim. An image ID is a long hex string like sha256:9f9.... Tags point to IDs, and you can retag an image anytime. You can also have multiple tags for the same image. The ID never changes unless the image content changes. When you pull python:3.11, you're pulling whatever image that tag currently points to—which could change if the maintainers update it. That's why pinning to a specific version in your Dockerfile is good practice.

Do I need to learn Kubernetes if I learn Docker?

No. Kubernetes is an orchestrator for containerized apps across multiple machines. Docker itself is enough for local development, running a single service, or even a small production server. You'd learn Kubernetes when you have many containers, need auto-scaling, or need high availability. Many teams start with Docker Compose—a simpler tool for running multiple containers on one host. That's often sufficient for years, depending on your scale.

Come and sit a class before you decide

Reading about it only gets you so far. Pick a day, sit in on a class that is already running, write some code, and talk to the students in it. Free, and nothing to pay afterwards unless you want to join.

DockerContainersDevOps

Found this useful?

Share it with someone who is learning.

CallWhatsAppFree demo