What Is Docker? Why It Exists and How Containers Solved "It Works on My Machine"
DevOpsSep 19, 20269 min read

What Is Docker? Why It Exists and How Containers Solved "It Works on My Machine"

Learn why Docker exists, how containers solve the “it works on my machine” problem, and how Docker changed modern software development.


You build an application on your laptop.

It works.

You push the same application to another developer's machine, staging, or production.

Suddenly, something breaks.

A package version is different. The Python version is different. A system dependency is missing. The database behaves differently. An environment variable is not configured correctly.

Then comes the sentence every developer has heard:

"But it works on my machine."

This is one of the problems that containerization was designed to address.

And Docker became one of the most widely used platforms for building, shipping, and running containerized applications.

But before learning commands like docker build, docker run, or docker compose up, it is more important to understand why Docker exists in the first place.


The Real Problem: Your Application Does Not Run in Isolation

When you write an application, you are not only writing code.

Your application depends on an environment.

For example, imagine building a Django application.

Your project might require:

Python 3.12
Django
PostgreSQL
Redis
Celery
System libraries
Environment variables

Your code may work perfectly on your laptop because your laptop has exactly what the application needs.

So you might have:

Your Laptop
 
Python 3.12
Django
PostgreSQL
Redis
Celery
Your Application

Now another developer clones your project.

Their machine might have:

Another Developer's Laptop
 
Python 3.10
Different PostgreSQL version
Different Redis version
Different system libraries
Different environment configuration
Your Application

The source code might be identical.

The environment isn't.

And that difference can be enough to cause problems.


Before Docker: Setting Up Applications Manually

Before containers became widely adopted, deploying an application often involved manually configuring the server.

For a Django application, you might need to:

  1. Install Python.
  2. Install PostgreSQL.
  3. Install Redis.
  4. Install system dependencies.
  5. Create a Python virtual environment.
  6. Install Python packages.
  7. Configure environment variables.
  8. Configure the database.
  9. Configure Redis.
  10. Configure Gunicorn.
  11. Configure Nginx.
  12. Configure SSL.
  13. Configure the operating system.

Every server could end up slightly different.

That creates an important problem:

The application code may be consistent while the environment running it is not.

This is where things become difficult.


The "Works on My Machine" Problem

Let's say you build an application using:

Python 3.12
Django
PostgreSQL 16
Redis 7

Everything works locally.

Your teammate uses:

Python 3.11
Django
PostgreSQL 15
Redis 6

The application might still work.

Or it might not.

Then you deploy to a production server running:

Python 3.10
PostgreSQL 14
Redis 6

Now something fails.

The frustrating part is that the source code hasn't changed.

The environment has.

This is why software engineers started looking for better ways to package applications and their dependencies into reproducible environments.


Virtual Machines Helped — But They Were Heavy

Virtual machines were one important solution.

A virtual machine allows you to create an isolated environment with its own operating system.

You could have:

Physical Server


Virtual Machine

       ├── Operating System
       ├── Python
       ├── PostgreSQL
       └── Application

This provided strong isolation.

But there was a trade-off.

Each virtual machine typically needed its own operating system and associated resources.

If you had several applications, you could end up with:

Physical Server

├── VM 1
│    └── Full Operating System

├── VM 2
│    └── Full Operating System

├── VM 3
│    └── Full Operating System

└── VM 4
     └── Full Operating System

That meant more:

  • CPU usage
  • memory usage
  • storage
  • startup overhead
  • operational complexity

Developers needed a way to achieve application isolation without requiring a complete guest operating system for every application.

That brings us to containers.


What Is a Container?

A container is an isolated environment for running an application and its dependencies.

Instead of thinking:

"Install everything this application needs on the server."

You can think:

"Package the application and its required environment into a portable unit."

Conceptually:

Container

├── Application
├── Runtime
├── Dependencies
└── Configuration

Docker describes containers as lightweight, isolated environments that contain what an application needs to run, reducing reliance on what happens to be installed on the host machine.

The goal becomes:

Build

Package

Test

Run the same application environment

Development

Production

This is one of the fundamental ideas behind containerization.


So Where Does Docker Come In?

Docker is an open platform for developing, shipping, and running applications. It provides tools for packaging applications into containers and managing their lifecycle.

