Installing Docker and Running Your First Container
Get Docker onto macOS, Windows or Linux, prove it works, and run something real in one command — then look at what actually happened underneath.
Get Docker onto macOS, Windows or Linux, prove it works, and run something real in one command — then look at what actually happened underneath.
Knowing why containers exist is one thing; having them on your own machine is another, and this is the lesson where the idea becomes something you can type. By the end of it you will have Docker installed, you will have run a real web server without installing a web server, and you will know how to stop and clean up what you started.
Take the installation slowly. It is the one step in this course that differs between operating systems, and everything afterwards is identical no matter which one you are on.
Before downloading anything, it helps to know what the pieces are, because the error messages later refer to them by name.
There are two pieces, and they can fail independently. That is what makes the most common beginner error confusing until you know this.
The `docker` command you type.
It does almost nothing itself: it packages your request, sends it to the engine, and prints whatever comes back.
docker --version proves only this half exists.
A background service that does the work.
A daemon — always running, waiting to be told what to do. It builds images, starts containers and manages them.
docker info is the command that proves this half is
actually running.
On macOS and Windows you install Docker Desktop, which bundles the engine, the CLI, a small Linux virtual machine for the engine to live in, and a graphical dashboard. On Linux the engine runs natively and there is nothing to virtualise.
Download Docker Desktop from docker.com/products/docker-desktop
and pick the build that matches your chip — Apple silicon for M1
and later, Intel for older Macs. Choosing wrong is not dangerous;
the installer simply refuses.
Open the downloaded .dmg, drag Docker into Applications, then
launch it. The first start takes a minute while it creates its
virtual machine. You will know it is ready when the whale icon in
the menu bar stops animating.
If you use Homebrew, this does the same thing:
brew install --cask dockerYou still have to launch the Docker application once afterwards.
Installing the package does not start the engine, and until the
engine is running the docker command has nothing to talk to.
Docker on Windows runs your containers inside WSL 2, the Windows Subsystem for Linux — a real Linux kernel Microsoft ships with Windows. Containers are a Linux feature, so something has to provide the Linux part.
Install WSL 2 first, from PowerShell as Administrator:
wsl --installRestart when it asks. Then install Docker Desktop from
docker.com/products/docker-desktop, accepting the default option
to use the WSL 2 backend.
On Linux, install the engine itself; there is no Desktop layer to
add. Docker publishes its own repository, and using it matters:
the docker.io package in a distribution's own repositories is
often years behind.
For Ubuntu or Debian, the official convenience script is the shortest honest route:
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.shThen add yourself to the docker group so you do not need sudo
for every command:
sudo usermod -aG docker $USER
newgrp docker # apply the new group in this shellWhichever route you took, two commands confirm the result. Run the first:
docker --versionYou should see something like Docker version 27.3.1, build ce12230.
The exact numbers do not matter.
That only proves the CLI is installed. This one proves the daemon is running and reachable:
docker infoA working setup prints a long block describing the engine — how many containers exist, the storage driver, the kernel version. If instead you see this, the CLI is fine and the engine is not:
Cannot connect to the Docker daemon at
unix:///var/run/docker.sock. Is the docker daemon running?On macOS or Windows, launch Docker Desktop and wait for it to
finish starting. On Linux, start the service with
sudo systemctl start docker. This error will find you again on
some morning when Desktop did not auto-start, so it is worth
recognising on sight.
Now the interesting part. Run this:
docker run -p 8080:80 nginxOpen http://localhost:8080 in a browser and a real web server
answers you. Nothing was installed on your machine to make that
happen.
Read the output in your terminal, because it narrates exactly what Docker did:
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
a480a496ba95: Pull complete
f3ace1b8ce45: Pull complete
Digest: sha256:0c86dddac19f2ce4fd716ac58c0fd87bf69bfd4edbd...
Status: Downloaded newer image for nginx:latestLook for the image locally
None found — which is what
Unable to find image 'nginx:latest' locally means. Run the
same command again later and this step succeeds instead.
Pull it from Docker Hub
The default public registry. Those Pull complete lines are
the image's pieces arriving one at a time.
Create a container from the image
The image is not changed by this, and it is not running yet. The next lesson is about why that distinction matters.
Start it, and wire up the port
Your port 8080 is connected to port 80 inside the container, which is why the browser finds anything at all.
Your terminal is now showing the server's log output and will not return to a prompt. That is normal: the container is running in the foreground, and you are attached to it. Reload the page in your browser and watch a new line appear.
Press Ctrl + C to stop it.
That short line had three parts, and every container you ever start has the same shape:
docker run -p 8080:80 nginx
# ^^^^^^^^^^ ^^^^^
# options imageThe image comes last and says what to run. The options
come before it and say how to run it. -p 8080:80 publishes a
port: traffic arriving at 8080 on your machine is forwarded to 80
inside the container. The order is always host:container, which
is worth memorising now because getting it backwards produces a
page that never loads and no error at all.
Two more options you will use constantly:
docker run -d -p 8080:80 --name web nginx-d is short for detach: the container runs in the background and
you get your prompt back immediately. --name web gives it a name
you choose, instead of the cheerful random one Docker invents.
With -d the container is running invisibly. These are the
commands that make it visible again.
docker psCONTAINER ID IMAGE STATUS PORTS NAMES
c9a1f4e2b8d7 nginx Up 2 minutes 0.0.0.0:8080->80/tcp webYou get an id, the image it came from, how long it has been up, the port mapping and the name. Any command that takes a container can take either the name or the id — and with an id, the first few characters are enough.
docker logs web # what the container has printed
docker logs -f web # ...and keep following it live
docker stop web # ask it to shut down (SIGTERM)
docker start web # run it again
docker rm web # delete it permanentlyBad — starts a fresh container every time, and quietly accumulates them:
docker run -d -p 8080:80 nginx
docker run -d -p 8080:80 nginx # "port is already allocated"Good — restarts the container that already exists:
docker start webdocker run always creates something new. Reaching for it when you
meant "start that thing again" is how people end up with forty
stopped containers holding disk space, and how the second run fails
on a port the first one still owns.
Stopped containers keep their filesystem and their logs, which is useful for diagnosing a crash and wasteful once you are done.
docker ps -a # everything, including stopped
docker rm web # remove one
docker container prune # remove every stopped containerFor a throwaway container, --rm avoids the mess entirely by
deleting it the moment it exits:
docker run --rm -p 8080:80 nginx# Confirming the install
docker --version # CLI is installed
docker info # daemon is running and reachable
# Running
docker run nginx # run in the foreground
docker run -d nginx # run detached, in the background
docker run -p 8080:80 nginx # host port 8080 -> container 80
docker run --name web nginx # choose the container's name
docker run --rm nginx # delete it as soon as it exits
docker run -it ubuntu bash # interactive shell in a container
# Looking
docker ps # running containers
docker ps -a # all containers, running or not
docker logs web # a container's output
docker logs -f web # follow the output live
# Lifecycle
docker stop web # ask it to shut down
docker start web # start an existing container again
docker restart web # stop then start
docker rm web # delete a stopped container
docker container prune # delete every stopped containerYou have used the words image and container as though the difference were obvious, and it is not — it is the single idea that makes everything else in Docker click. The next lesson takes it apart: why an image is a recipe and a container is the meal, what a tag really points at, and why the file you edited inside a container vanished when it stopped.
Before moving on, run something other than nginx. Try
docker run -it python:3.12 python for a Python prompt on a
version you do not have installed, or
docker run -it ubuntu bash for a shell in a clean Ubuntu. Then
exit, and notice your own machine is untouched.