Learn Docker from Scratch
This tutorial covers every Docker concept — from the basics of operating systems to running multi-container applications in production. No prior experience needed.
1. Prerequisites
Before we dive into Docker, let's make sure you understand some fundamental computing concepts. Even if you're a complete beginner, this section will get you up to speed.
What is an Operating System (OS)?
Think of your computer as a house. The hardware (CPU, RAM, disk) is the house's structure. The Operating System is like the building management — it controls who can use the electricity, water (resources), and ensures everything runs smoothly.
An OS is software that sits between your hardware and your applications. Examples: Linux macOS Windows
What is a Process?
When you open a program (like Chrome or a Python script), the OS creates a process — a running instance of that program. Each process gets its own allocated memory and CPU time. If you open two Chrome windows, those are two processes.
What is a Network Port?
Imagine your computer is a large apartment building. Its IP address is the building's street address. A port is the apartment number — it tells incoming data which specific application to reach.
There are 65,535 possible ports. Common examples:
What is a Server?
A server is simply a program (or machine) that provides services to other programs (called clients). When you visit google.com, your browser (client) sends a request; Google's server responds with the webpage. A server is always listening on a port for incoming requests.
What are Environment Variables?
Environment variables are key-value pairs stored in the OS environment that applications can read. They let you configure applications without changing their code — for example, setting a database password or API key.
# Setting an environment variable (Linux/macOS)
export DATABASE_URL="postgres://localhost:5432/mydb"
export PORT=3000
# Reading it in a program (Python example)
import os
db = os.getenv("DATABASE_URL") # reads the value
Docker uses environment variables heavily to configure containers at runtime without rebuilding images. Understanding them now will make later sections much easier.
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
2. What is Docker?
Docker solves one of software's oldest headaches — "it works on my machine." Let's understand the problem and how Docker elegantly fixes it.
The Problem Docker Solves
Imagine you build a web app on your laptop. It uses Python 3.11, a specific version of a database driver, and a particular Linux library. Everything works perfectly.
Then you hand it to a colleague. Their machine has Python 3.9, a different library version — and nothing works. You've encountered the classic "works on my machine" problem.
Docker's Solution: The Shipping Container Analogy
Before the 1960s, shipping goods internationally was chaotic. Different ships had different-sized cargo holds, and loading/unloading was manual and slow. Then the standardized shipping container was invented — a uniform box that fits on any ship, truck, or train, regardless of what's inside.
Docker does the same thing for software. A Docker container packages your application and all its dependencies into a single, portable unit that runs identically everywhere.
Key Facts About Docker
What Can You Do With Docker?
- Package an application with all its dependencies
- Run the same app consistently across dev, test, and prod environments
- Spin up a database with one command (no installation needed)
- Run multiple isolated services on a single machine
- Scale applications horizontally with ease
- Enable microservices architecture
The Docker logo is a whale (named Moby Dock) carrying containers on its back — a perfect visual metaphor for the shipping container concept!
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
3. Containers vs Virtual Machines
Before Docker, the standard way to isolate applications was with Virtual Machines. Understanding the difference helps you appreciate why containers are so revolutionary.
Virtual Machines (VMs) Explained
A Virtual Machine is a software emulation of a complete computer. It uses a program called a hypervisor (like VMware, VirtualBox, or Hyper-V) to simulate hardware. Inside the VM, a full OS boots up — just like on a physical computer.
Think of it like building an entire house inside another house. Each VM has its own walls, plumbing, electricity — completely separate from the host, but wasteful of space and resources.
Containers Explained
A container is much lighter. Instead of running a full OS, it shares the host's kernel and uses Linux namespaces to create an isolated environment. Think of it like renting a single room — you share the building's infrastructure (plumbing, electricity/kernel) but your room is private and isolated.
Side-by-Side Comparison
| Feature | Virtual Machine | Docker Container |
|---|---|---|
| Startup time | Minutes (boots full OS) | Seconds (or milliseconds) |
| Size | GBs (includes OS) | MBs (just app + libs) |
| Isolation level | Strong (hardware-level) | Good (kernel-level) |
| Performance overhead | High (hypervisor layer) | Near-native |
| OS compatibility | Can run any OS (e.g., Windows on Linux) | Must match host kernel type |
| Resource usage | High (each VM reserves RAM/CPU) | Low (shares host resources) |
| Portability | Moderate | Excellent — runs identically anywhere |
| Use case | Full OS isolation, run Windows on Linux | Microservices, CI/CD, dev environments |
How Linux Namespaces Enable Isolation
Linux namespaces are the magic behind containers. They give each container its own isolated view of the system:
How cgroups Control Resources
cgroups (control groups) let Docker limit and monitor how much CPU, memory, disk I/O, and network a container can use. This prevents one container from consuming all system resources and starving others.
# Limit a container to 512MB RAM and 50% of 1 CPU core
docker run --memory="512m" --cpus="0.5" nginx
# This uses cgroups under the hood to enforce these limits
Choose VMs when: you need to run Windows containers on Linux, require maximum security isolation (e.g., untrusted code), or need to run a completely different kernel. For most web applications and microservices, containers are the right choice.
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
--memory, --cpus, and other resource limit flags on containers.4. Installing Docker
Let's get Docker running on your machine. The easiest way for most developers is Docker Desktop, which bundles everything you need.
Docker Desktop (Mac, Windows, Linux)
Docker Desktop is a GUI application that includes Docker Engine, Docker CLI, Docker Compose, and more. It's the recommended way to run Docker on a developer machine.
Docker Desktop is free for personal use, education, and small businesses (<250 employees, <$10M revenue). Larger companies need a paid subscription.
Installation on macOS
- Go to docker.com/products/docker-desktop and download the macOS installer (choose Intel or Apple Silicon)
- Open the downloaded
.dmgfile and drag Docker to your Applications folder - Launch Docker from Applications — a whale icon appears in the menu bar
- Wait for Docker to start (the whale stops animating)
- Open Terminal and verify:
docker version
Installation on Linux (Ubuntu/Debian)
# Step 1: Update packages and install prerequisites
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
# Step 2: Add Docker's official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
# Step 3: Set up the repository
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Step 4: Install Docker Engine
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Step 5: Run Docker without sudo (optional but recommended)
sudo usermod -aG docker $USER
newgrp docker
Installation on Windows
- Enable WSL 2 (Windows Subsystem for Linux) — Docker Desktop uses it for Linux containers
- Download Docker Desktop installer from docker.com
- Run the installer and follow the prompts
- Restart your computer when prompted
- Launch Docker Desktop from the Start menu
Verifying the Installation
# Check Docker version (client and server info)
docker version
# Output shows Client version, Server (daemon) version, API version, etc.
# Check Docker system info (resources, containers, images count)
docker info
# Output shows: containers running/stopped, images, storage driver, OS, CPUs, memory
# The classic Hello World test
docker run hello-world
# Docker pulls a tiny test image, runs it, prints a success message, then exits
The docker run hello-world command does several things automatically:
- Checks if the
hello-worldimage is locally available - Doesn't find it, so pulls it from Docker Hub (the default registry)
- Creates a container from the image
- Runs the container — it prints a message and exits
Understanding: docker version vs docker info
| Command | What it shows |
|---|---|
docker version | Client version, Server (daemon) version, API version, Go version, OS/Arch |
docker info | Number of containers/images, storage driver, cgroup driver, OS, memory, CPUs, registry URL |
Show Answer & Explanation
Show Answer & Explanation
docker version shows both the Client version and Server (daemon) version. If the daemon is not running, it will show "Cannot connect to the Docker daemon" — a clear sign something is wrong. docker info also works but provides much more verbose output.Show Answer & Explanation
dockerd, managed by systemd. You can check its status with sudo systemctl status docker and start it with sudo systemctl start docker. It automatically starts on boot after installation.Show Answer & Explanation
dockerd daemon, a REST API, and the CLI. When you run docker run, the CLI sends a request to the daemon via the API, and the daemon does the actual work of creating/running the container.Show Answer & Explanation
docker ps (ps = process status) shows only running containers by default. To see all containers including stopped ones, use docker ps -a. The output includes Container ID, Image, Command, Created time, Status, Ports, and Name.5. Docker Architecture
Understanding Docker's architecture helps you know what's happening behind the scenes every time you type a Docker command.
The Three Main Components
Docker uses a client-server architecture. The three main components are:
docker run, the client sends that request to the daemon.How a docker run Command Works Step by Step
- You type
docker run nginxin the terminal - The Docker client parses the command and sends a REST API request to the Docker daemon via the Unix socket (
/var/run/docker.sock) - The daemon checks if the
nginximage exists locally - Image not found → daemon sends a pull request to Docker Hub (registry)
- The image layers are downloaded and stored locally
- The daemon creates a new container from the image
- The container starts running — nginx is now serving requests
The Docker Socket
The Docker daemon listens on a Unix socket at /var/run/docker.sock. This is how the CLI communicates with it. When you mount this socket into a container, that container can control Docker on the host — a powerful but security-sensitive pattern.
# See all Docker objects and system info
docker info
# The daemon is a systemd service on Linux
sudo systemctl status docker
# You can also communicate via TCP (for remote Docker)
# DOCKER_HOST=tcp://192.168.1.10:2376 docker ps
Show Answer & Explanation
dockerd) is the server component that runs in the background. It manages all Docker objects (images, containers, networks, volumes) and listens for Docker API requests from clients. The client sends commands; the daemon executes them.Show Answer & Explanation
/var/run/docker.sock. For remote Docker, TCP (with TLS for security) is used. This clean API design means you can also control Docker programmatically from any language.Show Answer & Explanation
docker pull nginx, Docker Hub is the default source.docker pull ubuntu, which component actually fetches the image?Show Answer & Explanation
Show Answer & Explanation
/var/run/docker.sock is the Unix domain socket that the Docker daemon listens on by default. The Docker CLI connects to this socket to send API requests. Mounting this socket into a container (e.g., in CI/CD pipelines) allows that container to control the host Docker daemon.Show Answer & Explanation
Show Answer & Explanation
registry:2 image).docker run ubuntu and the image is not found locally, what does Docker automatically do?Show Answer & Explanation
6. Docker Images
A Docker image is like a recipe or blueprint. It contains everything needed to run an application — code, runtime, libraries, config files — but it's not running yet.
What is a Docker Image?
Think of a Docker image as a snapshot of a filesystem. It's read-only — you can't modify it. When you want to run it, Docker creates a container from it (like making a cake from a recipe — the recipe doesn't change, but each cake is a new instance).
Images are described by a name and a tag: name:tag — e.g., nginx:1.25 or python:3.11-slim.
The Layer System
Docker images are composed of read-only layers stacked on top of each other. Each layer represents a change: adding files, installing software, modifying config. This system is very efficient — if two images share common base layers, those layers are stored only once on disk.
Essential Image Commands
Pulling an Image
# Pull the latest nginx image from Docker Hub
docker pull nginx
# Docker pulls it as: nginx:latest (default tag)
# Pull a specific version (tag)
docker pull nginx:1.25-alpine
# alpine = a very small Linux distro, great for small images
# Pull an image from a specific registry (not Docker Hub)
docker pull gcr.io/google-containers/pause:3.9
How it works: Docker checks if you have the image locally. If not, it contacts the registry, downloads each layer individually (in parallel), and assembles the image locally. Downloaded layers are cached — if another image shares a layer, it won't be re-downloaded.
Listing Images
# List all locally available images
docker images
# or equivalently:
docker image ls
# Output columns:
# REPOSITORY TAG IMAGE ID CREATED SIZE
# nginx latest a6bd71f48f68 2 weeks ago 187MB
# ubuntu 22.04 174c8c134b2a 5 weeks ago 77.8MB
# Show all images including intermediate layers
docker images -a
# Filter images by name
docker images nginx
Inspecting an Image
# Get detailed JSON metadata about an image
docker inspect nginx
# This shows: layers, environment variables, exposed ports,
# architecture, OS, creation date, entrypoint, etc.
# Show image layer history
docker history nginx
# Output shows each layer, its size, and the command that created it
Removing Images
# Remove a specific image
docker rmi nginx
# or:
docker image rm nginx
# Force remove (even if containers use it)
docker rmi -f nginx
# Remove all unused images (not used by any container)
docker image prune
# Remove ALL images (careful!)
docker image prune -a
Image Tags Explained
Tags are labels attached to images to distinguish versions. Format: repository:tag
The :latest tag is the default when no tag is specified, but it changes over time. In production, always pin to a specific version tag like nginx:1.25.3 to ensure reproducible deployments.
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
docker pull explicitly downloads an image from a registry. Note that docker run also pulls automatically if the image isn't local. docker pull is useful when you want to pre-download images or update to the latest version of a tagged image.docker images (or docker image ls) display?Show Answer & Explanation
docker images lists all images in your local Docker image cache, showing REPOSITORY, TAG, IMAGE ID, CREATED, and SIZE. The Image ID is a SHA256 hash (truncated). The same image can have multiple tags pointing to it.Show Answer & Explanation
python:3.11 and python:3.9 are different tags of the Python image. Tags like -slim or -alpine indicate size variants. Multiple tags can point to the same underlying image ID.docker pull nginx without specifying a tag?Show Answer & Explanation
:latest. So docker pull nginx is identical to docker pull nginx:latest. Be careful: :latest just means "the image tagged 'latest'" — it doesn't always mean the newest version. Image maintainers choose what latest points to.Show Answer & Explanation
docker rmi (remove image) deletes a local image. The equivalent modern syntax is docker image rm. If a container (even stopped) is using the image, Docker will refuse to remove it — use -f to force. To remove all unused images, use docker image prune -a.docker inspect nginx command output?Show Answer & Explanation
docker inspect returns a JSON object with all metadata: the image's layers (RootFS), environment variables, exposed ports, entrypoint, labels, architecture, OS, creation timestamp, and more. It works on both images and containers.Show Answer & Explanation
ubuntu, debian, alpine, and scratch. When you write FROM ubuntu:22.04 in a Dockerfile, ubuntu:22.04 is your base image.scratch image in Docker is:Show Answer & Explanation
scratch is a special reserved name in Docker — it's a completely empty image with no OS, no shell, no files. It's used for building ultra-minimal images, typically with statically compiled Go binaries that don't need any OS libraries. It results in the smallest possible images.7. Docker Containers
A container is a running instance of an image. This is where your application actually lives and executes. Let's master all the essential container operations.
Container Lifecycle
Running a Container: docker run
docker run is the most important Docker command. It creates and starts a container in one step.
# Basic: run nginx web server
docker run nginx
# Pulls nginx:latest if not local, creates & starts a container
# (runs in FOREGROUND — use Ctrl+C to stop)
# Run in detached (background) mode with -d
docker run -d nginx
# Returns the container ID immediately, runs in background
# Run with a custom name
docker run -d --name my-nginx nginx
# Map host port 8080 to container port 80
docker run -d -p 8080:80 --name web nginx
# Now visit http://localhost:8080 to see nginx
# Set environment variables
docker run -d -e MYSQL_ROOT_PASSWORD=secret --name db mysql:8
# Run an interactive terminal (e.g., explore Ubuntu)
docker run -it ubuntu bash
# -i = interactive (keep stdin open)
# -t = allocate a pseudo-TTY (terminal)
# bash = command to run inside the container
Anatomy of docker run -d -p 8080:80 --name web nginx
Managing Running Containers
# List running containers
docker ps
# List ALL containers (including stopped)
docker ps -a
# Stop a running container (graceful - sends SIGTERM, waits 10s, then SIGKILL)
docker stop web
# Forcefully kill a container immediately (sends SIGKILL)
docker kill web
# Start a stopped container
docker start web
# Restart a container
docker restart web
# Remove a stopped container
docker rm web
# Remove a running container (force)
docker rm -f web
# Remove ALL stopped containers
docker container prune
Interacting with Running Containers
# Open a shell inside a running container
docker exec -it web bash
# -i = interactive, -t = terminal, bash = shell command
# Run a single command inside a container
docker exec web ls /etc/nginx/
# View container logs (stdout + stderr)
docker logs web
# Follow logs in real-time (like tail -f)
docker logs -f web
# Show last 50 lines
docker logs --tail 50 web
# View real-time resource usage (CPU, memory, network, disk)
docker stats
# View container processes
docker top web
# Copy files from container to host
docker cp web:/etc/nginx/nginx.conf ./nginx.conf
# Copy files from host to container
docker cp ./nginx.conf web:/etc/nginx/nginx.conf
Any files written inside a container are lost when it's removed (docker rm). To persist data, use Volumes (Section 9). This is by design — containers are meant to be stateless and disposable.
Show Answer & Explanation
-d flag do in docker run -d nginx?Show Answer & Explanation
-d (detached) flag runs the container in the background. The command returns immediately with the container ID, and the container keeps running. Without -d, the container runs in the foreground and you see its output directly — pressing Ctrl+C stops it. To auto-delete on stop, use --rm.docker run -p 8080:80 nginx, what does -p 8080:80 mean?Show Answer & Explanation
host_port:container_port. So -p 8080:80 means: map your machine's port 8080 to the container's port 80. When you visit localhost:8080 in your browser, Docker forwards that traffic to nginx listening on port 80 inside the container.docker stop and docker kill?Show Answer & Explanation
docker stop is graceful: it sends SIGTERM (please shut down cleanly), waits 10 seconds for the app to save state and exit, then sends SIGKILL. docker kill immediately sends SIGKILL (force quit) with no grace period. Use stop for production workloads.docker ps -a display?Show Answer & Explanation
docker ps by default only shows running containers. The -a (all) flag includes stopped, exited, and created containers. This is essential for debugging — if a container exited unexpectedly, you need -a to see it and then docker logs to see why it crashed.docker exec -it mycontainer bash do?Show Answer & Explanation
docker exec runs a command in an EXISTING running container. The -it flags provide an interactive terminal. This is incredibly useful for debugging — you can explore the container's filesystem, check logs, test commands, etc., without stopping the container.docker rm?Show Answer & Explanation
--name flag in docker run is used to:Show Answer & Explanation
--name, Docker generates a random name (like "jovial_einstein" or "hardcore_tesla"). Using --name lets you use a meaningful name in subsequent commands: docker stop myapp, docker logs myapp instead of using the container ID.docker logs mycontainer show?Show Answer & Explanation
docker logs retrieves this output. Add -f to stream in real-time (like tail -f). This is your primary debugging tool when a container misbehaves. Applications in containers should log to stdout/stderr for this reason.Show Answer & Explanation
docker container prune removes all stopped containers. It asks for confirmation first. To also clean up dangling images and unused networks/volumes, use docker system prune. Add -f to skip the confirmation prompt in scripts.8. Dockerfile
A Dockerfile is a text script with instructions to build a custom Docker image. It's how you tell Docker exactly how to package your application.
What is a Dockerfile?
Think of a Dockerfile as a recipe for building a Docker image. Each instruction in the file creates a new layer in the image. Docker reads the file top-to-bottom and executes each instruction sequentially during the build.
All Dockerfile Instructions Explained
FROM — Set Base Image
FROM node:18-alpine
# Every Dockerfile MUST start with FROM (except multi-stage)
# node:18-alpine means Node.js version 18 on Alpine Linux (small ~50MB)
# vs node:18 which is ~900MB on Debian
WORKDIR — Set Working Directory
WORKDIR /app
# Sets /app as the working directory for all subsequent instructions
# Creates the directory if it doesn't exist
# Better than: RUN mkdir /app && cd /app (which doesn't persist)
COPY — Copy Files from Host
COPY package*.json ./
# Copies package.json and package-lock.json from build context (host) to /app
COPY . .
# Copies everything from build context to /app
# (use .dockerignore to exclude node_modules, .git, etc.)
RUN — Execute Commands During Build
RUN npm install
# Executes during IMAGE BUILD, creates a new layer
# Good for: installing packages, compiling code, setting up config
RUN apt-get update && apt-get install -y curl vim && rm -rf /var/lib/apt/lists/*
# Combine multiple commands with && to create ONE layer (more efficient)
# rm -rf cleans up apt cache to reduce image size
ENV — Set Environment Variables
ENV NODE_ENV=production
ENV PORT=3000
# Available during build AND runtime
# Can be overridden at runtime: docker run -e NODE_ENV=development
EXPOSE — Document Port
EXPOSE 3000
# DOCUMENTS that the container listens on port 3000
# Does NOT actually publish the port (that's -p at runtime)
# It's informational and used by orchestration tools
CMD — Default Command
CMD ["node", "server.js"]
# The default command that runs when a container starts
# Can be OVERRIDDEN at runtime: docker run myapp npm test
# Use JSON array form (exec form) — preferred over shell form
ENTRYPOINT — Set Main Executable
ENTRYPOINT ["node"]
CMD ["server.js"]
# ENTRYPOINT: the fixed executable (harder to override)
# CMD: default arguments to ENTRYPOINT (easily overridden)
# Combined: runs "node server.js" by default
# Override: docker run myapp other-file.js → runs "node other-file.js"
ADD vs COPY
COPY src/ /app/src/ # Preferred: simple, predictable
ADD archive.tar.gz /app/ # ADD can auto-extract tar archives
ADD http://example.com/file.txt /app/ # ADD can download URLs
# Rule of thumb: prefer COPY unless you need ADD's extra features
Complete Example: Dockerizing a Node.js App
# Stage 1: Use official Node.js Alpine image as base
FROM node:18-alpine
# Set working directory inside the container
WORKDIR /app
# Copy dependency files first (layer caching optimization!)
# If package.json hasn't changed, Docker reuses the cached layer
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy remaining application source code
COPY . .
# Declare the port our app listens on (documentation)
EXPOSE 3000
# Set NODE_ENV to production
ENV NODE_ENV=production
# Command to run when container starts
CMD ["node", "server.js"]
Building the Image
# Build image from Dockerfile in current directory
# -t = tag (name:version) . = build context (current directory)
docker build -t myapp:1.0 .
# Build with a different Dockerfile location
docker build -t myapp:1.0 -f docker/Dockerfile.prod .
# Build with build arguments (values passed at build time)
docker build -t myapp:1.0 --build-arg API_URL=https://api.prod.com .
# After building, run it
docker run -d -p 3000:3000 --name myapp myapp:1.0
The .dockerignore File
Just like .gitignore excludes files from git, .dockerignore excludes files from the build context. This speeds up builds and prevents accidentally copying secrets into images.
node_modules/
.git/
.env
*.log
.DS_Store
README.md
*.test.js
Multi-Stage Builds
Multi-stage builds let you use multiple FROM statements. Each stage can copy artifacts from previous stages. This is great for compiled languages — compile in one stage, copy only the binary to the final small image.
# Stage 1: Build (has all build tools)
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myserver .
# Stage 2: Run (only has the tiny binary)
FROM alpine:3.18
WORKDIR /app
COPY --from=builder /app/myserver .
EXPOSE 8080
CMD ["./myserver"]
# Result: ~15MB image instead of ~1GB (no Go compiler needed at runtime!)
Docker caches each layer. If a layer hasn't changed, it reuses the cached version (much faster). Always copy dependency files (package.json, requirements.txt) and install dependencies BEFORE copying your application code. That way, a code change doesn't invalidate the expensive "install dependencies" layer.
Show Answer & Explanation
Show Answer & Explanation
FROM must be the first instruction in a Dockerfile (ARG can appear before FROM). It sets the base image that all subsequent instructions build on. Every image must start from something — even if it's FROM scratch (empty).RUN instruction do in a Dockerfile?Show Answer & Explanation
RUN executes commands during the BUILD phase (when you run docker build). It's used to install packages, compile code, create directories, etc. Each RUN creates a new cached layer. To start the app when a container runs, use CMD or ENTRYPOINT.CMD and ENTRYPOINT in a Dockerfile?Show Answer & Explanation
CMD sets default command/arguments that can be completely replaced: docker run myimage custom-command replaces CMD. ENTRYPOINT sets the executable — you'd need --entrypoint flag to override it. Combined: ENTRYPOINT is the executable, CMD is its default args.COPY instruction do in a Dockerfile?Show Answer & Explanation
COPY <src> <dest> copies files from the build context (directory sent to Docker daemon during build) into the container filesystem. src is relative to the build context; dest is inside the container. Files in .dockerignore are excluded from the context.WORKDIR /app do in a Dockerfile?Show Answer & Explanation
WORKDIR sets the working directory for subsequent RUN, CMD, ENTRYPOINT, COPY, and ADD instructions. It creates the directory if it doesn't exist. It's equivalent to mkdir -p /app && cd /app but persists across subsequent instructions..dockerignore file?Show Answer & Explanation
.gitignore, a .dockerignore file specifies files to exclude from the build context. This speeds up builds (less data transferred to daemon), avoids accidentally including secrets (.env), and prevents large directories like node_modules from being needlessly sent.Show Answer & Explanation
docker build is the command that reads a Dockerfile and builds an image. -t tags the image with name:version. The . at the end specifies the build context (current directory). Docker sends this context to the daemon, which uses it along with the Dockerfile.docker build -t myapp:1.0 ., what does the trailing . represent?Show Answer & Explanation
. specifies the build context — the directory whose contents are sent to the Docker daemon. The daemon then uses files from this context when processing COPY instructions. The Dockerfile itself is expected to be in this directory, but you can override it with -f path/to/Dockerfile.Show Answer & Explanation
ENV NODE_ENV=production instruction in a Dockerfile:Show Answer & Explanation
ENV variables are persisted in the image and available at runtime. They can be overridden when running a container with docker run -e NODE_ENV=development. Be careful: never use ENV for secrets (passwords, API keys) as they're visible in docker inspect.EXPOSE 8080 in a Dockerfile actually do?Show Answer & Explanation
EXPOSE is documentation — it tells users and tools that the container listens on that port, but it doesn't actually publish it to the host. To make it accessible from outside, you still need -p 8080:8080 (or -P to auto-publish all exposed ports to random host ports).9. Docker Volumes
Containers are ephemeral — data inside them disappears when they're removed. Volumes are the solution, providing persistent storage that outlives any container.
Why Volumes?
Imagine running a database inside a container. The database stores its files in the container's filesystem. If you remove the container (to update the database version, for example), all your data is gone. That's a disaster.
Docker Volumes solve this by storing data on the host filesystem (or network storage) outside the container. The container can be deleted and recreated, but the volume — and your data — persists.
Three Types of Docker Storage
Named Volumes — The Recommended Way
Named volumes are managed entirely by Docker. Docker decides where on the host to store them (usually /var/lib/docker/volumes/). They're portable — they can be backed up, migrated, and shared between containers.
# Create a named volume
docker volume create mydata
# Use it when running a container
# -v volume_name:container_path
docker run -d \
--name postgres-db \
-e POSTGRES_PASSWORD=secret \
-v mydata:/var/lib/postgresql/data \
postgres:15
# The database files are stored in the 'mydata' volume
# Delete and recreate the container — data persists!
docker rm -f postgres-db
docker run -d --name postgres-db -v mydata:/var/lib/postgresql/data postgres:15
# ✅ Data is still there!
# List all volumes
docker volume ls
# Inspect a volume (shows where it's stored on host)
docker volume inspect mydata
# Remove a volume (DATA IS DELETED!)
docker volume rm mydata
# Remove all unused volumes
docker volume prune
Bind Mounts — Mount a Host Directory
Bind mounts link a specific directory on your host to a path inside the container. Changes on either side are immediately reflected on the other side. Perfect for development — edit code on your host, see changes immediately in the container.
# Mount current host directory to /app in container
# $(pwd) = print working directory (current directory)
docker run -d \
-p 3000:3000 \
-v $(pwd):/app \
node:18-alpine \
node server.js
# Edit code on your host → changes instantly visible in container
# Read-only bind mount (container can't modify the host files)
docker run -d -v $(pwd)/config:/app/config:ro nginx
tmpfs Mounts — In-Memory Storage
tmpfs mounts store data in the host's RAM. The data is never written to disk — it disappears when the container stops. Useful for sensitive temporary data (like secrets or session tokens) that you don't want written to disk.
# Mount an in-memory filesystem at /tmp inside the container
docker run -d --tmpfs /tmp nginx
# Or using the --mount syntax (more verbose but explicit)
docker run -d \
--mount type=tmpfs,destination=/tmp,tmpfs-size=100m \
nginx
Comparison: Volumes vs Bind Mounts
| Feature | Named Volume | Bind Mount |
|---|---|---|
| Storage location | Docker-managed (/var/lib/docker/volumes) | Any host path you specify |
| Best for | Production data (databases, persistent files) | Development (live code reload) |
| Portability | High — Docker manages it | Low — depends on host path |
| Performance | Optimized by Docker | Good on Linux, slower on Mac/Windows |
| Backup | Easy via Docker commands | Manual host backup |
Show Answer & Explanation
Show Answer & Explanation
Show Answer & Explanation
docker volume create <name> creates a named volume. You can also let Docker create it automatically by using it in docker run -v mydata:/path — if "mydata" doesn't exist, Docker creates it. Volumes can be shared between multiple containers simultaneously.Show Answer & Explanation
/home/user/myapp) to a path inside the container. Both see the same files — changes on either side are instantly visible. This is essential for development workflows where you want live code reload.Show Answer & Explanation
docker run flag is used to mount a volume or bind mount?Show Answer & Explanation
-v and --mount attach storage to containers. -v volume:/path is the short form; --mount type=volume,source=vol,target=/path is the explicit long form. --mount is recommended for new code as it's more readable and supports all options.Show Answer & Explanation
/var/lib/docker/ on Linux — including images, containers, and volumes. Named volumes are in /var/lib/docker/volumes/<volume-name>/_data/. You can verify this with docker volume inspect <name> which shows the "Mountpoint".Show Answer & Explanation
docker volume ls lists all volumes (both named and anonymous). The output shows DRIVER and VOLUME NAME. To remove unused volumes, use docker volume prune. To see details about a specific volume (like its host path), use docker volume inspect <name>.10. Docker Networks
Docker networking allows containers to communicate with each other and with the outside world. Understanding networks is essential for building multi-service applications.
Why Docker Networking?
When you run multiple containers (e.g., a web app + database + cache), they need to talk to each other. Docker networking provides isolated virtual networks where containers can communicate securely.
Network Drivers
Network Drivers Explained
ping db works). Always prefer this over the default bridge.localhost inside the container IS the host's localhost. No port mapping needed, but no network isolation.Working with Networks
# List all networks
docker network ls
# Shows: NETWORK ID, NAME, DRIVER, SCOPE
# Default networks: bridge, host, none
# Create a custom bridge network
docker network create mynetwork
# Create with custom subnet
docker network create --subnet=192.168.10.0/24 mynetwork
# Run containers on the same network
docker run -d --name api --network mynetwork myapi-image
docker run -d --name db --network mynetwork postgres:15
# 'api' can reach 'db' by name: postgres://db:5432/mydb
# This works because Docker's embedded DNS resolves container names
# Connect an existing container to a network
docker network connect mynetwork existing-container
# Disconnect a container from a network
docker network disconnect mynetwork existing-container
# Inspect a network (see connected containers and their IPs)
docker network inspect mynetwork
# Remove a network (all containers must be disconnected first)
docker network rm mynetwork
Container-to-Container Communication
On a user-defined bridge network, containers can communicate using their names:
# Create a network
docker network create appnet
# Start a database container
docker run -d --name postgres \
--network appnet \
-e POSTGRES_DB=mydb \
-e POSTGRES_PASSWORD=secret \
postgres:15
# Start your app — connect to 'postgres' by name
docker run -d --name myapp \
--network appnet \
-p 3000:3000 \
-e DATABASE_URL="postgres://postgres:secret@postgres:5432/mydb" \
myapp:latest
# 'postgres' in the DATABASE_URL resolves to the postgres container's IP
The default bridge network doesn't support DNS resolution by container name. Always create a named network for your applications. Docker Compose does this automatically — it creates a network per project and adds all services to it.
Show Answer & Explanation
--network, it attaches to the default bridge network called "bridge". Containers on the default bridge can communicate by IP but not by name. For name-based communication, create a user-defined bridge network.Show Answer & Explanation
db:5432 without knowing the actual IP.host network mode do?Show Answer & Explanation
--network host, the container has no network namespace of its own — it uses the host's network directly. Port 80 inside the container IS port 80 on the host. No -p mapping needed. Benefit: best performance. Drawback: no network isolation, port conflicts possible.none network driver used for?Show Answer & Explanation
none driver removes the container from all networks — it has no network interface (except loopback). Useful for batch processing jobs that shouldn't have internet access, or for maximum security isolation. docker run --network none myimageoverlay network driver is primarily used for:Show Answer & Explanation
overlay driver creates a distributed virtual network that spans multiple Docker hosts in a Swarm cluster. Containers on different machines can communicate as if on the same local network. Kubernetes uses similar networking concepts with its CNI (Container Network Interface) plugins.Show Answer & Explanation
Show Answer & Explanation
docker network create <name> creates a new bridge network by default. You can specify the driver with --driver (e.g., --driver overlay). Docker Compose automatically creates a network for each compose project, but for standalone containers, you create it manually.docker network inspect mynetwork show?Show Answer & Explanation
docker network inspect returns detailed JSON metadata: the network's subnet, gateway, connected containers with their IPv4/IPv6 addresses and MAC addresses, and labels. It's the main debugging tool for container networking issues.11. Docker Compose
Managing multiple containers with individual docker run commands gets tedious fast. Docker Compose lets you define a multi-container application in a single YAML file and manage everything with one command.
What is Docker Compose?
Docker Compose is a tool for defining and running multi-container Docker applications. You describe all your services (web app, database, cache, etc.) in a docker-compose.yml file, and then start everything with docker compose up.
Anatomy of a docker-compose.yml
services:
# Service 1: Our Node.js web app
web:
build: . # Build from Dockerfile in current dir
ports:
- "3000:3000" # host:container port mapping
environment:
- NODE_ENV=production
- DATABASE_URL=postgres://db:5432/mydb
depends_on:
- db # Wait for 'db' to start before 'web'
networks:
- appnet
volumes:
- ./logs:/app/logs # Bind mount for logs
# Service 2: PostgreSQL database
db:
image: postgres:15
environment:
POSTGRES_DB: mydb
POSTGRES_PASSWORD: secret
volumes:
- dbdata:/var/lib/postgresql/data # Named volume for persistence
networks:
- appnet
# Service 3: Redis cache
redis:
image: redis:7-alpine
networks:
- appnet
# Named volumes (persistent data)
volumes:
dbdata:
# Custom network
networks:
appnet:
driver: bridge
Essential Docker Compose Commands
# Start all services (in foreground — shows all logs)
docker compose up
# Start in detached (background) mode
docker compose up -d
# Build images AND start services (force rebuild)
docker compose up --build -d
# Stop all services (keeps containers, volumes, networks)
docker compose stop
# Stop AND remove containers + networks (keeps volumes)
docker compose down
# Stop AND remove EVERYTHING including volumes (⚠️ DATA LOSS!)
docker compose down -v
# View logs for all services
docker compose logs
# Follow logs in real-time
docker compose logs -f
# Follow logs for a specific service
docker compose logs -f web
# List all running services
docker compose ps
# Run a one-off command in a service
docker compose exec web bash
docker compose exec db psql -U postgres
# Scale a service (run multiple instances)
docker compose up --scale web=3 -d
# Rebuild only one service
docker compose up --build web
Environment Variables in Compose
POSTGRES_PASSWORD=supersecret
NODE_ENV=production
API_KEY=abc123
services:
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # Read from .env
The newer docker compose (with a space, as a Docker plugin) replaced the older docker-compose (with a hyphen, a standalone binary). Both work similarly, but the plugin version is now bundled with Docker and is preferred. Most modern Docker installations include the plugin.
Show Answer & Explanation
docker-compose.yml file and manage it with simple commands. Instead of running 5 separate docker run commands with many flags, you run docker compose up.Show Answer & Explanation
docker-compose.yml (or docker-compose.yaml) by default. Newer versions also recognize compose.yml / compose.yaml. You can specify a different file with -f: docker compose -f prod.yml up.Show Answer & Explanation
docker compose up creates and starts all services. If images need to be built, it builds them first. It also creates the required networks and volumes. Add -d for background mode. docker compose start exists but only starts existing stopped services (doesn't create new ones).docker compose down do?Show Answer & Explanation
docker compose down stops containers, removes them, and removes the networks created by Compose. Volumes are preserved by default (to protect data). To also delete volumes, add -v. To also remove images, add --rmi all.depends_on field is used to:Show Answer & Explanation
depends_on controls startup order. If web depends_on db, Compose starts db first, then web. Note: it only waits for the container to START, not for the service inside to be READY. For readiness checks, use healthcheck combined with depends_on: condition: service_healthy.Show Answer & Explanation
docker compose logs shows logs from all services. Adding -f follows/streams the output in real-time (like tail -f). Logs from each service are color-coded and prefixed with the service name. To see logs for one service: docker compose logs -f web.build: . key in a docker-compose.yml service definition mean?Show Answer & Explanation
build: ., Compose builds a Docker image using the Dockerfile in that directory. If you specify both image: and build:, Compose builds and tags the image with the name from image:. Use --build flag to force rebuild: docker compose up --build.Show Answer & Explanation
-d stands for "detached" in both docker run and docker compose up. Services run in the background and the terminal is free. Use docker compose logs -f to watch logs afterward. Production deployments typically always use -d.ports: - "3000:3000" mean for a service?Show Answer & Explanation
ports: - "HOST:CONTAINER" in docker-compose.yml is equivalent to -p HOST:CONTAINER in docker run. It publishes the container port to the host. To expose a port only within the Docker network (not to the host), use expose: instead of ports:.volumes: section at the top level (not inside a service) defines:Show Answer & Explanation
volumes: in docker-compose.yml declares named volumes. These are created by Docker and persist even when containers are removed. Multiple services can mount the same volume. Example: volumes: dbdata: creates a volume called projectname_dbdata. Removed with docker compose down -v.12. Docker Registry & Docker Hub
Registries are the distribution layer of the Docker ecosystem — they store and serve Docker images. Understanding how to push and pull images is essential for collaboration and deployment.
What is a Docker Registry?
A Docker Registry is a storage system for Docker images. Think of it like GitHub for code — but for container images. You push images to share them, and others pull them to run containers.
registry:2 Docker image.Image Naming Convention
The full name of a Docker image is: registry/username/repository:tag
# Official images on Docker Hub (no username prefix)
nginx:latest # registry: Docker Hub, official image
postgres:15
# User/org images on Docker Hub
myusername/myapp:1.0 # hub.docker.com/r/myusername/myapp
# Images on other registries
gcr.io/myproject/myapp:v2.3 # Google Container Registry
123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:prod # AWS ECR
ghcr.io/myorg/myapp:latest # GitHub Container Registry
Working with Docker Hub
# Step 1: Log into Docker Hub
docker login
# Enter your Docker Hub username and password
# Credentials stored in ~/.docker/config.json
# Step 2: Tag your image with your Docker Hub username
docker tag myapp:1.0 yourusername/myapp:1.0
# Now the image is tagged as: yourusername/myapp:1.0
# Step 3: Push to Docker Hub
docker push yourusername/myapp:1.0
# Uploads all layers (only new layers are uploaded)
# Step 4: Anyone can now pull your image
docker pull yourusername/myapp:1.0
# Log out of Docker Hub
docker logout
Running a Private Local Registry
# Start a local registry on port 5000
docker run -d -p 5000:5000 --name registry registry:2
# Tag an image for your local registry
docker tag myapp:1.0 localhost:5000/myapp:1.0
# Push to local registry
docker push localhost:5000/myapp:1.0
# Pull from local registry
docker pull localhost:5000/myapp:1.0
Image Tags & Versioning Best Practices
Use semantic versioning: myapp:1.2.3, myapp:1.2, myapp:1, myapp:latest — all pointing to the same image. This lets users pin to exact versions or floating tags based on their needs.
# Tag the same image with multiple tags
docker tag myapp:1.2.3 myusername/myapp:1.2.3
docker tag myapp:1.2.3 myusername/myapp:1.2
docker tag myapp:1.2.3 myusername/myapp:latest
# Push all tags
docker push myusername/myapp:1.2.3
docker push myusername/myapp:1.2
docker push myusername/myapp:latest
# Or push all tags at once
docker push --all-tags myusername/myapp
Docker Content Trust (DCT)
Docker Content Trust is a security feature that uses digital signatures to verify image authenticity. When enabled, you can only pull images that have been signed — protecting against tampered or malicious images.
# Enable Docker Content Trust (for signing and verification)
export DOCKER_CONTENT_TRUST=1
# Now all push/pull operations require signed images
docker push myusername/myapp:1.0 # Signs the image
docker pull myusername/myapp:1.0 # Verifies the signature
Show Answer & Explanation
docker pull nginx, it comes from Docker Hub by default.Show Answer & Explanation
docker login authenticates with Docker Hub (or another registry with docker login registry.url). Credentials are stored in ~/.docker/config.json (as base64 or via a credential store). Required before pushing private images or to avoid Docker Hub pull rate limits.Show Answer & Explanation
username/repository:tag. Only official images (maintained by Docker Inc.) can omit the username prefix (e.g., nginx, postgres). You must tag your local image appropriately before pushing: docker tag myapp yourusername/myapp:1.0.Show Answer & Explanation
docker push username/image:tag uploads the image to the registry. Docker is smart about it — it only uploads layers that don't already exist in the registry. If you change one small file and rebuild, only that new layer is pushed, not the entire image. You must be logged in with docker login first.Show Answer & Explanation
registry:2 Docker image yourself. Private registries are essential for proprietary application code.node:18-alpine tell you about the image?Show Answer & Explanation
node:18-alpine is ~120MB vs node:18 at ~900MB — a massive size difference for production images.Show Answer & Explanation
DOCKER_CONTENT_TRUST=1), Docker refuses to pull unsigned images. This protects against man-in-the-middle attacks and ensures you run exactly what the publisher intended.docker logout do?Show Answer & Explanation
docker logout removes the stored authentication credentials for the registry (by default Docker Hub) from ~/.docker/config.json. After logout, docker push to private repos will fail until you docker login again. Important for security when working on shared machines.Congratulations! You've Completed the Docker Tutorial
You've covered all 12 sections and 100 practice questions. You now understand everything from what Docker is to building production-ready containerized applications.