Skip to content
DevOps AI ToolKit
Newsletter
All guides
Docker with AI By James Joyner IV · · 9 min read

What Is Docker? A Plain, No-Hype Explanation for Engineers

What is Docker? A plain, no-hype guide for engineers — what it is, the "works on my machine" problem it solves, container vs VM, and the commands that matter.

  • #docker
  • #containers
  • #beginners

If you’ve been around software for more than a week, you’ve heard “just Dockerize it” and nodded along without being totally sure what that meant. Docker is a tool for packaging an application together with everything it needs to run — its code, runtime, libraries, and config — into a single, portable unit called a container. The whole point is that the thing which runs on your laptop runs the same way on a server, and you stop losing afternoons to “works on my machine.”

That’s the answer. The rest of this post is the reasoning behind it, because Docker is genuinely less magic than it sounds once you see where the pieces sit.

The problem Docker actually solves

Here’s the situation almost everyone has lived through. You write some code, it runs fine locally, you hand it off, and it breaks in test. Someone spends an hour discovering the test box has Python 3.9 and yours has 3.11, or a system library is a different version, or an environment variable only exists on your machine.

None of that is a code bug. It’s an environment bug, and environment bugs are miserable because they hide. The application is correct; the ground it’s standing on is different.

Docker’s core idea is to stop shipping just your code and start shipping the environment with it. You describe the environment once, build it into an image, and that image is what moves between machines. The base your app stands on travels with the app.

After enough years in operations, the thing I appreciate most about this isn’t speed or density — it’s repeatability. A build you can reproduce is a build you can reason about.

Container vs. VM: the part people get wrong

The most common mental model I hear is “a container is a lightweight VM.” It’s close enough to be useful and wrong enough to cause confusion, so let’s be precise.

A virtual machine virtualizes hardware. A hypervisor gives each VM its own emulated machine, and each VM runs a full guest operating system — its own kernel, its own init system, the works. That’s why VMs are heavy: you’re booting a whole OS per VM.

A container does not do that. Containers share the host’s kernel. A container is really just one or more normal Linux processes that the kernel has been told to isolate — it gets its own view of the filesystem, its own process tree, its own network interfaces, and limits on CPU and memory. But there’s no guest OS and no second kernel. It’s your process, fenced off.

The mechanisms doing the fencing are ordinary Linux kernel features:

  • Namespaces — give the process an isolated view of things like the process list (PID), mounts, network, and hostname.
  • Cgroups (control groups) — cap how much CPU, memory, and I/O the process can use.
  • A layered filesystem — the image is built from stacked read-only layers, with a thin writable layer on top when the container runs.

That’s the whole trick. There’s no tiny operating system inside the container — just your process wearing a set of restrictions the kernel already knew how to enforce.

The practical consequences fall out of that design:

  • Containers start in milliseconds, not the seconds-to-minutes a VM boot takes. There’s no OS to boot.
  • They’re small — you ship your app and its dependencies, not a whole OS image.
  • Because they share the host kernel, a Linux container needs a Linux kernel. On macOS and Windows, Docker quietly runs a small Linux VM in the background and starts your containers inside that. So on those platforms you get both, which is fine to know but rarely something you think about.

Image vs. container: two words worth separating

These get used interchangeably and shouldn’t be. The distinction is the same as a class and an object, or a program on disk and a running process.

  • An image is the built, immutable package — the filesystem plus metadata about how to start it. It’s a template. It doesn’t run.
  • A container is a running (or stopped) instance of an image. You can start many containers from one image, and each gets its own writable layer.

You build an image once and run it as many containers as you need. When a container stops and is removed, its writable layer goes with it — the image is untouched. That immutability is a feature, not a limitation, and it’s why we’ll come back to state later.

Your first docker run

Enough theory. The fastest way to feel it is to run something. If you have Docker installed:

docker run hello-world

Docker looks for the hello-world image locally, doesn’t find it, pulls it from Docker Hub, and runs it. It prints a short message and exits. That one command exercised the whole pipeline: pull an image, create a container, run it.

Now something you can actually poke at — a web server:

docker run --rm -p 8080:80 nginx:1.27

Breaking that down, because I don’t like magic flags:

  • --rm — remove the container when it stops, so you don’t leave stopped containers lying around.
  • -p 8080:80 — map port 8080 on your host to port 80 inside the container. Left of the colon is your machine; right is inside.
  • nginx:1.27 — the image name and its tag. The tag pins the version. Reach for a real version tag, not latest, so you know what you’re running.