The basic idea looks like this:

Application + Dependencies


      Docker Image


       Container

The important thing here is that Docker isn't simply another way to run your application.

It changes how we think about the application's environment.


Docker Makes the Environment Part of the Application

Without containerization, you might think about your project like this:

Source Code


Application

But your application actually depends on much more:

Application

    ├── Python version
    ├── Libraries
    ├── System dependencies
    ├── Configuration
    ├── Database
    └── Other services

Docker allows developers to package an application and its dependencies into a standardized container environment.

So instead of saying:

"Install Python 3.12, then install these packages, then configure this..."

you can describe much of the required environment as code.

That description can then be used to build the same application environment repeatedly.

This is one of the biggest shifts Docker introduced into everyday development workflows.


Build Once, Run Consistently

One of the ideas you'll hear frequently when learning Docker is:

Build once, run consistently.

Suppose you build a Docker image containing your application and its dependencies.

You can use that image in different environments:

              Docker Image

       ┌───────────┼───────────┐
       ▼           ▼           ▼
 Development     Testing    Production

The infrastructure around those environments can still differ, but the application package becomes much more consistent.

Docker's documentation specifically describes the container as a unit that can be used for distributing and testing an application before deployment.

This helps reduce an entire category of environment-related problems.


Docker Does Not Eliminate Configuration

This is an important distinction.

Docker does not magically make every environment identical.

You still have things such as:

  • environment variables
  • secrets
  • database credentials
  • network configuration
  • storage
  • external services
  • infrastructure differences

For example, your development database might be:

PostgreSQL running locally

while production might use:

Managed PostgreSQL

Docker doesn't remove these differences.

Instead, it gives you a much more consistent way to package and run the application itself.


Why Docker Became So Important

Modern applications rarely consist of a single process.

A backend application might have:

Django

   ├── PostgreSQL
   ├── Redis
   ├── Celery
   └── Nginx

Each component has its own requirements.

Without containerization, setting everything up manually can become complicated.

With containers, these components can be isolated into separate environments:

┌─────────────────────────────────┐
│          Application            │
│                                 │
│  ┌───────┐   ┌──────────────┐  │
│  │Django │   │ PostgreSQL   │  │
│  └───────┘   └──────────────┘  │
│                                 │
│  ┌───────┐   ┌──────────────┐  │
│  │ Redis │   │    Celery    │  │
│  └───────┘   └──────────────┘  │
└─────────────────────────────────┘

This is where Docker becomes particularly useful for backend development.


Docker Also Changed Team Collaboration

Imagine joining a project where the setup documentation says:

Install Python
Install PostgreSQL
Install Redis
Install these packages
Configure this
Configure that

There is room for mistakes.

With Docker, a project can define much of its environment in files such as:

Dockerfile
docker-compose.yml

A developer can use those definitions to reproduce the required environment.

Instead of every developer manually interpreting a setup guide, the project itself contains much of the setup information.

This makes development environments more reproducible.


Docker and CI/CD

Docker also fits naturally into modern CI/CD pipelines.

A simplified workflow might look like:

Developer

    │ git push

GitHub


CI Pipeline

    ├── Run Tests
    ├── Run Security Checks
    └── Build Docker Image


       Container Registry


         Deployment

Docker's documentation describes containers as useful in continuous integration and continuous delivery workflows, where an application can move from development to testing and eventually production as a container image.

The result is a more standardized path from:

"What we tested"

to:

"What we deployed"

Docker Is Not a Virtual Machine

This distinction is worth remembering.

A simplified virtual machine architecture looks like:

Physical Machine


   Host OS

   Hypervisor

 ┌─────┴─────┐
 ▼           ▼
 VM          VM
 │           │
 OS          OS
 │           │
 App         App

Containers work differently:

Physical Machine


   Host OS


 Docker Engine

 ┌─────┼──────────┐
 ▼     ▼          ▼
App   Redis      Database

Containers share the host operating system's kernel rather than requiring a complete guest operating system for every application.

This generally makes containers lighter and faster to start than traditional virtual machines. Docker also describes containers as a lightweight alternative to hypervisor-based virtualization for many workloads.


Docker Did Not Invent Containers

This is an important piece of Docker's history that is often simplified.

