Support

Lesson 1 of 6 ·15 min read

Docker

Package an application with everything it needs, so it runs the same on every machine.

Fast answer: Docker packages an application together with everything it needs (code, libraries, dependencies, runtime) into a container. That container runs the same on your laptop, a test server, and production. The old complaint is "it works on my machine"; Docker's job is to make it work on every machine.

Why Docker exists

Imagine you built a Python app.

Your laptop                    Your teammate's laptop
Python 3.12                    Python 3.10
NumPy 2.0                      NumPy 1.26
Ubuntu                         Windows

The same code can fail on the second machine, not because the code is wrong, but because the environment is different. Multiply that by a test server and a production server and you have four environments that can each break in their own way.

Docker solves this by packaging your application, its Python version, its libraries, and its system dependencies into one unit. Everything then runs inside the same isolated environment, wherever that unit goes.

Containers vs virtual machines

People often assume Docker is a lightweight virtual machine. The difference matters.

VIRTUAL MACHINES                    DOCKER CONTAINERS

Computer                            Computer
├── Operating system                ├── Operating system
├── Hypervisor                      ├── Docker Engine
├── VM 1                            ├── Container 1
│   ├── Guest OS  (gigabytes)       │   └── App        (megabytes)
│   └── App                         ├── Container 2
└── VM 2                            │   └── App
    ├── Guest OS  (gigabytes)       └── Container 3
    └── App                             └── App

Every VM carries an entire operating system, which makes VMs comparatively heavy, slower to start, and large. Containers share the host machine's OS kernel, so they are usually smaller, start faster, and are easier to move around. That efficiency is a major reason containers became a standard deployment unit. (It also has a security consequence, covered in Lesson 4.)

Images, containers, and Dockerfiles

Nearly everything in Docker reduces to three things.

An image is a blueprint. Think of a recipe: Python 3.12, plus Flask, plus your code, written down as layers. You never run an image directly; you create containers from it.

A container is a running instance of an image. The relationship is the one you already know from Python:

Image        Class
  ↓            ↓
Container    Object

One image can start many identical containers, the way one class can create many objects.

A Dockerfile tells Docker how to build an image. Here is a complete one for a small Python app:

Dockerfile
FROM python:3.12

WORKDIR /app

COPY . .

RUN pip install -r requirements.txt

CMD ["python", "app.py"]

Reading it top to bottom: start from the official Python image, work inside /app, copy the project in, install the dependencies, and declare what to run when a container starts. Five lines, and the environment problem from the top of this lesson is gone.

The fourth name you will meet is Docker Hub: GitHub, but for images. Official images for python, postgres, redis, nginx, and thousands more, ready to pull down with one command.

Your first container

Install Docker Desktop (Windows or macOS) or Docker Engine (Linux), then confirm it is alive:

Terminal
docker --version
docker run hello-world

That second command quietly performs the entire Docker lifecycle:

download image → create container → run it → print message → exit

For something more interesting, run Ubuntu interactively:

Terminal
docker run -it ubuntu bash

You are now inside a Linux shell, inside a container, on your own machine. Try ls, pwd, whoami. Type exit to leave. (Plain docker run ubuntu without -it exits immediately: the container has nothing long-running to do, so it stops. Containers live exactly as long as the process inside them.)

Try it: watch a container's lifecycle

  1. Run docker run hello-world.
  2. Run docker ps. The container is absent because its process has finished.
  3. Run docker ps -a. The stopped container is still recorded.

Say what happened: Docker created a container from an image, ran its one process, and stopped the container when that process ended. If that sentence makes sense, you understand the container lifecycle.

The commands that matter

CommandWhat it does
docker psList running containers
docker ps -aList all containers, including stopped ones
docker imagesList images on this machine
docker stop <id> / docker start <id>Stop or restart a container
docker rm <id>Remove a container
docker rmi <name>Remove an image
docker logs <id>See what a container printed

Ports: reaching what runs inside

A container has its own isolated network view. If your app listens on port 5000 inside the container, your browser cannot see it until you map a port on your machine to it:

Terminal
docker run -p 8000:5000 myapp
Browser → localhost:8000 → container → 5000

The number on the left is your machine's port; the number on the right is the container's. This one flag explains most "the container is running but I can't reach it" moments.

Building and running your own image

With app.py, requirements.txt, and the Dockerfile above in one folder:

Terminal
docker build -t myapp .
docker run -p 8000:5000 myapp

-t myapp names the image; the . says "build from this folder". Every teammate who runs these two commands gets the identical environment, whatever is installed on their machine. When you want the full line-by-line treatment for a real API, including layer caching and .dockerignore, the Dockerizing a Python API guide is the hands-on version of this section.

Volumes: data that outlives the container

Containers are disposable. Delete one and its files go with it. That is fine for the application (the image can recreate it in seconds) and catastrophic for a database. A volume stores data outside the container's lifecycle:

Terminal
docker run -v mydata:/data postgres

The container can now be stopped, deleted, and recreated, and /data survives. Databases, uploads, and anything else you cannot regenerate belong in volumes (or, in real production, in a managed database outside the container entirely).

Configuration: the image stays generic

Never hardcode secrets or environment-specific settings into the code you package. Pass them in when the container starts:

Terminal
docker run -e DB_PASSWORD=secret myapp
Python
import os

password = os.getenv("DB_PASSWORD")

The same image then runs in development, testing, and production, and only the configuration changes. This is the same .env discipline from the API keys guide, extended to containers. Why environment variables are a delivery mechanism rather than a security feature is Lesson 4's territory.

If images and containers still blur together

Close the terminal and imagine a stamped metal mould. The image is the mould: fixed and reusable. Each container is one object made from it: separate, temporary, and able to be started or discarded without changing the mould. A Dockerfile is the set of instructions used to make the mould.

What Docker does not do

Docker solves repeatability. It does not give you a public domain, HTTPS, automatic restarts, monitoring, backups, or a safe release process. Those belong to the platform and pipeline around the container, which is exactly where the rest of this course goes: multiple containers talking to each other (Lesson 2), moving images to servers (Lesson 3), and running containers at scale (Lesson 5).

Check your understanding

Answer before opening each explanation. The hint points you back to the governing idea without giving the answer away.

What is the difference between an image and a container, and which one does docker build produce?

Hint: Which one is the blueprint and which one is running?

Answer: An image is the reusable, read-only blueprint. A container is a running instance created from that image. docker build reads a Dockerfile and produces an image; docker run creates and starts a container from it.

Your container runs but localhost:8000 shows nothing. Which flag did you probably forget?

Hint: The browser needs a route from a host port to a container port.

Answer: The port mapping, such as -p 8000:5000. The left number is the port on your machine; the right number is the port where the application listens inside the container.

Why does docker run ubuntu exit while docker run -it ubuntu bash stays open?

Hint: A container's lifetime follows one thing.

Answer: A container lives only while its main process is running. Plain Ubuntu has no long-running command here, so it finishes. The second command starts an interactive Bash process, which remains alive until you exit it.

Why should PostgreSQL data not live only inside its container?

Hint: Containers are meant to be replaceable.

Answer: Removing the container would remove its writable filesystem and therefore the database files. Store them in a named volume (or use a managed database) so the data survives replacement of the application container.