Docker Explained: How Containers Make Software Run the Same Everywhere

Diagram of several Docker containers running side by side on one shared host kernel, each holding an app and its tools

Docker containers are sealed packages that hold an app together with everything it needs to run, so it behaves the same on your laptop, a teammate's laptop or a server. Docker was built to end the familiar excuse, "but it works on my machine."

This guide starts with that problem and works up, one idea at a time, to what goes inside a container, how containers differ from virtual machines, and how a Dockerfile becomes an image that you can run as many containers.

In short
  • Code often breaks on another computer because its environment is different, not because the code changed.
  • A container bundles your app with its runtime, libraries, configuration and basic files.
  • Containers share the host's kernel, so they start quickly and use fewer resources than virtual machines.
  • A Dockerfile is a recipe. Docker builds it into a read-only image made of layers.
  • You can start many containers from one image, and each begins from the same template.
  1. 0:00Intro
  2. 0:23The works on my machine problem
  3. 1:21Software depends on its environment
  4. 2:17A container packs everything together
  5. 3:11What goes inside the box
  6. 4:04Containers share the host's kernel
  7. 4:59Fast to start, light to run
  8. 5:41Virtual machines carry a full OS
  9. 6:41Containers vs virtual machines
  10. 7:35When to use which one
  11. 8:29A Dockerfile is a recipe
  12. 9:29From recipe to image, step by step
  13. 10:24An image is a read-only template
  14. 11:14One image, many containers

Why does code work on my machine but not on others?

Almost every developer has lived through this. Your code runs perfectly on your laptop. Then you push it to a server or hand it to a teammate, and it breaks. The code is identical, byte for byte, but the result depends on which computer runs it. That tells you the real problem isn't the code at all.

The cause is the environment. Software never runs alone. Your app relies on libraries, settings and specific versions of tools, and all of these quietly differ from one computer to the next. Think of your code as the tip of an iceberg, with a lot of supporting software underneath that your app expects to find exactly as it was when you built it.

Small differences cause real failures. You might build with a newer version of a library while the server still has an older one. A function you rely on doesn't exist there, so the app fails while it's running. Or an environment variable you set months ago and forgot about simply isn't on the new machine.

  • Broken releases: the app passes every local test, then crashes on the server.
  • Slow onboarding: a new teammate spends days installing tools before running the project once.
  • Lost time: every hour spent fixing setups is an hour not spent building features.

What is a Docker container?

Once you see that the environment is the problem, the solution follows naturally: ship the environment along with the app. That is what a container does. You don't hope the other computer is set up correctly. You bring the setup with you, and the app and everything it needs travel together in one sealed package.

The name comes from shipping. Before standard steel containers, cargo was loaded piece by piece. With one standard box, any ship, train or truck can carry it without caring what's inside. Docker containers work the same way. The computer running your app only needs Docker installed. It doesn't need your exact library versions, because the container already has them.

  • A container is your app bundled with its libraries, settings and tools.
  • It behaves the same on a laptop or on a server in a data center.
  • The host needs Docker. The container brings the rest.

What goes inside a container?

A container holds several kinds of things, from your own code down to the basic files the app expects to find. The goal is that once the app is packaged, the host computer doesn't have to provide anything special. The box should be complete on its own.

Take a small Python web app. Its container holds your script, the exact Python version you tested with, and every package your script imports. Nobody has to install Python by hand on the machine that runs it.

Notice what's missing, though. A container does not include the core of the operating system, called the kernel. Leaving the kernel out is a deliberate choice, and it is the reason containers are so light.

  • Your code
  • The language runtime it needs
  • The libraries it imports
  • Its configuration
  • A small set of basic system files

How containers share the host's kernel

Every operating system has a kernel. It manages memory, files and processor time, and it talks to the hardware. A useful way to picture it is as a building manager who hands out resources whenever a program asks. Containers don't bring their own kernel. They borrow the one already running on the host.

That lets many containers run side by side on one machine. A web app, a database, a cache and a background worker can all share the same kernel without seeing each other's files. By default, one container can't see another's files or processes. They're like separate apartments in one building, isolated even though they share a manager.

Sharing the kernel brings speed and efficiency. There is no operating system to boot, so a container starts about as quickly as the app inside it. As the video puts it, a container starts like an app, not like a computer. Because they're cheap to start, teams run many containers on one server and replace them whenever they like.

Containers vs virtual machines: what's the difference?

Before containers, the popular way to package software with its environment was the virtual machine (VM). A VM pretends to be an entire computer. Real hardware sits at the bottom, a program called a hypervisor divides it into virtual machines, and each VM boots a complete guest operating system before your app can start.

VMs have real strengths. Each one is strongly separated and can run a completely different operating system, such as Windows on a Linux server. The cost is weight. Running several VMs means running several full operating systems, each needing its own memory, disk space and boot time.

