Step 1 — Verify the install
Open a fresh terminal — PowerShell on Windows, Terminal on macOS, your shell of choice on Linux — and run:
$ docker --version Docker version 27.2.0, build d5c9a4b $ docker run --rm hello-world
The first command prints the engine version. The second pulls a tiny test image, runs it, prints a welcome message, and exits. --rm tells Docker to clean up the container when it finishes. If the welcome message appears, your install is working.
If you see Cannot connect to the Docker daemon, the engine isn’t reachable. Check the whale icon — it should be steady, not animating. If it’s stuck, jump to troubleshooting.
Step 2 — Run a real web server
Let’s start an Nginx server in the background, mapped to port 8080 on your machine:
$ docker run -d --name web -p 8080:80 nginx:alpine 8e0f4a3c2c5d... $ curl -I http://localhost:8080 HTTP/1.1 200 OK Server: nginx/1.27.1
Open http://localhost:8080 in your browser and you’ll see the Nginx welcome page. The flag breakdown:
-d— run in the background (detached).--name web— assign a friendly name so you can refer to it later.-p 8080:80— map host port 8080 to the container’s port 80.nginx:alpine— the image to run, using the lighter Alpine variant.
Step 3 — Inspect, log, and exec
$ docker ps CONTAINER ID IMAGE COMMAND PORTS NAMES 8e0f4a3c2c5d nginx:alpine "nginx -g …" 0.0.0.0:8080->80/tcp web $ docker logs web --tail 5 $ docker exec -it web sh / # ls /usr/share/nginx/html 50x.html index.html / # exit
docker ps lists running containers, docker logs tails their stdout/stderr, and docker exec -it opens an interactive shell inside the container. The -it flag combo is shorthand for “interactive + allocate a TTY.”
The Docker Desktop dashboard exposes the same operations graphically — click a container to view logs, inspect environment variables, or open a shell in one click.
Step 4 — Stop and clean up
$ docker stop web && docker rm web $ docker ps -a # confirm it is gone
docker stop sends SIGTERM and gives the process 10 seconds to exit cleanly. docker rm removes the container record. The image itself stays cached so the next pull is instant.
Step 5 — Build your first image
Create a small project directory with a Dockerfile like this:
# Dockerfile FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev COPY . . EXPOSE 3000 CMD ["node", "server.js"]
Add a .dockerignore so big folders stay out of the build context:
# .dockerignore node_modules .git .next *.log
Build and run:
$ docker build -t myapp . $ docker run --rm -p 3000:3000 myapp
Hit localhost:3000 and you should see your app responding from inside the container.
The next 30 minutes
- Multi-service stacks. Add a
docker-compose.ymlwith your app + Postgres and rundocker compose up. The Compose Specification at compose-spec.io is the cross-tool reference. - Persistent volumes. Avoid bind-mounting database directories on macOS or Windows — use named volumes (
-v pgdata:/var/lib/postgresql/data) which live inside the VM and are dramatically faster. - Multi-arch builds.
docker buildx build --platform linux/amd64,linux/arm64 -t myapp .produces images for both architectures from a single command. - Local Kubernetes. Settings → Kubernetes → Enable Kubernetes. Wait a minute, then point
kubectlat thedocker-desktopcontext. - Stuck on something? Jump to troubleshooting.