Open http://localhost:8080 and you’ll get the nginx welcome page. Stop it with Ctrl+C.

A few commands cover most of daily use:

docker ps              # list running containers
docker ps -a           # include stopped ones
docker images          # list images you have locally
docker logs <id>       # see a container's output
docker exec -it <id> sh   # get a shell inside a running container
docker stop <id>       # stop it
docker rm <id>         # remove a stopped container

docker exec -it <id> sh is the one you’ll lean on when something misbehaves — it drops you into the container so you can look around the way you would on any Linux box.

Building your own image

You describe an image with a Dockerfile. Here’s a minimal, realistic one for a Node service:

FROM node:20-alpine
WORKDIR /app

# Copy dependency manifests first so this layer caches
# and doesn't rebuild every time your source changes.
COPY package*.json ./
RUN npm ci --omit=dev

COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Build and run it:

docker build -t myapp:1.4.2 .
docker run --rm -p 3000:3000 myapp:1.4.2

The -t myapp:1.4.2 tags the image with a name and version. When you push to a registry, that becomes something like registry.example.com/myapp:1.4.2. The ordering in the Dockerfile — copying package*.json and installing before copying the rest of your source — is deliberate. Docker caches each layer, so keeping the parts that rarely change near the top means faster rebuilds. That’s a small habit that pays off every single build.

Where Docker stops and orchestration begins

Docker runs containers on one host. That’s the right scope for local development, CI jobs, and small single-box deployments.

The moment you want containers spread across many machines, restarted automatically when they die, load-balanced, and rolled out without downtime — that’s a different job, called orchestration. Kubernetes is the common answer; Docker Compose is a lighter option for running a few containers together on a single host.

The clean way to hold it in your head: Docker is how you build and run a container. Kubernetes is how you operate a fleet of them. You don’t need the second thing to get value from the first, and I’d argue most people should be comfortable with plain Docker before they go anywhere near a cluster.

If you want a worked-through setup for a real Docker environment rather than toy examples, the Docker stack toolkit on devopsaitoolkit.com walks through a sensible baseline.

Honest limits — where people get burned

Docker is a good tool, and good tools have edges. Two are worth stating plainly.

A container is not a security boundary by itself. Because containers share the host kernel, a kernel-level exploit or a careless --privileged flag can cross from container to host. Containers isolate processes; they don’t sandbox them the way a VM does. If you’re running untrusted code, don’t lean on the container alone — add real isolation. And treat a leaked root inside a container as a serious problem, not a contained one.

Containers are ephemeral, and that bites stateful workloads. Remember that writable layer that disappears when the container is removed? Any data written inside the container goes with it. For databases and anything else that needs to persist, you mount a volume so the data lives outside the container’s lifecycle:

docker run -d -v pgdata:/var/lib/postgresql/data postgres:16

Here pgdata is a named volume that survives container restarts and removals. Forgetting this is the classic beginner mistake — everything looks fine until a container is recreated and the data is gone. Plan for state explicitly.

When one of these edges does bite you and the error message isn’t obvious, the error guides on the blog are a decent place to look before you start guessing. And if you paste a failing container log into an AI assistant for a first hypothesis — a genuinely useful move — scrub any secrets, tokens, or connection strings out of it first.

Wrapping up

Docker isn’t sorcery. It’s a way to package your app with its environment and run it as an isolated process on a shared kernel — repeatable, portable, and fast to start. Learn the handful of commands above, understand image versus container, respect the two limits, and you have most of what daily use requires.

The durable lesson: an environment you can rebuild from a file is an environment you can trust. That’s the real win, and it’s the same discipline whether you’re running one container or ten thousand.

If you take one action, write a Dockerfile for a small project you already have and run it — the concept clicks the moment your own code comes up in a container.

Newsletter

Free: the DevOps AI Incident-Triage Cheat Sheet

Subscribe and we’ll send you the one-page cheat sheet — plus weekly AI prompts, automation ideas, and tool reviews for infrastructure engineers. One email a week. No spam, unsubscribe anytime.

  • AI Incident-Triage Cheat Sheet (PDF)
  • Access to 2,778 DevOps AI prompts
  • One practical workflow email per week
Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.