An everyday analogy helps. A VM is a detached house with its own plumbing and heating. A container is an apartment: private inside, but sharing the building's pipes and foundation. That sharing has one catch. Containers need a compatible kernel, so Linux containers need a Linux kernel. That's why Docker on a Mac or Windows quietly runs a small Linux virtual machine in the background.

  • VM: includes a full guest OS, slower to start, uses more memory and disk, can run a different OS.
  • Container: shares the host's kernel, starts in moments, uses fewer resources, needs a compatible kernel.

When should you use a container or a VM?

Beginners often assume containers replaced virtual machines. In practice, the two solve slightly different problems, and many real systems use both. To choose, ask one question: am I isolating one application, or an entire computer?

If you want to ship and run an application consistently, use a container. If you need a whole separate machine with its own operating system, use a virtual machine. In the cloud, your containers very often run inside VMs, so the two are partners, not rivals.

  • Containers suit web apps, APIs, background jobs and systems split into many small services.
  • VMs suit different operating systems, or a stronger wall around workloads you don't trust.

What is a Dockerfile?

To make a container, you start with a plain text file called a Dockerfile. It's a recipe: a list of instructions Docker follows step by step. The Dockerfile usually lives in the same project folder as your code, and anyone with Docker can read it and build exactly the same result.

Here is a small example for a Node web app. FROM picks a starting image with Node installed. WORKDIR sets a folder, COPY brings in your code, RUN installs packages, and CMD says what to launch when the container starts. In cooking terms, FROM is the base ingredient, COPY and RUN are the steps, and CMD is how to serve the dish.

The big win is that setup knowledge no longer lives in someone's head or on an old wiki page. It's written as code, reviewed with code and versioned with code, so the setup is no longer hidden knowledge.

# start from an image with Node installed
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]

How Docker builds an image, layer by layer

When Docker reads a Dockerfile, it works through the instructions from top to bottom, and each instruction adds a layer. An image isn't one big blob. It's a stack of thin layers, and each layer records what a single instruction changed.

First, Docker pulls the base image named in FROM, so someone else's work installing Node gives you a solid starting point. Next, it runs each instruction in order. COPY adds your files as one layer and RUN adds your packages as another. If a step hasn't changed since the last build, Docker reuses the saved layer, so rebuilds get much faster. Finally, Docker saves the whole stack as an image with a name you choose.

An image is read-only. Once it's built, nobody edits it. To make a change, you update the Dockerfile and build a fresh image. Like a photograph, it captures one moment exactly. Images also get tags, such as myapp:1.2, which work like version labels. If a new version has a bug, you run the older image again, so rolling back is easy.

Docker image vs container: what's the difference?

These are the two words people mix up most in Docker. An image is what you build, store and share. It doesn't run on its own. A container is a running instance of an image. The image is the plan, and the container is the plan in action.

You build an image once, then start as many containers from it as you need. Each container begins from exactly the same template, so they all start identical, even on different machines. Think of a cookie cutter: the image is the cutter, and every container is a cookie. Eating a cookie doesn't change the cutter.

A running container can write files and change things, but only in its own thin writable layer on top of the image. The image underneath stays untouched. When you delete a container, its changes go with it, and the image is safe to start fresh from again.

Key takeaways

  • The "works on my machine" problem comes from different environments, not different code.
  • Docker containers package an app with its runtime, libraries, config and basic files, but not a kernel.
  • Sharing the host's kernel makes containers fast to start and light to run.
  • Use containers to ship applications and VMs when you need a whole separate machine. They often work together.
  • A Dockerfile builds a layered, read-only image, and each container is a running instance of that image.

Frequently asked questions

Is a Docker container the same as a virtual machine?

No. A virtual machine runs a full guest operating system on top of a hypervisor. A container shares the host's kernel, so it starts in moments and uses fewer resources.

Why does Docker on Mac or Windows use a virtual machine?

Linux containers need a Linux kernel. On a Mac or Windows, Docker quietly runs a small Linux virtual machine in the background to provide one.

What is the difference between a Docker image and a container?

An image is a read-only template that you build, store and share. A container is a running instance of that image, and you can start many containers from one image.

What does a Dockerfile do?

A Dockerfile is a text recipe that tells Docker how to build an image. Instructions such as FROM, WORKDIR, COPY, RUN and CMD set the base, add your code, install packages and define what to launch.

What happens to changes made inside a running container?

They are written to the container's own thin writable layer, and the image underneath stays untouched. When you delete the container, those changes are removed with it.

Why are Docker rebuilds faster after the first build?

Each Dockerfile instruction creates a layer, and Docker saves those layers. If a step hasn't changed since the last build, Docker reuses its saved layer instead of running it again.

Watch the full video on YouTube →

Souy Soeng

Souy Soeng

Hi there 👋, I’m Soeng Souy (StarCode Kh)
-------------------------------------------
🌱 I’m currently creating a sample Laravel and React Vue Livewire
👯 I’m looking to collaborate on open-source PHP & JavaScript projects
💬 Ask me about Laravel, MySQL, or Flutter
⚡ Fun fact: I love turning ☕️ into code!

Post a Comment

CAN FEEDBACK
Ad