Home / Glossary / Container (Docker)
Tech & ITContainer (Docker)
By Sitraka Forler · Lecturer, Durham Business SchoolUpdated 3 October 2026 About this site
An isolated, portable bundle of an app and everything it needs to run.
Also known as: containerisation.
A container runs an app with all it needs as an isolated process sharing the host kernel (the operating system's core), so it works the same on a laptop and in production. With Docker, you build an image (a read-only template) from a Dockerfile; each container is a runnable instance of that image. Unlike a virtual machine (VM), a container has no kernel of its own: on a Mac, Docker Desktop runs one in a Linux VM.
The intuition
A Python script that runs on your laptop depends on far more than its own lines: one interpreter version, one pandas version, the NumPy build underneath it, even system libraries you never chose. Move it to a colleague's machine or a server and any of those can differ, which is the story behind "it works on my machine". A container settles the matter by carrying its own files. The image holds everything the program sees when it runs, from the operating system's files up to your code, frozen as a read-only template; a container is one instance started from that image, with a thin scratch area on top for whatever it writes. Start ten containers from one image and you get ten isolated processes sharing one set of read-only files. The difference from a virtual machine is the kernel: a VM boots a complete guest operating system on simulated hardware, while a container is an ordinary process that the host's own kernel fences off.
You describe an image in a Dockerfile, a text file of instructions read top to bottom: FROM picks a base image, COPY brings files in, RUN executes a command at build time, and CMD records what to start when a container runs. Each instruction that changes files leaves a layer, an immutable set of file changes, and a union filesystem stacks those layers into the one directory tree the process sees, with the container's writable layer on top. Layers double as the build cache: an instruction whose inputs are unchanged reuses its stored layer, and once one layer changes, every layer after it is rebuilt. The isolation rests on two Linux kernel features. Namespaces give the process its own view of process IDs, mount points, network interfaces and hostname, so the command a container starts runs as process 1 and sees the image's files instead of the host's. Control groups, or cgroups, meter and cap its CPU and memory; by default a container has no resource constraints at all.
Sharing the kernel has consequences. A Linux container needs a Linux kernel, so on a Mac or Windows laptop Docker Desktop runs your containers inside a lightweight Linux VM (on Windows, usually through WSL 2), and an image built on an Apple Silicon Mac targets ARM processors unless you ask for another platform. Isolation is weaker than a VM's, because a flaw in the shared kernel can expose every container on the host, and the process runs as root unless the image or the run command names another user. Whatever a container writes to its own filesystem disappears when it is removed, so results belong in a mounted folder or a volume. Data analysts meet containers sooner than they expect: a repository that ships a Dockerfile, a dev container in VS Code or GitHub Codespaces, a scheduled job running on Kubernetes, or a throwaway PostgreSQL database started with one docker run. Practitioners pin the base image and every package, install dependencies before copying the code, and keep data out of the image.
Example
# pull the image if needed, then run a container from it docker run --rm hello-world # build an image from the Dockerfile in this folder docker build -t my-app . # start a container from your image docker run --rm my-app # list running containers docker ps # list images stored locally docker images
Package a pandas script as an image, run it, change it and feed it new data
- The project. A folder called trade-report holds three files. requirements.txt has one line, pandas==2.3.3. trades.csv holds a header and three trades: trade_id,quantity,price T-1001,10,101.5 T-1002,4,250.0 T-1003,25,98.4 report.py reads it and prints a summary: import platform import pandas as pd df = pd.read_csv("trades.csv") df["notional"] = df["quantity"] * df["price"] print(f"Running on {platform.system()} with pandas {pd.__version__}") print(df.to_string(index=False)) print(f"Total notional: {df['notional'].sum():.2f}")
- The Dockerfile, saved as a file named Dockerfile in the same folder: FROM python:3.13-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY report.py trades.csv ./ CMD ["python", "report.py"] FROM starts from the official Python 3.13 image in its slim Debian variant. WORKDIR creates /app and makes it the current directory. The first COPY brings in the requirements file alone, and RUN installs pandas plus the five packages it pulls in (numpy, python-dateutil, pytz, six and tzdata), so 1 + 5 = 6 packages. The second COPY adds the script and the data, and CMD records the command a container will start. Copying requirements.txt on its own, before the code, is deliberate, as step 6 shows.
- Build it: docker build -t trade-report:1.0 . where the final dot is the build context, the folder whose files COPY is allowed to read. BuildKit, Docker's default builder, prints one numbered line per step; trimmed of timings and of the internal lines that load the Dockerfile and the context, the output reads: => [1/5] FROM docker.io/library/python:3.13-slim@sha256:... => [2/5] WORKDIR /app => [3/5] COPY requirements.txt . => [4/5] RUN pip install --no-cache-dir -r requirements.txt => [5/5] COPY report.py trades.csv ./ => exporting to image => => naming to docker.io/library/trade-report:1.0 Six instructions give 6 - 1 = 5 numbered steps, because CMD only sets the image's configuration and changes no files. The sha256 digest on the FROM line records exactly which build of python:3.13-slim the tag pointed to on the day you built.
- Run it: docker run --rm trade-report:1.0. Docker creates a container from the image, starts its CMD, and --rm deletes the container as soon as the script exits. The output: Running on Linux with pandas 2.3.3 trade_id quantity price notional T-1001 10 101.5 1015.0 T-1002 4 250.0 1000.0 T-1003 25 98.4 2460.0 Total notional: 4475.00 Check the arithmetic: 10 × 101.5 = 1015.0, 4 × 250.0 = 1000.0 and 25 × 98.4 = 2460.0, then 1015.0 + 1000.0 + 2460.0 = 4475.0. The pandas version is the one pinned in requirements.txt, whatever is or is not installed on your laptop.
- One image, many containers. Run docker run trade-report:1.0 twice without --rm, then docker ps -a --filter ancestor=trade-report:1.0. It lists 2 containers, each with its own generated name and a status beginning Exited (0), the script's exit code, both created from the single trade-report:1.0 image. Each got its own writable layer over the same read-only layers, so the second run added one thin layer rather than a second copy of Python and pandas. docker rm followed by the two names deletes the containers and leaves the image untouched.
- Change the code and rebuild. Append one line to report.py, print(f"Trades: {len(df)}"), and run docker build -t trade-report:1.0 . again. Steps [2/5], [3/5] and [4/5] come back marked CACHED, so pip does not run at all, and only [5/5] COPY report.py trades.csv ./ executes, because it is the first instruction whose input changed. The next docker run --rm trade-report:1.0 ends with Trades: 3. Had the Dockerfile used a single COPY . . before the pip line, the same one-line edit would have changed that first COPY layer, and since every layer after a changed one is rebuilt, all 6 packages would be reinstalled on every edit.
- See the shared kernel. On a Mac, uname -s in the terminal prints Darwin. docker run --rm trade-report:1.0 uname -s replaces the CMD for this one run and prints Linux, which is also why the report's first line says Running on Linux: the container is a process on the kernel of the Linux VM that Docker Desktop manages, not a process on macOS. On a Linux machine running Docker Engine there is no VM in between, so uname -r inside any container prints the same kernel release as uname -r on the host.
- Feed it new data without rebuilding. Add a fourth line, T-1004,8,120.0, to trades.csv on your laptop. docker run --rm trade-report:1.0 still reports 4475.00, because the image holds the copy taken at build time. Mount the live file over it instead: docker run --rm -v "$PWD/trades.csv:/app/trades.csv:ro" trade-report:1.0. The mount hides the file baked into the image, :ro makes it read-only inside the container, and the output gains the row T-1004 8 120.0 960.0 and ends with Total notional: 5435.00 and Trades: 4. Check: 8 × 120.0 = 960.0 and 4475.0 + 960.0 = 5435.0.
The image freezes the interpreter, the packages and the code into layers you can rebuild and share, while each container is a disposable process started from it, so pin what goes in, install dependencies before copying code, and pass data in and results out through a mount.
Common pitfalls
- Keeping results inside the container. Files a container writes to its own filesystem land in its writable layer, which is deleted with the container, as soon as it exits if you started it with --rm. Write outputs to a bind-mounted folder such as -v "$PWD/out:/app/out" or to a named volume, and treat every container as disposable.
- Trusting a tag to stay still. python:3.13-slim moves to each new 3.13 patch release and each rebuilt base image, and a requirements.txt that pins pandas alone leaves numpy and the other four dependencies free to change, so an image rebuilt months later can differ from the one you validated. Pin the full dependency list, for example from pip freeze or a lockfile, and pin the base image to a digest (python:3.13-slim@sha256:...) when the result has to be reproducible.
- Copying the whole folder into the image. COPY . . with no .dockerignore file sweeps in .env files, credentials, notebooks and raw client data, and a file deleted by a later RUN instruction still sits in the earlier layer that added it, where anyone who pulls the image can extract it. List exclusions in .dockerignore, copy only what the program needs, and mount data at run time rather than baking it in.
- Building on an Apple Silicon Mac for an x86 server. The image comes out as linux/arm64 by default, and a linux/amd64 server cannot run it without emulation: the container typically fails at once with an exec format error. When the target is not your own machine, build with docker build --platform linux/amd64, or produce a multi-platform image.
- Installing Docker Desktop at work without checking the licence. Docker states that Desktop is free for personal use, education, non-commercial open source projects and small businesses with fewer than 250 employees and less than $10 million in annual revenue, while professional use in larger organisations, and use by government entities, requires a paid subscription. Ask IT which setup is approved before you install it on a company laptop.
Frequently asked questions
What is a container in simple terms?
A container is a program packaged with every file it needs (the language runtime, the libraries and the operating system files), then run as an isolated process on the host's kernel. Because it carries its own files, it behaves the same on a laptop, a colleague's machine and a server, provided they share its processor architecture. Docker is the best-known tool for building and running containers, which is why people often say a Docker container.
What is the difference between Docker and a virtual machine?
A virtual machine runs a complete guest operating system, with its own kernel, on virtual hardware provided by a hypervisor. A Docker container brings no kernel: it is a process on the host's kernel, isolated with namespaces and limited with cgroups. That makes containers lighter and quicker to start, while a VM gives stronger isolation and can run a different operating system from its host. The two are often combined, since many containers run inside cloud VMs and Docker Desktop itself runs a Linux VM on a Mac or Windows machine.
What is the difference between a Docker image and a container?
An image is a read-only template built from a Dockerfile, made of stacked layers that hold the files and settings. A container is a running, or stopped, instance of that image with a thin writable layer on top. One image can back many containers at once, and removing a container deletes whatever it wrote to that writable layer but leaves the image untouched.
What is a Dockerfile?
A Dockerfile is a plain text file of instructions that docker build follows, top to bottom, to produce an image. FROM names the base image, COPY adds files from your project, RUN executes commands at build time such as pip install, and CMD sets the default command a container runs. Because the file sits in the repository beside the code, the environment is versioned and reviewed like everything else.
Why would a data analyst or data scientist use Docker?
To make an analysis run the same way somewhere else: as a scheduled job on a server, on a colleague's laptop, or for a reviewer rerunning your results a year later. Fixing the Python version and every package inside an image removes the "works on my machine" problem. Analysts also start local databases such as PostgreSQL in a container for testing, and meet containers in dev containers, which open a ready-configured project in VS Code or GitHub Codespaces.
What is the difference between Docker and Kubernetes?
Docker builds images and runs containers on one machine. Kubernetes is an orchestrator: it schedules containers across a cluster of machines, restarts them when they fail and scales them up or down. Modern Kubernetes clusters usually run containers through a runtime such as containerd rather than through Docker Engine, but images built with docker build still run there, because Docker produces standard Open Container Initiative (OCI) images that any compliant runtime can run.