Containers as a technology existed before Docker.

Docker helped make container-based application development significantly more accessible and popular among developers.

The ecosystem later developed open standards through the Open Container Initiative (OCI), which was launched in 2015 by Docker, CoreOS, and other industry participants. OCI maintains specifications covering container runtimes, images, and distribution.

This means Docker is part of a much larger container ecosystem.

You can think of it like this:

Container Technology

        ├── Docker

        ├── OCI Standards

        ├── containerd

        ├── runc

        └── Other Container Tools

Understanding this distinction becomes increasingly important as you move deeper into cloud-native development.


What Docker Actually Gives Developers

Docker is useful because it addresses several recurring development and deployment problems.

1. Consistency

The application environment can be defined and reproduced more reliably.

2. Isolation

Applications and their dependencies can run in separate containers.

3. Reproducibility

You can recreate environments from defined configurations.

4. Portability

Containerized applications can run across many environments that support the container runtime.

5. Faster onboarding

Developers can spend less time manually configuring dependencies.

6. Easier CI/CD integration

Docker images can become standardized artifacts in automated build and deployment pipelines.

7. Service separation

Applications composed of multiple services can run those services independently.

Docker's documentation highlights consistency, portability, CI/CD workflows, and the ability to package an application as a unit for development, testing, and deployment as key benefits.


But Docker Is Not Magic

Docker solves a specific class of problems.

It doesn't automatically give you:

  • scalable architecture
  • good application design
  • database optimization
  • security
  • high availability
  • monitoring
  • disaster recovery

You can put a poorly designed application inside a Docker container and still have a poorly designed application.

Docker is an infrastructure and packaging tool, not a replacement for good engineering.

This distinction matters.

Docker can make a bad application easier to deploy. It doesn't make the application good.


The Mental Model You Should Have

If you're just starting with Docker, don't begin by memorizing commands.

Start with this mental model:

Problem


Different environments


"Inconsistent environments"


Containers


Docker


Portable and reproducible application environments

Then the technical pieces will make much more sense.

Docker introduces several concepts that work together:

Docker

├── Images
├── Containers
├── Dockerfiles
├── Volumes
├── Networks
└── Docker Compose

But what exactly are these things?

That's where the next part begins.


What's Next?

In Part 2, we'll move from the why to the how.

We'll break down the core Docker concepts:

Dockerfile


Docker Image


Docker Container

Then we'll understand:

  • What an image actually is
  • What a container actually is
  • How Docker builds an image
  • How a container starts
  • What a Dockerfile does
  • What volumes are
  • How Docker networking works
  • And the essential Docker commands

Because before running:

docker build
docker run
docker exec

you should understand what Docker is actually doing behind those commands.

Part 2: Docker Images, Containers and Dockerfiles — Understanding How Docker Works.


References

The concepts discussed in this article are based primarily on official Docker documentation and Open Container Initiative standards.

  1. Docker Documentation — What is Docker? Overview of Docker, containers, images, portability, CI/CD, and the relationship between containers and infrastructure. https://docs.docker.com/get-started/docker-overview/

  2. Docker Documentation — Build and Share a Containerized Application Practical explanation of packaging application dependencies into images and running them as containers. https://docs.docker.com/get-started/tutorials/run-an-app/

  3. Open Container Initiative — About OCI Background on container standards, including the Runtime, Image, and Distribution specifications. https://opencontainers.org/about/overview/

  4. Open Container Initiative — Runtime Specification Technical specification covering the configuration, execution environment, and lifecycle of containers. https://specs.opencontainers.org/runtime-spec/


Series: Docker Fundamentals: From "It Works on My Machine" to Production

Part 1: What Is Docker? Why It Exists and How Containers Solved "It Works on My Machine"

Part 2: Docker Images, Containers and Dockerfiles — Understanding How Docker Works

Part 3: Docker Compose — Running a Real-World Application with Multiple Containers

More posts

Software EngineeringSep 5, 20268 min read

Concurrency vs Parallelism: What's the Difference and Why It Matters

Concurrency and parallelism are essential concepts for building efficient software. Learn the difference between concurrency and parallelism, how CPU cores and I/O affect them, and when to use approaches like threads, processes, and asynchronous programming in real-world applications.