Creating Your Own Images (Dockerfile)

Now we'll learn how to create your own custom Docker images using Dockerfiles. This is where Docker becomes really powerful!


Part 1: What is a Dockerfile?

Simple Definition

Dockerfile = A text file containing instructions to build a Docker image.

Think of it as:

Recipe Card (Dockerfile):
├── List ingredients (base image)
├── Preparation steps (RUN commands)
├── Add your items (COPY files)
├── Cooking instructions (CMD)
└── Final dish (your custom image)

Following recipe → Creates the dish
Reading Dockerfile → Builds the image

Another Analogy:

Construction Blueprint (Dockerfile):
├── Foundation type (FROM)
├── Building materials (RUN install packages)
├── Interior design (COPY your files)
├── Final touches (CMD)
└── Complete building (custom image)

Why Create Custom Images?

Instead of using existing images, you create custom ones to:

1. Package your own application
   └── Your code + environment together

2. Customize existing images
   └── Add tools/packages you need

3. Create reproducible environments
   └── Same setup everywhere

4. Share with team
   └── Everyone uses same environment

5. Deploy applications
   └── Production-ready packages

Dockerfile Basics

A Dockerfile is just a text file named Dockerfile (no extension).

Simple example:

FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y python3
COPY app.py /app/
CMD ["python3", "/app/app.py"]

What this does:

Line 1: Start with Ubuntu 22.04 as base
Line 2: Update package lists
Line 3: Install Python 3
Line 4: Copy your app.py file into image
Line 5: Run your app when container starts

Part 2: Dockerfile Instructions - FROM

FROM - The Base Image

FROM = Starting point for your image

Syntax:

FROM image:tag

Examples:

# Start with Ubuntu
FROM ubuntu:22.04

# Start with Python already installed
FROM python:3.11

# Start with Node.js
FROM node:18

# Start with minimal Alpine Linux
FROM alpine:3.18

# Start from scratch (empty image)
FROM scratch

Understanding FROM

Every Dockerfile MUST start with FROM:

FROM ubuntu:22.04
# ↑
# This is always the first instruction
# (except for ARG, which we'll learn later)

Why use different base images?

Choose based on needs:

Need Python?
FROM python:3.11
└── Python already installed ✓

Need Node.js?
FROM node:18
└── Node.js already installed ✓

Want minimal size?
FROM alpine:3.18
└── Smallest base (5MB) ✓

Want full Ubuntu?
FROM ubuntu:22.04
└── More packages available ✓

Starting from scratch?
FROM scratch
└── Build everything yourself

Example: Different Base Images

Example 1: Start with Ubuntu, install Python

FROM ubuntu:22.04
RUN apt-get update && apt-get install -y python3
# Manual installation

Example 2: Start with Python already

FROM python:3.11
# Python already included! ✓

Which is better?

Option 2 (FROM python:3.11) because:
├── Python pre-configured correctly
├── Includes pip and other tools
├── Follows best practices
└── Less code to write

Unless you need specific Ubuntu features,
use official language images! ✓

Part 3: Dockerfile Instructions - RUN

RUN - Execute Commands During Build

RUN = Run commands when building the image

Syntax:

RUN command

Examples:

# Install packages (Ubuntu/Debian)
RUN apt-get update && apt-get install -y curl

# Install packages (Alpine)
RUN apk add --no-cache curl

# Install Python packages
RUN pip install flask

# Create directories
RUN mkdir -p /app/data

# Download files
RUN curl -O https://example.com/file.zip

RUN - Important Concepts

Each RUN creates a new layer:

FROM ubuntu:22.04
RUN apt-get update           # Layer 1
RUN apt-get install -y curl  # Layer 2
RUN apt-get install -y vim   # Layer 3

Better approach - combine commands:

FROM ubuntu:22.04
RUN apt-get update && \
    apt-get install -y curl vim
# Single layer ✓

Why combine?

Separate RUN commands:
├── Layer 1: apt-get update (50MB)
├── Layer 2: install curl (10MB)
├── Layer 3: install vim (30MB)
└── Total: 90MB + overhead

Combined RUN:
└── Layer 1: update + installs (60MB)
└── Total: 60MB (smaller!) ✓

RUN Examples for Different Languages

Python:

FROM python:3.11
RUN pip install flask sqlalchemy requests

Node.js:

FROM node:18
RUN npm install express mongoose

System packages:

FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
    git \
    curl \
    vim \
    && rm -rf /var/lib/apt/lists/*
# ↑ Cleanup to reduce size

Part 4: Dockerfile Instructions - COPY and ADD

COPY - Copy Files from Host to Image

COPY = Copy files from your computer into the image

Syntax:

COPY source destination

Examples:

# Copy single file
COPY app.py /app/

# Copy all files in current directory
COPY . /app/

# Copy multiple files
COPY app.py config.json /app/

# Copy directory
COPY ./src /app/src/

Understanding COPY

Visual:

Your Computer:              Docker Image:
┌─────────────────┐        ┌──────────────┐
│ my-project/     │        │              │
│ ├── app.py      │  COPY  │ /app/        │
│ ├── config.json │───────→│ ├── app.py   │
│ └── data/       │        │ └── config   │
└─────────────────┘        └──────────────┘

Example Dockerfile:

FROM python:3.11

# Create app directory in image
RUN mkdir /app

# Copy your files into image
COPY app.py /app/
COPY requirements.txt /app/

# Copy everything
COPY . /app/

ADD vs COPY

ADD = Like COPY but with extra features

# COPY: Just copies files
COPY app.py /app/

# ADD: Copies AND extracts archives
ADD archive.tar.gz /app/
# ↑ Automatically extracts!

# ADD: Can download from URL
ADD https://example.com/file.txt /app/

Which to use?

Use COPY (recommended):
├── Simpler
├── More predictable
└── Best practice ✓

Use ADD only when:
├── Need to extract archives
└── Need to download from URL

Docker best practice: Prefer COPY

Part 5: Dockerfile Instructions - WORKDIR

WORKDIR - Set Working Directory

WORKDIR = Set the current directory inside the image

Syntax:

WORKDIR /path/to/directory

Without WORKDIR:

FROM ubuntu:22.04
RUN mkdir /app
COPY app.py /app/
RUN cd /app && python3 app.py
# ↑ Must keep specifying /app

With WORKDIR:

FROM ubuntu:22.04
WORKDIR /app
# ↑ Set once

COPY app.py .
# . means current directory (/app)

RUN python3 app.py
# Already in /app directory

WORKDIR Benefits

Makes Dockerfile cleaner:

FROM python:3.11

# Set working directory
WORKDIR /app

# Now all commands run in /app
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

CMD ["python", "app.py"]
# Runs from /app directory automatically

WORKDIR creates directory if it doesn't exist:

FROM ubuntu:22.04
WORKDIR /app/data/logs
# ↑ Creates entire path automatically ✓

Part 6: Dockerfile Instructions - CMD and ENTRYPOINT

CMD - Default Command

CMD = Command to run when container starts

Syntax:

CMD ["executable", "param1", "param2"]

Examples:

# Run Python script
CMD ["python3", "app.py"]

# Start web server
CMD ["nginx", "-g", "daemon off;"]

# Run shell command
CMD ["echo", "Hello Docker!"]

Understanding CMD

CMD sets the default command:

FROM ubuntu:22.04
WORKDIR /app
COPY hello.py .
CMD ["python3", "hello.py"]

When you run container:

docker run myimage
# Automatically runs: python3 hello.py

Can be overridden:

docker run myimage echo "Different command"
# Runs: echo "Different command"
# (CMD is ignored)

CMD Formats

Three formats:

1. Exec form (recommended):

CMD ["python3", "app.py"]
# ↑ As JSON array

2. Shell form:

CMD python3 app.py
# ↑ As shell command

3. As parameters to ENTRYPOINT:

ENTRYPOINT ["python3"]
CMD ["app.py"]
# Together: python3 app.py

ENTRYPOINT - Fixed Command

ENTRYPOINT = Command that always runs (cannot be easily overridden)

Difference between CMD and ENTRYPOINT:

# Using CMD
CMD ["python3", "app.py"]

docker run myimage
# Runs: python3 app.py

docker run myimage ls
# Runs: ls (CMD ignored!)
# Using ENTRYPOINT
ENTRYPOINT ["python3", "app.py"]

docker run myimage
# Runs: python3 app.py

docker run myimage ls
# Runs: python3 app.py ls
#       ↑ Still runs ENTRYPOINT!

CMD + ENTRYPOINT Together

Powerful combination:

FROM python:3.11
WORKDIR /app
COPY app.py .

ENTRYPOINT ["python3"]
CMD ["app.py"]

Usage:

# Run default
docker run myimage
# Executes: python3 app.py

# Run different script
docker run myimage test.py
# Executes: python3 test.py
# ↑ ENTRYPOINT fixed, CMD replaced

Part 7: Dockerfile Instructions - ENV

ENV - Environment Variables

ENV = Set environment variables in the image

Syntax:

ENV KEY=VALUE

Examples:

# Set single variable
ENV APP_ENV=production

# Set multiple variables
ENV APP_ENV=production \
    DB_HOST=localhost \
    DB_PORT=5432

Using ENV

Example Dockerfile:

FROM python:3.11
WORKDIR /app

# Set environment variables
ENV PYTHONUNBUFFERED=1
ENV APP_PORT=8000
ENV DEBUG=False

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

In your Python code (app.py):

import os

port = os.getenv('APP_PORT')  # Gets 8000
debug = os.getenv('DEBUG')     # Gets False
print(f"Starting app on port {port}")

Override ENV at Runtime

You can override when running container:

docker run -e APP_PORT=9000 myimage
# APP_PORT is now 9000 (overrides Dockerfile ENV)

Part 8: Dockerfile Instructions - EXPOSE

EXPOSE - Document Which Ports are Used

EXPOSE = Tells Docker which ports the container listens on

Syntax:

EXPOSE port

Examples:

# Web server on port 80
EXPOSE 80

# Multiple ports
EXPOSE 80 443

# With protocol
EXPOSE 8080/tcp
EXPOSE 53/udp

Understanding EXPOSE

IMPORTANT: EXPOSE is just documentation!

EXPOSE does NOT:
├── Actually publish the port
├── Make port accessible from outside
└── Do port mapping

EXPOSE only:
└── Documents which ports app uses
└── Helps other developers understand

You still need -p when running:

docker run -p 8080:80 myimage
# ↑ This is what actually publishes the port

Example:

FROM nginx:latest
EXPOSE 80
# ↑ Documents: "nginx uses port 80"

Part 9: Creating Your First Dockerfile

Example 1: Simple Python Application

Let's create a real Dockerfile!

Step 1: Create project directory

mkdir my-python-app
cd my-python-app

Step 2: Create a simple Python app (app.py)

# app.py
print("Hello from Docker!")
print("This is my first containerized app!")

import time
while True:
    print("App is running...")
    time.sleep(5)

Step 3: Create Dockerfile

Create a file named Dockerfile (no extension):

# Use Python 3.11 as base image
FROM python:3.11-slim

# Set working directory
WORKDIR /app

# Copy Python script into container
COPY app.py .

# Run the application
CMD ["python", "app.py"]

Step 4: Build the image

docker build -t my-python-app .

Understanding the command:

docker build
    -t my-python-app    ← Tag (name) for the image
    .                   ← Build context (current directory)

What you'll see:

[+] Building 12.3s (8/8) FINISHED
 => [1/3] FROM python:3.11-slim
 => [2/3] WORKDIR /app
 => [3/3] COPY app.py .
 => exporting to image
 => => naming to my-python-app

Successfully built!

Step 5: Run your container

docker run my-python-app

Output:

Hello from Docker!
This is my first containerized app!
App is running...
App is running...
App is running...
...

🎉 Congratulations! You built your first Docker image!


Understanding the Build Process

What happened during docker build:

Step 1: Read Dockerfile
Step 2: FROM python:3.11-slim
        └── Download base image (if not present)

Step 3: WORKDIR /app
        └── Create /app directory in image

Step 4: COPY app.py .
        └── Copy your file into image

Step 5: CMD ["python", "app.py"]
        └── Set default command

Step 6: Create final image
        └── Tag it as "my-python-app"

Example 2: Python Web App with Dependencies

Let's create something more realistic!

Step 1: Create project structure

mkdir flask-app
cd flask-app

Step 2: Create app.py

# app.py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return '<h1>Hello from Dockerized Flask!</h1>'

@app.route('/about')
def about():
    return '<h1>This is a Flask app running in Docker</h1>'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Step 3: Create requirements.txt

Flask==3.0.0

Step 4: Create Dockerfile

# Use Python 3.11 slim image
FROM python:3.11-slim

# Set working directory
WORKDIR /app

# Copy requirements file
COPY requirements.txt .

# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY app.py .

# Expose port 5000
EXPOSE 5000

# Run the application
CMD ["python", "app.py"]

Step 5: Build the image

docker build -t flask-app .

Step 6: Run the container

docker run -d -p 5000:5000 --name my-flask-app flask-app

Step 7: Test it

Open browser and go to:

  • http://localhost:5000
  • http://localhost:5000/about

You should see your Flask app running! 🎉


Understanding This Dockerfile

Let's break it down:

FROM python:3.11-slim
# Start with lightweight Python image

WORKDIR /app
# All subsequent commands run in /app

COPY requirements.txt .
# Copy dependencies list first
# Why first? For layer caching! (explained next)

RUN pip install --no-cache-dir -r requirements.txt
# Install Python packages
# --no-cache-dir = Don't save pip cache (smaller image)

COPY app.py .
# Copy application code

EXPOSE 5000
# Document that Flask uses port 5000

CMD ["python", "app.py"]
# Start Flask when container runs

Build Context and .dockerignore

Build Context = Files Docker can access during build

When you run:

docker build -t myapp .
#                      ↑
#                 Build context (current directory)

Docker sends all files in this directory to Docker daemon:

my-project/
├── app.py              ← Sent to Docker
├── requirements.txt    ← Sent to Docker
├── data.csv            ← Sent to Docker
├── old_backup.zip      ← Sent to Docker (unnecessary!)
└── node_modules/       ← Sent to Docker (huge, unnecessary!)

Using .dockerignore

Create .dockerignore file to exclude files:

# .dockerignore

# Python
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
env/
venv/

# IDE
.vscode/
.idea/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Git
.git/
.gitignore

# Documentation
*.md
docs/

# Tests
tests/
*.test.py

# Data files (if not needed in image)
*.csv
*.xlsx
data/

Now build is faster:

Without .dockerignore:
└── Sends 500MB to Docker

With .dockerignore:
└── Sends 5MB to Docker ✓

Build time: Much faster! ✓

Part 10: Building Images - Best Practices

Best Practice 1: Layer Caching

Docker caches layers to speed up builds!

Bad example (slow rebuilds):

FROM python:3.11-slim
WORKDIR /app

# Copy everything
COPY . .

# Install dependencies
RUN pip install -r requirements.txt

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

Problem:

Change app.py:
        ↓
COPY . . changes (includes app.py)
        ↓
Cache invalidated
        ↓
Must re-run pip install (slow!) ✗

Good example (fast rebuilds):

FROM python:3.11-slim
WORKDIR /app

# Copy only requirements first
COPY requirements.txt .

# Install dependencies
RUN pip install -r requirements.txt

# Copy application code
COPY app.py .

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

Why better:

Change app.py:
        ↓
COPY requirements.txt . (unchanged)
        ↓
RUN pip install (cached! ✓)
        ↓
COPY app.py . (only this layer rebuilds)
        ↓
Fast rebuild! ✓

Rule: Copy files that change less frequently first!


Best Practice 2: Minimize Layers

Combine RUN commands:

Bad:

RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
RUN apt-get install -y git
# 4 layers ✗

Good:

RUN apt-get update && apt-get install -y \
    curl \
    vim \
    git
# 1 layer ✓

Best Practice 3: Clean Up in Same Layer

Bad:

RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# Cleanup in separate layer doesn't reduce image size! ✗

Good:

RUN apt-get update && \
    apt-get install -y curl && \
    rm -rf /var/lib/apt/lists/*
# Cleanup in same layer reduces size! ✓

Best Practice 4: Use Specific Tags

Bad:

FROM python:latest
# What version? Changes over time! ✗

Good:

FROM python:3.11-slim
# Specific version, predictable! ✓

Best Practice 5: Use .dockerignore

Always create .dockerignore to exclude unnecessary files!


Best Practice 6: Multi-line for Readability

Bad:

RUN apt-get update && apt-get install -y curl vim git wget htop

Good:

RUN apt-get update && apt-get install -y \
    curl \
    vim \
    git \
    wget \
    htop
# Easier to read and modify ✓

Practice Exercises

Let's practice creating Dockerfiles!

Exercise 1: Node.js Application

Create a simple Node.js app:

app.js:

const http = require('http');

const server = http.createServer((req, res) => {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end('<h1>Hello from Node.js in Docker!</h1>');
});

server.listen(3000, '0.0.0.0', () => {
    console.log('Server running on port 3000');
});

Your task: Create Dockerfile

Hints:

  • Use FROM node:18
  • WORKDIR /app
  • COPY app.js .
  • EXPOSE 3000
  • CMD ["node", "app.js"]

Build and run:

docker build -t node-app .
docker run -d -p 3000:3000 node-app

Test: http://localhost:3000


Exercise 2: Static Website with Nginx

Create index.html:

<!DOCTYPE html>
<html>
<head>
    <title>My Docker Site</title>
</head>
<body>
    <h1>Welcome to my Dockerized website!</h1>
    <p>This is served by Nginx running in a Docker container.</p>
</body>
</html>

Your task: Create Dockerfile

Hints:

  • Use FROM nginx:alpine
  • Copy index.html to /usr/share/nginx/html/
  • EXPOSE 80

Build and run:

docker build -t my-website .
docker run -d -p 8080:80 my-website

Test: http://localhost:8080


Summary

What We Learned:

✅ What a Dockerfile is
✅ Dockerfile instructions:
   ├── FROM (base image)
   ├── RUN (execute commands)
   ├── COPY (copy files)
   ├── WORKDIR (set directory)
   ├── CMD (default command)
   ├── ENTRYPOINT (fixed command)
   ├── ENV (environment variables)
   └── EXPOSE (document ports)
✅ Building images with docker build
✅ Layer caching
✅ Best practices
✅ .dockerignore
✅ Created real applications

Understanding Docker Images in Detail

What Exactly is a Docker Image?

Simple Definition:

A Docker image is a read-only template that contains everything needed to run an application.

Real-Life Analogy:

Think of images like:

Recipe Book (Image):
├── List of ingredients (dependencies)
├── Step-by-step instructions (configuration)
├── Cooking method (commands)
└── Expected result (application)

The Actual Dish (Container):
└── Made by following the recipe
└── Can make multiple dishes from same recipe

Another Analogy:

Software Installation CD (Image):
├── Contains all files
├── Instructions how to install
├── Configuration settings
└── Everything packaged together

Installed Software (Container):
└── Running instance on your computer
└── Can install multiple times from same CD

What's Inside a Docker Image?

An image contains:

Docker Image = Package containing:
│
├── 1. Base Operating System
│   └── Usually minimal Linux (Ubuntu, Alpine, etc.)
│
├── 2. Application Files
│   └── Your code, scripts, executables
│
├── 3. Dependencies
│   └── Libraries, packages, frameworks
│
├── 4. Configuration Files
│   └── Settings, environment variables
│
├── 5. Metadata
│   └── Author, version, description
│
└── 6. Default Commands
    └── What to run when container starts

Example: Nginx Image Contains:

nginx:latest image includes:
├── Minimal Linux OS (Debian-based)
├── Nginx web server (compiled binary)
├── Default configuration files
├── HTML files (welcome page)
├── Required libraries (SSL, etc.)
└── Start command (run nginx)

Total size: ~141MB
Everything packaged together! ✓

Images are Read-Only

Important Concept:

Images are IMMUTABLE (cannot be changed):

Image = Read-Only Template
├── Once created, cannot be modified
├── Like a sealed recipe book
└── Always the same

Container = Writable Layer on Top
├── Can make changes while running
├── Changes don't affect the image
└── Like writing notes on a photocopy

Visual:

┌─────────────────────────┐
│   Container             │
│   (Writable Layer)      │ ← Your changes here
├─────────────────────────┤
│   Image Layer 3         │
│   (Read-only)           │
├─────────────────────────┤
│   Image Layer 2         │
│   (Read-only)           │
├─────────────────────────┤
│   Image Layer 1         │
│   (Read-only)           │ ← Base image
└─────────────────────────┘

Part 2: Image Layers Concept

What are Image Layers?

Docker images are built in LAYERS - like a cake!

Simple Example:

Think of making a cake:

Layer 1 (Bottom): Cake base
Layer 2: Cream filling
Layer 3: Chocolate layer
Layer 4: Icing
Layer 5 (Top): Decorations

Each layer is separate,
but together they make the whole cake!

Docker Image Layers:

Example: Building a Python web app image

Layer 1: Base OS (Ubuntu)          [200MB]
         ↓
Layer 2: Python installed          [+300MB]
         ↓
Layer 3: Pip packages installed    [+50MB]
         ↓
Layer 4: Your application code     [+10MB]
         ↓
Layer 5: Configuration files       [+1MB]

Total: 561MB

Why Layers Matter - Efficiency!

Key Benefit: Layer Caching

Scenario:

You have two images:

Image A (Python Web App 1):
├── Layer 1: Ubuntu (200MB)
├── Layer 2: Python (300MB)
├── Layer 3: Flask (50MB)
└── Layer 4: App1 code (10MB)

Image B (Python Web App 2):
├── Layer 1: Ubuntu (200MB)      ← Same as Image A!
├── Layer 2: Python (300MB)      ← Same as Image A!
├── Layer 3: Flask (50MB)        ← Same as Image A!
└── Layer 4: App2 code (10MB)    ← Different

Storage needed:
Without layers: 560MB + 560MB = 1,120MB
With layers: 550MB + 10MB = 560MB

Saved: 560MB! ✓

Docker is Smart:

Docker reuses common layers:
├── Layers 1, 2, 3 stored ONCE
├── Both images share these layers
└── Only unique layers are stored separately

Multiple images using same base:
10 Python apps using same base
├── Without layers: 10 × 500MB = 5GB
└── With layers: 500MB + (10 × 10MB) = 600MB

Huge savings! ✓

Seeing Image Layers

Let's examine an image's layers!

docker pull nginx

Look at the pull output carefully:

Using default tag: latest
latest: Pulling from library/nginx

a2abf6c4d29d: Pull complete  ← Layer 1
a9edb18cadd1: Pull complete  ← Layer 2
589b7251471a: Pull complete  ← Layer 3
186b1aaa4aa6: Pull complete  ← Layer 4
b4df32aa5a72: Pull complete  ← Layer 5
a0bcbecc962e: Pull complete  ← Layer 6

Each line = One layer being downloaded!

Inspect image layers:

docker history nginx

Output:

IMAGE          CREATED       CREATED BY                                      SIZE
605c77e624dd   2 weeks ago   CMD ["nginx" "-g" "daemon off;"]               0B
<missing>      2 weeks ago   STOPSIGNAL SIGQUIT                             0B
<missing>      2 weeks ago   EXPOSE map[80/tcp:{}]                          0B
<missing>      2 weeks ago   ENTRYPOINT ["/docker-entrypoint.sh"]           0B
<missing>      2 weeks ago   COPY docker-entrypoint.sh /                    1.2kB
<missing>      2 weeks ago   RUN /bin/sh -c set -x     && apt-get update... 61MB
<missing>      2 weeks ago   ENV NGINX_VERSION=1.25.3                       0B
<missing>      2 weeks ago   LABEL maintainer=NGINX Docker Maintainers      0B
<missing>      3 weeks ago   /bin/sh -c #(nop)  CMD ["bash"]                0B
<missing>      3 weeks ago   /bin/sh -c #(nop) ADD file:d25aab2dd... in /   77MB

Understanding the output:

CREATED BY = Command that created this layer
SIZE = How much storage this layer uses

Notice:
├── Some layers are 0B (just metadata)
├── Large layers are actual files
└── Total = Sum of all layer sizes

Layer Caching in Action

Example: Pull Python image

docker pull python:3.11

Now pull Python with different tag:

docker pull python:3.11-slim

Watch the output:

a2abf6c4d29d: Already exists  ← Docker reused this!
b8e89f3d8926: Already exists  ← And this!
c9f8e5d2a1b3: Pull complete   ← Only downloading new layers

What happened:

python:3.11 and python:3.11-slim share base layers:
├── Both use same Ubuntu base
├── Docker: "I already have these layers!"
├── Only downloads different layers
└── Faster download! Less storage! ✓

Part 3: Docker Hub - The Image Repository

What is Docker Hub?

Simple Definition:

Docker Hub = The "App Store" for Docker images.

Website: https://hub.docker.com

What you can find:

Docker Hub contains:
├── Official Images (verified by Docker)
├── Community Images (made by users)
├── Public Images (free to download)
├── Private Images (your own, paid feature)
└── Millions of ready-to-use images!

Exploring Docker Hub

Let's explore Docker Hub together!

Open browser and go to: https://hub.docker.com

Homepage shows:

┌────────────────────────────────────┐
│  Docker Hub                         │
│                                     │
│  [Search box]                       │
│                                     │
│  Popular Images:                    │
│  ├── nginx                          │
│  ├── ubuntu                         │
│  ├── python                         │
│  ├── node                           │
│  ├── postgres                       │
│  └── mysql                          │
└────────────────────────────────────┘

Searching for Images

Method 1: On Docker Hub Website

1. Go to hub.docker.com
2. Use search box at top
3. Type: nginx
4. Press Enter

You'll see:
├── nginx (Official Image) ⭐
├── nginx-proxy
├── nginx-alpine
└── Many more...

Method 2: Using Command Line

docker search nginx

Output:

NAME                     DESCRIPTION                                     STARS     OFFICIAL
nginx                    Official build of Nginx.                        19000+    [OK]
jwilder/nginx-proxy      Automated nginx proxy for Docker containers     2000+
nginxinc/nginx-unpri...  Unprivileged NGINX Dockerfiles                  150+
nginx/nginx-ingress      NGINX and NGINX Plus Ingress Controllers        100+
...

Understanding columns:

NAME        = Image repository name
DESCRIPTION = What the image does
STARS       = Like GitHub stars (popularity)
OFFICIAL    = [OK] means official image ✓

Understanding Image Names

Full Image Name Format:

REGISTRY/USERNAME/REPOSITORY:TAG

Examples:

1. nginx:latest
   └── Short form (Docker Hub default)
       Full: docker.io/library/nginx:latest

2. ubuntu:22.04
   └── Short form
       Full: docker.io/library/ubuntu:22.04

3. myusername/myapp:v1.0
   └── User image with version tag

4. ghcr.io/company/app:latest
   └── GitHub Container Registry

Breaking it down:

docker.io/library/nginx:latest
    │       │       │      │
    │       │       │      └── TAG (version)
    │       │       └──────── REPOSITORY (image name)
    │       └──────────────── USERNAME/NAMESPACE
    └──────────────────────── REGISTRY (where it's stored)

When you type:
docker pull nginx

Docker automatically expands to:
docker pull docker.io/library/nginx:latest

Part 4: Official vs Community Images

Official Images

What are Official Images?

Official Images are:
├── Curated by Docker, Inc.
├── Verified and maintained
├── Security scanned regularly
├── High quality
├── Best practices followed
└── Marked with "Official Image" badge

Examples:
├── nginx
├── ubuntu
├── python
├── node
├── redis
├── postgres
└── mysql

How to identify:

On Docker Hub:
├── Badge says: "Docker Official Image"
├── Name is simple: nginx (not username/nginx)
└── Usually most popular (high stars)

In command:
docker search nginx
└── OFFICIAL column shows: [OK]

Viewing Image Details on Docker Hub

Let's look at nginx image in detail:

Go to: https://hub.docker.com/_/nginx

Page shows:

┌────────────────────────────────────────────┐
│  nginx                                      │
│  Docker Official Image                      │
│  ⭐ 19,000+ stars                           │
│                                             │
│  [Overview] [Tags] [Dockerfile]             │
│                                             │
│  Description:                               │
│  Official build of Nginx web server...     │
│                                             │
│  Quick reference:                           │
│  ├── Where to file issues                  │
│  ├── Maintained by                         │
│  └── Supported architectures               │
│                                             │
│  How to use this image:                    │
│  $ docker run -d -p 80:80 nginx            │
│                                             │
│  Environment Variables:                     │
│  ...                                        │
│                                             │
│  Volumes:                                   │
│  ...                                        │
└────────────────────────────────────────────┘

Available Tags

Click "Tags" tab on nginx page:

Tags show different versions:

┌────────────────────────────────────────┐
│  TAG                 SIZE     UPDATED  │
├────────────────────────────────────────┤
│  latest              141MB    1 day ago│
│  1.25                141MB    1 day ago│
│  1.25.3              141MB    1 day ago│
│  1.25-alpine         41MB     1 day ago│
│  stable              141MB    1 week   │
│  stable-alpine       41MB     1 week   │
│  mainline            143MB    2 days   │
│  alpine              41MB     1 day    │
└────────────────────────────────────────┘

Understanding tags:

latest      = Most recent stable version
1.25        = Major.minor version
1.25.3      = Specific version (major.minor.patch)
alpine      = Lightweight version (smaller)
stable      = Current stable release
mainline    = Development version

Why so many tags?

Different use cases:

Production:
└── Use specific version: nginx:1.25.3
    └── Ensures consistency

Development:
└── Use latest: nginx:latest
    └── Always get updates

Low resources:
└── Use alpine: nginx:alpine
    └── Smaller size

Alpine vs Regular Images

What is Alpine?

Alpine Linux:
├── Minimal Linux distribution
├── Only 5MB base size!
├── Includes only essentials
└── Used for lightweight images

Regular (Debian/Ubuntu based):
├── Full-featured Linux
├── More packages included
├── Larger size
└── More compatible

Size Comparison:

nginx:latest  = 141MB (Debian-based)
nginx:alpine  = 41MB  (Alpine-based)

Difference: 100MB saved! ✓

python:3.11        = 1.01GB
python:3.11-alpine = 51MB

Difference: 959MB saved! ✓

When to use each:

Use Regular (Debian/Ubuntu):
├── Need specific packages
├── Better compatibility
├── Easier debugging
└── Size not critical

Use Alpine:
├── Want smaller images
├── Faster downloads
├── Save disk space
└── Basic functionality enough

Community Images

What are Community Images?

Community Images:
├── Created by individual users
├── Often by companies/organizations
├── Not officially verified
├── May be high quality or not
└── Username included in name

Examples:
├── jwilder/nginx-proxy
├── bitnami/nginx
├── linuxserver/nginx
└── company/custom-app

How to evaluate community images:

Before using, check:

1. Stars (popularity):
   └── High stars = trusted by many

2. Downloads (pull count):
   └── Many downloads = widely used

3. Last updated:
   └── Recently updated = actively maintained

4. Description:
   └── Clear documentation = professional

5. Dockerfile availability:
   └── Can review what's inside

6. User/organization reputation:
   └── Known company = more trustworthy

Verified Publishers

Another category on Docker Hub:

Verified Publishers:
├── Companies verified by Docker
├── More trustworthy than random users
├── Examples:
│   ├── bitnami/*
│   ├── gitlab/*
│   └── microsoft/*
└── Badge: "Verified Publisher"

Part 5: Pulling and Managing Images

Pulling Specific Versions

Pull latest version:

docker pull nginx
# Same as: docker pull nginx:latest

Pull specific version:

docker pull nginx:1.25.3

Pull alpine version:

docker pull nginx:alpine

Pull from specific registry:

# From GitHub Container Registry
docker pull ghcr.io/company/app:latest

# From AWS ECR
docker pull 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:v1.0

Listing Images (Detailed)

docker images

Output:

REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
nginx         latest    605c77e624dd   2 weeks ago    141MB
nginx         alpine    8e75cbc5b25c   2 weeks ago    41MB
nginx         1.25.3    605c77e624dd   2 weeks ago    141MB
python        3.11      a5d7930b60cc   4 weeks ago    1.01GB
ubuntu        22.04     ba6acccedd29   3 weeks ago    77.8MB

Notice:

Same IMAGE ID for nginx:latest and nginx:1.25.3:
605c77e624dd = 605c77e624dd

Why?
└── They're the SAME image!
└── Just different tags pointing to same image
└── No extra storage used! ✓

Filtering Images

Show images from specific repository:

docker images nginx

Output:

REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
nginx         latest    605c77e624dd   2 weeks ago    141MB
nginx         alpine    8e75cbc5b25c   2 weeks ago    41MB
nginx         1.25.3    605c77e624dd   2 weeks ago    141MB

Show dangling images (unused):

docker images -f "dangling=true"

Dangling images = layers with no tag:

<none>        <none>    b8d7c9f3e5a1   1 week ago     150MB
<none>        <none>    c9e1d4f6a8b2   2 weeks ago    200MB

These are leftover layers from old builds

Inspecting Images

Get detailed information:

docker image inspect nginx

Output (JSON format):

[
    {
        "Id": "sha256:605c77e624dd...",
        "RepoTags": [
            "nginx:latest"
        ],
        "Created": "2025-01-15T10:30:00Z",
        "Size": 141000000,
        "Architecture": "amd64",
        "Os": "linux",
        "Config": {
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:...",
                "NGINX_VERSION=1.25.3"
            ],
            "Cmd": [
                "nginx",
                "-g",
                "daemon off;"
            ],
            "ExposedPorts": {
                "80/tcp": {}
            }
        }
    }
]

Useful information from inspect:

Id          = Full image hash
Size        = Total size in bytes
Created     = When image was built
Config.Cmd  = Default command to run
Config.Env  = Environment variables
ExposedPorts= Which ports container uses

Tagging Images

Give an image another name/tag:

docker tag nginx:latest my-nginx:v1.0

Check result:

docker images

Output:

REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
nginx         latest    605c77e624dd   2 weeks ago    141MB
my-nginx      v1.0      605c77e624dd   2 weeks ago    141MB
              ↑                ↑
         New name      Same IMAGE ID!

What happened:

docker tag creates another reference to same image:

Original:
nginx:latest → Image 605c77e624dd

After tagging:
nginx:latest    → Image 605c77e624dd
my-nginx:v1.0   → Image 605c77e624dd (same!)

No extra storage used!
Just another name pointing to same image ✓

Removing Images

Remove by name:

docker rmi nginx:alpine

Remove by ID:

docker rmi 605c77e624dd

Remove multiple:

docker rmi nginx:latest ubuntu:22.04 python:3.11

Force remove (even if containers exist):

docker rmi -f nginx:latest

Remove all unused images:

docker image prune

Output:

WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y

Deleted Images:
untagged: ...
deleted: sha256:...
deleted: sha256:...

Total reclaimed space: 1.5GB

Remove ALL images (careful!):

docker image prune -a

Saving and Loading Images

Save image to file:

docker save nginx:latest -o nginx-backup.tar

What this does:

Creates a .tar file containing:
├── All image layers
├── Metadata
└── Configuration

File: nginx-backup.tar (141MB)

Use case:
└── Transfer image to another computer without internet

Load image from file:

docker load -i nginx-backup.tar

Output:

Loaded image: nginx:latest

Image restored! ✓

Practical scenario:

Computer A (with internet):
1. docker pull nginx
2. docker save nginx -o nginx.tar
3. Copy nginx.tar to USB drive

Computer B (no internet):
1. Copy nginx.tar from USB
2. docker load -i nginx.tar
3. docker run nginx ✓

Transfer images offline! ✓

Practice Exercises

Let's practice everything we learned!

Exercise 1: Explore Docker Hub

1. Go to hub.docker.com
2. Search for "redis"
3. Click on official redis image
4. Read the description
5. Check available tags
6. Note the size differences between tags

Exercise 2: Pull and Compare Images

# Pull different versions
docker pull redis:latest
docker pull redis:alpine
docker pull redis:7.2

# List and compare sizes
docker images redis

# Question: Which is smallest? Why?

Exercise 3: Inspect Images

# Inspect nginx
docker image inspect nginx:latest

# Find answers:
# - What is the default command?
# - Which ports are exposed?
# - What environment variables are set?

Exercise 4: Tag and Manage

# Pull an image
docker pull ubuntu:22.04

# Create custom tags
docker tag ubuntu:22.04 my-ubuntu:v1
docker tag ubuntu:22.04 my-ubuntu:production

# List all
docker images my-ubuntu

# Remove one tag
docker rmi my-ubuntu:v1

# Check - original still there?
docker images ubuntu

Exercise 5: Cleanup

# See all images
docker images

# Remove unused images
docker image prune

# Remove specific image
docker rmi redis:7.2

# Verify removal
docker images redis

Summary

What We Learned:

✅ What Docker images are
✅ Image layers and how they work
✅ Layer caching benefits
✅ Docker Hub exploration
✅ Official vs Community images
✅ Image tags and versions
✅ Alpine vs Regular images
✅ Pulling specific versions
✅ Listing and filtering images
✅ Inspecting image details
✅ Tagging images
✅ Removing images
✅ Saving/Loading images

Key Concepts:

1. Images are templates (read-only)
2. Images are built in layers
3. Layers are cached and reused
4. Tags are different names for same/different versions
5. Official images are more trustworthy
6. Alpine images are smaller
7. Always check Docker Hub for documentation

Running Your First Real Container

 Excellent! Now let's run some real containers and learn the basic Docker commands.


Understanding What We'll Do

Before we start, quick overview:

We'll learn to:
├── Pull images from Docker Hub
├── Run containers
├── See running containers
├── Stop containers
├── Remove containers
└── Basic container management

Think of it like learning to drive:

  • Pull image = Getting a car from the dealer
  • Run container = Starting the car
  • Stop container = Parking the car
  • Remove container = Selling the car

Command 1: docker pull (Download Images)

What is docker pull?

Simple Definition:

docker pull = Downloads a Docker image from Docker Hub to your computer.

Syntax:

docker pull IMAGE_NAME

Let's Pull Our First Image - Nginx

Nginx = A popular web server (serves websites)

Open Command Prompt or PowerShell and type:

docker pull nginx

What you'll see:

Using default tag: latest
latest: Pulling from library/nginx

a2abf6c4d29d: Pull complete  ← Downloading layer 1
a9edb18cadd1: Pull complete  ← Downloading layer 2
589b7251471a: Pull complete  ← Downloading layer 3
186b1aaa4aa6: Pull complete  ← Downloading layer 4
b4df32aa5a72: Pull complete  ← Downloading layer 5
a0bcbecc962e: Pull complete  ← Downloading layer 6
Digest: sha256:xxxxxxxxxxxxx
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest

✓ Download complete!

What happened?

Step 1: Docker contacted Docker Hub
        └── "Do you have nginx image?"

Step 2: Docker Hub responded
        └── "Yes! Here it is" (sends the image)

Step 3: Docker downloaded in layers
        └── Images are split into layers for efficiency

Step 4: Image stored on your computer
        └── Ready to use anytime!

Understanding Image Tags

What is a tag?

Image Name Format:
IMAGE_NAME:TAG

Examples:
├── nginx:latest       ← Latest version (default)
├── nginx:1.25         ← Specific version 1.25
├── nginx:alpine       ← Lightweight version
└── nginx:1.25-alpine  ← Version 1.25, lightweight

If you don't specify tag:
docker pull nginx
        ↓
Automatically uses: nginx:latest

Try pulling different versions:

# Pull specific version
docker pull nginx:alpine

# Pull another popular image
docker pull ubuntu

# Pull Python
docker pull python:3.11

Command 2: docker images (List Downloaded Images)

Now let's see what images we have!

docker images

Output you'll see:

REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
nginx         latest    605c77e624dd   2 weeks ago    141MB
nginx         alpine    8e75cbc5b25c   2 weeks ago    41MB
ubuntu        latest    ba6acccedd29   3 weeks ago    77.8MB
python        3.11      a5d7930b60cc   4 weeks ago    1.01GB
hello-world   latest    feb5d9fea6a5   2 years ago    13.3kB

Understanding the columns:

REPOSITORY = Image name (nginx, ubuntu, etc.)
TAG        = Version (latest, alpine, 3.11)
IMAGE ID   = Unique identifier (first 12 chars of hash)
CREATED    = When this image was built
SIZE       = How much space it takes on disk

Example:
nginx:latest = 141MB (full featured)
nginx:alpine = 41MB (lightweight, only essentials)

Notice:

  • hello-world is tiny (13.3kB!)
  • Python is large (1.01GB - includes entire Python environment)
  • Alpine versions are smaller (minimal Linux)

Command 3: docker run (Start a Container)

This is the most important command! Let's run nginx web server.

Basic docker run

docker run nginx

What happens:

/docker-entrypoint.sh: Configuration complete
nginx: [notice] starting nginx process

← Terminal seems "stuck"
← This is NORMAL! Container is running
← Nginx is working in the foreground

To stop it:

Press Ctrl + C

You'll see:
nginx: signal process terminated
← Container stopped

Problem: Can't Access the Web Server

You ran nginx, but if you open browser and go to http://localhost, nothing appears!

Why?

Container is running in ISOLATION:
├── Nginx is running inside container
├── Container has its own network
├── Port 80 inside container
└── Your computer can't access it!

Like having a shop inside a locked building:
├── Shop is open (nginx running)
├── But door is locked (no port mapping)
└── Customers can't enter!

Solution: Port Mapping

We need to "open the door" - map container port to your computer port.

Stop the previous container (Ctrl + C if still running)

Run with port mapping:

docker run -p 8080:80 nginx

Understanding -p flag:

-p 8080:80

Format: -p HOST_PORT:CONTAINER_PORT

8080 = Port on YOUR computer (host)
80   = Port inside container

Meaning:
├── Traffic to localhost:8080 (your computer)
│       ↓
└── Goes to port 80 inside container (nginx)

Visual:

Your Computer                Container
┌────────────────┐          ┌──────────┐
│                │          │          │
│  Port 8080 ────┼─────────►│ Port 80  │
│                │  mapped  │          │
│  Browser       │          │  Nginx   │
│  localhost:8080│          │          │
└────────────────┘          └──────────┘

Test the Web Server

With container running (terminal looks "stuck"):

Open your web browser

Go to: http://localhost:8080

You should see:
┌─────────────────────────────────┐
│  Welcome to nginx!              │
│                                 │
│  If you see this page, the      │
│  nginx web server is            │
│  successfully installed and     │
│  working.                       │
└─────────────────────────────────┘

🎉 Congratulations! Your nginx container is running!

What just happened?

You in browser:
├── Visit localhost:8080
│       ↓
Your computer:
├── Port 8080 receives request
│       ↓
Docker:
├── Forwards to container port 80
│       ↓
Nginx in container:
├── Receives request
├── Sends back HTML page
│       ↓
Your browser:
└── Displays the page!

Running Containers in Background (Detached Mode)

Problem: Terminal is "stuck" - you can't use it while container runs.

Solution: Run container in background (detached mode)

Stop current container (Ctrl + C)

Run in detached mode:

docker run -d -p 8080:80 nginx

Understanding -d flag:

-d = Detached mode (background)

Output:
a3c5b8f9e1d7c2a4b6e8f0d1c3a5b7e9f1d3c5a7b9e1f3d5c7a9b1e3f5d7c9

↑ This is the Container ID

What happened:

Container started:
├── Running in background ✓
├── Terminal is free to use ✓
├── Container ID displayed
└── Nginx still working at localhost:8080

Check browser:
http://localhost:8080
← Still works! ✓

Command 4: docker ps (List Running Containers)

Let's see our running containers!

docker ps

Output:

CONTAINER ID   IMAGE    COMMAND                  CREATED          STATUS          PORTS                  NAMES
a3c5b8f9e1d7   nginx    "/docker-entrypoint.…"   30 seconds ago   Up 29 seconds   0.0.0.0:8080->80/tcp   eager_darwin

Understanding the columns:

CONTAINER ID = Unique ID (short version)
               a3c5b8f9e1d7

IMAGE        = Which image it's using
               nginx

COMMAND      = Command running inside
               /docker-entrypoint.sh nginx

CREATED      = When container was created
               30 seconds ago

STATUS       = Current state
               Up 29 seconds (running)

PORTS        = Port mapping
               0.0.0.0:8080->80/tcp
               (Your port 8080 → Container port 80)

NAMES        = Random name Docker gave
               eager_darwin
               (You can use this instead of ID)

Command 5: docker ps -a (List ALL Containers)

See all containers (including stopped ones):

docker ps -a

Output:

CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS                      PORTS                  NAMES
a3c5b8f9e1d7   nginx         "/docker-entrypoint.…"   2 minutes ago    Up 2 minutes               0.0.0.0:8080->80/tcp   eager_darwin
b4d6c9f1e3a5   nginx         "/docker-entrypoint.…"   5 minutes ago    Exited (0) 3 minutes ago                          romantic_curie
c7e9f2d4a6b8   hello-world   "/hello"                 10 minutes ago   Exited (0) 10 minutes ago                         clever_turing

Notice:

Running containers:
└── STATUS: Up 2 minutes

Stopped containers:
└── STATUS: Exited (0) X minutes ago
            ↑
         Exit code (0 = normal exit)

Command 6: docker stop (Stop a Running Container)

Let's stop our nginx container.

Method 1: Using Container ID

docker stop a3c5b8f9e1d7

# You don't need full ID, first few characters work:
docker stop a3c5

Method 2: Using Container Name

docker stop eager_darwin

Output:

a3c5b8f9e1d7
← Container ID returned

Container stopped ✓

Verify it stopped:

docker ps

# No containers shown (none running)

docker ps -a

# Shows container with STATUS: Exited

Try accessing in browser:

http://localhost:8080
← Connection refused (container stopped)

Command 7: docker start (Restart a Stopped Container)

Start the stopped container again:

docker start a3c5

# Or using name:
docker start eager_darwin

Output:

a3c5b8f9e1d7
← Container started

Check it's running:

docker ps

# Container appears in list again

Browser test:

http://localhost:8080
← Works again! ✓

Command 8: docker logs (See Container Output)

See what's happening inside a container:

docker logs a3c5

Output (nginx logs):

/docker-entrypoint.sh: Configuration complete
172.17.0.1 - - [19/Feb/2026:10:30:15 +0000] "GET / HTTP/1.1" 200 615
172.17.0.1 - - [19/Feb/2026:10:30:16 +0000] "GET /favicon.ico HTTP/1.1" 404 153

Understanding logs:

Each line = One request to nginx:

172.17.0.1 = Your IP address
GET / = Requested homepage
HTTP/1.1 = HTTP protocol version
200 = Success status code
615 = Response size (bytes)

Follow logs in real-time:

docker logs -f a3c5

# -f = follow (like tail -f)
# New logs appear as they happen
# Press Ctrl + C to stop following

Now refresh browser (http://localhost:8080) and watch logs appear live!


Command 9: docker exec (Execute Commands Inside Container)

Run commands inside a running container.

Example: Access nginx container's shell

docker exec -it a3c5 /bin/bash

Understanding the flags:

-i = Interactive (keep connection open)
-t = TTY (gives you a terminal)
/bin/bash = Command to run (bash shell)

What happens:

Your terminal changes:
root@a3c5b8f9e1d7:/#
↑                ↑
root user    container ID

You're now INSIDE the container! ✓

Try some commands inside container:

# See where you are
pwd
# Output: /

# List files
ls
# Output: bin  boot  dev  etc  home  lib  ...

# Check nginx is running
ps aux | grep nginx
# Shows nginx processes

# See nginx config
cat /etc/nginx/nginx.conf

# Exit container
exit
# Back to your normal terminal

Command 10: docker rm (Remove Container)

Delete a stopped container.

First, stop the container if running:

docker stop a3c5

Then remove it:

docker rm a3c5

Output:

a3c5b8f9e1d7
← Container removed

Verify:

docker ps -a
# Container is gone from the list

Remove multiple containers:

docker rm container1 container2 container3

Force remove (stop + remove):

docker rm -f a3c5

# -f = force (stops and removes in one command)

Command 11: docker rmi (Remove Image)

Delete a downloaded image.

Important: You must remove all containers using this image first!

# Remove nginx image
docker rmi nginx

# Or using image ID:
docker rmi 605c77e624dd

If image is in use:

Error: image is being used by stopped container

Solution:
1. docker ps -a  (find containers using this image)
2. docker rm CONTAINER_ID  (remove those containers)
3. docker rmi nginx  (now you can remove image)

Remove multiple images:

docker rmi nginx ubuntu python

Running a Different Container - Ubuntu

Let's try running Ubuntu Linux!

docker run -it ubuntu

Understanding -it flags:

-i = Interactive
-t = TTY (terminal)
-it together = Interactive terminal session

What happens:

Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
... downloading ...

root@b8d7c9f3e5a1:/#
↑
You're now inside Ubuntu container!

Try Linux commands:

# Check Ubuntu version
cat /etc/os-release

# Update package lists
apt-get update

# Install a program
apt-get install curl -y

# Use curl
curl https://www.google.com

# Exit
exit

When you exit:

Container stops automatically
(Because you exited the main process)

Giving Containers Custom Names

Instead of random names, give your own:

docker run -d -p 8080:80 --name my-nginx nginx

Now:

docker ps

NAMES
my-nginx  ← Your custom name!

# Use it in commands:
docker stop my-nginx
docker start my-nginx
docker logs my-nginx
docker rm my-nginx

Much easier to remember!


Running Multiple Containers

Run multiple nginx containers on different ports:

# Container 1 on port 8080
docker run -d -p 8080:80 --name nginx1 nginx

# Container 2 on port 8081
docker run -d -p 8081:80 --name nginx2 nginx

# Container 3 on port 8082
docker run -d -p 8082:80 --name nginx3 nginx

Check all running:

docker ps

Access them:

http://localhost:8080  ← nginx1
http://localhost:8081  ← nginx2
http://localhost:8082  ← nginx3

All running simultaneously! ✓

Summary of Basic Commands

Commands we learned:

# Download images
docker pull IMAGE_NAME

# List images
docker images

# Run container
docker run IMAGE_NAME
docker run -d IMAGE_NAME           # Background
docker run -p HOST:CONTAINER IMAGE # Port mapping
docker run -it IMAGE_NAME          # Interactive
docker run --name NAME IMAGE       # Custom name

# List containers
docker ps          # Running only
docker ps -a       # All (including stopped)

# Stop container
docker stop CONTAINER_ID_OR_NAME

# Start stopped container
docker start CONTAINER_ID_OR_NAME

# View logs
docker logs CONTAINER_ID_OR_NAME
docker logs -f CONTAINER_ID        # Follow real-time

# Execute command in container
docker exec -it CONTAINER_ID COMMAND

# Remove container
docker rm CONTAINER_ID_OR_NAME
docker rm -f CONTAINER_ID          # Force remove

# Remove image
docker rmi IMAGE_NAME

Practice Exercise

Let's practice what we learned!

Try this yourself:

# 1. Pull Python image
docker pull python:3.11

# 2. Run Python container interactively
docker run -it --name my-python python:3.11

# Inside container, try:
python --version
print("Hello from Docker!")
exit()

# 3. Exit container (Ctrl+D or exit())

# 4. List all containers
docker ps -a

# 5. Remove the container
docker rm my-python

# 6. List images
docker images

# 7. Remove Python image
docker rmi python:3.11

Common Patterns You'll Use

Pattern 1: Quick Test

# Run, test, remove
docker run --rm IMAGE_NAME

# --rm = Automatically remove after stop

Pattern 2: Development

# Run with name, port, background
docker run -d -p 8080:80 --name dev-server nginx

Pattern 3: Debugging

# Check logs
docker logs my-container

# Access shell
docker exec -it my-container /bin/bash

Pattern 4: Cleanup

# Stop all running containers
docker stop $(docker ps -q)

# Remove all stopped containers
docker rm $(docker ps -a -q)

# Remove all images
docker rmi $(docker images -q)

Key Takeaways

Remember:

Image vs Container:
├── Image = Blueprint (static)
│   └── Like a recipe
│
└── Container = Running instance (active)
    └── Like the actual cooked dish

You can create multiple containers from one image!

Container Lifecycle:

pull → run → running → stop → stopped → start → running
                                    ↓
                                   rm (remove)

Port Mapping:

ALWAYS use -p for web servers:
-p 8080:80
   ↑    ↑
   │    └── Port inside container
   └── Port on your computer

Excellent work! You now know the basic Docker commands!

You've learned to: ✅ Pull images from Docker Hub ✅ Run containers (foreground and background) ✅ Map ports ✅ List containers and images ✅ Stop/start containers ✅ View logs ✅ Execute commands inside containers ✅ Remove containers and images


Installation & First Steps

Prerequisites Check (Before Installing)

Before installing Docker Desktop, your Windows 11 needs to meet certain requirements.

Check 1: Windows Version

You need:

  • Windows 11 (64-bit) ✓ (You have this!)
  • Or Windows 10 64-bit: Pro, Enterprise, or Education (Build 19041 or higher)

To check your Windows version:

Step 1: Press Windows Key + R
Step 2: Type: winver
Step 3: Press Enter

You'll see a window showing:
- Version (should be Windows 11)
- Build number

You have Windows 11, so this is ✓


Check 2: System Requirements

Your computer needs:

Minimum Requirements:
├── 64-bit processor ✓
├── 4GB RAM (8GB recommended)
├── BIOS-level hardware virtualization support
└── WSL 2 (Windows Subsystem for Linux)

To check if virtualization is enabled:

Step 1: Press Ctrl + Shift + Esc (Open Task Manager)
Step 2: Click "Performance" tab
Step 3: Click "CPU"
Step 4: Look at bottom right

You should see:
"Virtualization: Enabled" ✓

If it says "Disabled":
└── You need to enable it in BIOS

Is virtualization enabled on your system?

  • If YES, continue below
  • If NO, You need to enable it in BIOS.

Installing Docker Desktop on Windows 11

Step 1: Download Docker Desktop

Option A: Direct Download (Recommended)

1. Open your web browser

2. Go to: https://www.docker.com/products/docker-desktop/

3. Click the big blue button: "Download for Windows"

4. File will download: "Docker Desktop Installer.exe"
   (Size: ~500MB, takes 2-5 minutes depending on internet)

Option B: From Docker Hub

1. Go to: https://hub.docker.com/

2. Click "Download Docker Desktop"

3. Choose "Windows"

4. Download starts

Step 2: Install Docker Desktop

Once download is complete:

Step 1: Locate the downloaded file
├── Usually in: Downloads folder
└── File name: "Docker Desktop Installer.exe"

Step 2: Double-click the installer
├── Windows might ask: "Do you want to allow this app to make changes?"
└── Click "Yes"

Step 3: Installation wizard opens
You'll see: "Docker Desktop Installer"

Step 4: Configuration options
You'll see two checkboxes:

[✓] Use WSL 2 instead of Hyper-V (recommended)
    └── Keep this CHECKED ✓

[✓] Add shortcut to desktop
    └── Optional (your choice)

Step 5: Click "Ok" or "Install"

Step 6: Installation begins
├── Progress bar appears
├── Takes 3-5 minutes
├── Installing components:
│   ├── Docker Engine
│   ├── Docker CLI
│   ├── Docker Compose
│   └── WSL 2 (if not already installed)

Step 7: Installation completes
└── You'll see: "Installation succeeded"

Step 8: Click "Close"

Step 3: First Time Setup

After installation:

Step 1: Docker Desktop will start automatically
├── If not, find Docker Desktop icon on desktop
└── Or search "Docker Desktop" in Start menu

Step 2: First launch screen
You'll see Docker Desktop loading:
"Starting Docker Desktop..."
├── This takes 1-2 minutes first time
└── Docker whale icon in system tray (bottom right)

Step 3: Service Agreement
├── Docker may show terms of service
└── Click "Accept" (if you agree)

Step 4: Welcome screen (might appear)
├── Quick tutorial option
├── You can skip it for now
└── Click "Skip tutorial" or close

Step 5: Check if Docker is running
Look at system tray (bottom right of taskbar):
├── You should see Docker whale icon
├── If green/white = Docker is running ✓
└── If red/gray = Docker is not running ✗

Step 4: Verify Installation

Let's make sure Docker is installed correctly!

Open Command Prompt or PowerShell:

Method 1: Using Search
├── Press Windows Key
├── Type: cmd
├── Click "Command Prompt"

Method 2: Using Run
├── Press Windows Key + R
├── Type: cmd
├── Press Enter

Method 3: PowerShell
├── Press Windows Key + X
├── Click "Windows PowerShell"

Run verification commands:

# Check Docker version
docker --version

Expected output:
Docker version 24.0.x, build xxxxxxx
(Version number might be different - that's okay!)

# Check Docker is running
docker info

Expected output:
Client:
 Version:    24.0.x
 Context:    desktop-linux
 ...
Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 0
 ...

# If you see this information, Docker is installed correctly! ✓

If you get an error:

Error: "docker is not recognized..."

Solution:
├── Docker Desktop might not be running
├── Go to Start menu
├── Search "Docker Desktop"
├── Open it
└── Wait 1-2 minutes for it to start

Then try commands again

Understanding Docker Desktop Interface

Once Docker Desktop is running, let's explore it:

Docker Desktop Window has:

Top Menu Bar:
├── Containers (manage running containers)
├── Images (see downloaded images)
├── Volumes (data storage)
├── Dev Environments (advanced)
└── Settings (configuration)

Main Screen:
├── Quick Start Guide
├── Recently used containers
└── Learning resources

System Tray Icon (bottom right):
├── Right-click whale icon
├── Options:
│   ├── Dashboard (opens main window)
│   ├── Settings
│   ├── Restart Docker
│   ├── Quit Docker Desktop
│   └── About Docker Desktop

WSL 2 Setup (Important!)

Docker Desktop on Windows uses WSL 2 (Windows Subsystem for Linux).

Check if WSL 2 is installed:

Open PowerShell as Administrator:

Step 1: Press Windows Key
Step 2: Type: PowerShell
Step 3: Right-click "Windows PowerShell"
Step 4: Click "Run as administrator"
Step 5: Click "Yes" when prompted

Check WSL version:

wsl --list --verbose

Expected output:
  NAME                   STATE           VERSION
* docker-desktop         Running         2
  docker-desktop-data    Running         2

If you see VERSION 2, you're good! ✓

If WSL 2 is not installed (you get an error):

# Install WSL 2
wsl --install

# This will:
├── Download WSL 2
├── Install Ubuntu (default Linux)
├── Takes 5-10 minutes
└── Restart required

# After restart:
├── Ubuntu will finish setup
├── Create username/password (remember these!)
└── Then Docker Desktop will work

Test Docker Installation

Let's run your FIRST Docker command!

Open Command Prompt or PowerShell (regular, not admin):

docker run hello-world

What happens:

Step-by-step output you'll see:

1. Unable to find image 'hello-world:latest' locally
   └── Docker: "I don't have this image on your computer"

2. latest: Pulling from library/hello-world
   └── Docker: "I'm downloading it from Docker Hub"

3. xxxxxxxxx: Pull complete
   └── Docker: "Download finished"

4. Digest: sha256:xxxxxx
   Status: Downloaded newer image for hello-world:latest
   └── Docker: "Image is ready"

5. Hello from Docker!
   This message shows that your installation appears to be working correctly.
   
   To generate this message, Docker took the following steps:
   1. The Docker client contacted the Docker daemon.
   2. The Docker daemon pulled the "hello-world" image from Docker Hub.
   3. The Docker daemon created a new container from that image.
   4. The Docker daemon streamed that output to the Docker client.
   
   └── Docker: "Everything works! ✓"

If you see "Hello from Docker!" - Congratulations! 🎉 Docker is installed and working perfectly!


Common Installation Issues

Issue 1: "Docker Desktop requires a newer version of Windows"

Solution:
├── Update Windows 11
├── Go to: Settings > Windows Update
├── Click "Check for updates"
└── Install all updates

Issue 2: "Hardware assisted virtualization is not enabled"

Solution:
├── Restart computer
├── Enter BIOS (press F2, F10, Del, or F12 during boot)
├── Find "Virtualization Technology" or "VT-x"
├── Enable it
└── Save and exit BIOS

Issue 3: "WSL 2 installation is incomplete"

Solution:
Open PowerShell as admin:
wsl --update
wsl --set-default-version 2

Restart Docker Desktop

Issue 4: "Docker Desktop is starting..." (forever)

Solution 1:
├── Quit Docker Desktop completely
├── Restart computer
└── Open Docker Desktop again

Solution 2:
├── Uninstall Docker Desktop
├── Restart computer
└── Install again

Docker Desktop Settings (Optional Configuration)

Open Docker Desktop Settings:

Click Docker whale icon in system tray
→ Click "Settings" (gear icon)

Recommended Settings:

General:
[✓] Start Docker Desktop when you log in
    └── Auto-start Docker (convenient)

[✓] Use the WSL 2 based engine
    └── Better performance

Resources:
├── CPUs: 2 (default is fine for learning)
├── Memory: 2GB (can increase if you have 16GB+ RAM)
└── Disk image size: 60GB (default)

Docker Engine:
└── Leave as default (for now)

Quick Start Guide

Now that Docker is installed, here's what you can do:

✓ Docker is installed
✓ Docker is running
✓ You ran your first container (hello-world)

Next steps (we'll do together):
├── Learn basic Docker commands
├── Pull some images
├── Run containers
└── Explore Docker

Verification Checklist

Make sure everything is working:

□ Docker Desktop installed
□ Docker Desktop is running (green icon in system tray)
□ docker --version works in Command Prompt
□ docker info shows information
□ docker run hello-world completed successfully
□ You saw "Hello from Docker!" message

If all checked ✓ → Installation successful! 🎉

What We Installed

Docker Desktop for Windows includes:

Components:
├── Docker Engine (core)
├── Docker CLI (command-line interface)
├── Docker Compose (multi-container tool)
├── Docker Content Trust (security)
├── Kubernetes (optional, advanced)
└── WSL 2 backend (Linux compatibility)

You can now:
├── Run Linux containers on Windows
├── Use all Docker commands
├── Build and deploy applications
└── Learn Docker!

Excellent! Docker is now installed on your Windows 11 system!


Docker Architecture Basics

Let's understand HOW Docker actually works behind the scenes. What are the different components and how do they work together?


Overview - The Big Picture

Simple Analogy First:

Think of Docker like a Restaurant System:

Restaurant (Docker System):
│
├── Customer (You/Developer)
│   └── Orders food (runs Docker commands)
│
├── Waiter (Docker Client)
│   └── Takes your order, brings food back
│
├── Kitchen Manager (Docker Daemon)
│   └── Receives orders, manages cooking
│
├── Chefs (Docker Engine)
│   └── Actually cook the food (run containers)
│
└── Food Supplier (Docker Registry/Hub)
    └── Provides ingredients (provides images)

Now let's understand each component in detail!


Component 1: Docker Client

What is Docker Client?

Simple Definition:

Docker Client = The interface you use to talk to Docker. It's like a remote control for Docker.

What It Does:

You (Developer):
├── Type commands on keyboard
├── "docker run nginx"
├── "docker build -t myapp ."
└── "docker ps"
        ↓
Docker Client:
├── Takes your commands
├── Translates them
├── Sends to Docker Daemon
└── Shows you the results

Real-Life Example:

Think of TV Remote Control:
│
├── You press buttons (give commands)
│       ↓
├── Remote sends signals (Docker Client)
│       ↓
├── TV receives signals (Docker Daemon)
│       ↓
└── TV changes channel (Action happens)

You don't directly touch the TV,
you use the remote!

Docker Client in Action

When you type a command:

$ docker run hello-world

What happens:

Step 1: You type in Terminal
Command: docker run hello-world
        ↓
Step 2: Docker Client receives it
Client thinks: "User wants to run 'hello-world' container"
        ↓
Step 3: Client sends request to Docker Daemon
Client says: "Hey Daemon, please run hello-world container"
        ↓
Step 4: Daemon does the work
Daemon runs the container
        ↓
Step 5: Client shows you the result
Output: "Hello from Docker!"

Visual Diagram:

┌─────────────────┐
│   Your Terminal │
│                 │
│  $ docker run   │
│    hello-world  │
└────────┬────────┘
         │
         │ Command
         ↓
┌─────────────────┐
│  Docker Client  │
│                 │
│  - Parses cmd   │
│  - Validates    │
│  - Sends to     │
│    daemon       │
└────────┬────────┘
         │
         │ API Call
         ↓
┌─────────────────┐
│  Docker Daemon  │
│                 │
│  - Receives     │
│  - Executes     │
│  - Returns      │
│    result       │
└────────┬────────┘
         │
         │ Result
         ↓
┌─────────────────┐
│   Your Terminal │
│                 │
│  Output shown   │
└─────────────────┘

Important Points About Docker Client

1. The CLI (Command Line Interface):

This is the Docker Client:
$ docker <command>

Examples:
$ docker run nginx        ← Docker Client command
$ docker ps              ← Docker Client command
$ docker build .         ← Docker Client command
$ docker stop myapp      ← Docker Client command

2. Can Be Remote:

Docker Client can be on different computer:

Your Laptop (Client):
$ docker run myapp
        ↓
        │ Internet
        ↓
Remote Server (Daemon):
└── Actually runs the container

You control remote Docker from your laptop!

3. Different Interfaces:

Ways to use Docker Client:
├── Command Line (Terminal) ← Most common
├── Docker Desktop (GUI)    ← Visual interface
├── Docker API (Code)       ← From programs
└── Third-party tools       ← Portainer, etc.

All talk to Docker Daemon!

Component 2: Docker Daemon (dockerd)

What is Docker Daemon?

Simple Definition:

Docker Daemon = The background service that does all the actual work. The "brain" of Docker.

What It Does:

Docker Daemon (Background Process):
├── Listens for commands from Client
├── Manages containers (create, start, stop)
├── Manages images (build, pull, push)
├── Manages networks
├── Manages volumes
└── Does ALL the heavy lifting!

Real-Life Example:

Think of a Power Plant:
│
├── You flip light switch (Docker Client)
│       ↓
├── Signal goes to power plant (Docker Daemon)
│       ↓
├── Power plant generates electricity
│       ↓
└── Your light turns on

You don't see the power plant working,
but it's doing all the work!

Docker Daemon Responsibilities

1. Container Lifecycle Management:

Docker Daemon manages:

Creating containers:
$ docker run nginx
        ↓
Daemon: "I'll create nginx container"
        ↓
Container created ✓

Starting/Stopping:
$ docker stop nginx
        ↓
Daemon: "I'll stop nginx container"
        ↓
Container stopped ✓

Removing:
$ docker rm nginx
        ↓
Daemon: "I'll remove nginx container"
        ↓
Container removed ✓

2. Image Management:

Docker Daemon handles:

Pulling images:
$ docker pull ubuntu
        ↓
Daemon: "I'll download ubuntu from Docker Hub"
        ↓
Image downloaded ✓

Building images:
$ docker build -t myapp .
        ↓
Daemon: "I'll read Dockerfile and build image"
        ↓
Image built ✓

Storing images:
Daemon keeps all images on disk
Ready for use anytime

3. Network Management:

Docker Daemon creates networks:

Default network:
Daemon automatically creates bridge network

Custom networks:
$ docker network create mynetwork
        ↓
Daemon creates isolated network ✓

Connecting containers:
Daemon connects containers to networks
So they can talk to each other

4. Volume Management:

Docker Daemon manages storage:

Creating volumes:
$ docker volume create mydata
        ↓
Daemon creates storage space ✓

Mounting volumes:
$ docker run -v mydata:/app/data nginx
        ↓
Daemon mounts volume to container ✓

Docker Daemon Process

Where It Runs:

Background Process (Always Running):

Linux:
systemctl status docker
● docker.service - Docker Application Container Engine
   Active: active (running)

Windows/Mac (Docker Desktop):
Docker Desktop app manages daemon
Daemon runs in VM in background

Check if running:
$ docker info
If you see output, daemon is running ✓

How It Communicates:

Docker Daemon listens on:

Unix Socket (Local):
/var/run/docker.sock
        ↑
Docker Client connects here (by default)

TCP Port (Remote):
Port 2375 (unsecured) or 2376 (secured)
        ↑
Remote clients can connect here

Component 3: Docker Engine

What is Docker Engine?

Simple Definition:

Docker Engine = The complete Docker system. It includes the Daemon plus all the underlying technology that makes containers work.

Think of it as:

Docker Engine = Complete Package:
│
├── Docker Daemon (Main process)
├── containerd (Container runtime)
├── runc (Low-level container executor)
└── All supporting components

Like a car engine:
├── Pistons (Daemon)
├── Fuel system (containerd)
├── Spark plugs (runc)
└── Everything working together

Docker Engine Components (Detailed)

The Layers:

                    ┌──────────────────┐
                    │  Docker Client   │
                    └────────┬─────────┘
                             │
                             ↓
┌─────────────────────────────────────────────┐
│          Docker Engine                       │
│                                              │
│  ┌────────────────────────────────────┐    │
│  │      Docker Daemon (dockerd)        │    │
│  │  - High-level operations            │    │
│  │  - API server                       │    │
│  │  - Image management                 │    │
│  └──────────────┬─────────────────────┘    │
│                 │                            │
│                 ↓                            │
│  ┌────────────────────────────────────┐    │
│  │      containerd                     │    │
│  │  - Container lifecycle              │    │
│  │  - Image distribution               │    │
│  └──────────────┬─────────────────────┘    │
│                 │                            │
│                 ↓                            │
│  ┌────────────────────────────────────┐    │
│  │      runc                           │    │
│  │  - Actually creates containers      │    │
│  │  - Low-level operations             │    │
│  └─────────────────────────────────────┘   │
│                                              │
└──────────────────┬───────────────────────────┘
                   │
                   ↓
            ┌──────────────┐
            │  Containers   │
            └──────────────┘

What Each Layer Does:

1. Docker Daemon (dockerd) - Top Layer:

High-level manager:
├── Receives commands from Client
├── "User wants to run nginx"
├── Passes request down to containerd
└── Returns results to Client

2. containerd - Middle Layer:

Container supervisor:
├── Receives from dockerd
├── "Okay, I'll manage nginx container"
├── Handles image pulling
├── Passes to runc for actual creation
└── Monitors container lifecycle

3. runc - Bottom Layer:

Container creator:
├── Receives from containerd
├── "I'll create the actual container now"
├── Uses Linux kernel features (namespaces, cgroups)
├── Actually spawns the container process
└── Container is now running!

Example Flow - Starting a Container

When you run:

$ docker run nginx

Complete flow through Docker Engine:

Step 1: Docker Client
You type: docker run nginx
Client sends: "Run nginx container" to Daemon

Step 2: Docker Daemon (dockerd)
Daemon receives: "Run nginx container"
Daemon checks: "Do I have nginx image?"
        ├─ Yes → Continue to step 3
        └─ No → Download from Docker Hub first

Step 3: Daemon → containerd
Daemon tells containerd: "Create nginx container"
containerd prepares: Image, network, volumes

Step 4: containerd → runc
containerd tells runc: "Spawn the container process"
runc creates: Linux namespaces, cgroups
runc starts: nginx process inside container

Step 5: Container Running
nginx container is now running! ✓
        ↓
Result sent back up:
runc → containerd → dockerd → client → you

You see: "Container started successfully"

Component 4: Docker Registry (Docker Hub)

What is Docker Registry?

Simple Definition:

Docker Registry = A storage place for Docker images. Like GitHub for code, but for Docker images.

Simple Analogy:

Think of it like an App Store:

Apple App Store:
├── Stores apps
├── You download apps
├── Developers upload apps
└── Everyone shares apps

Docker Registry (Docker Hub):
├── Stores Docker images
├── You download (pull) images
├── Developers upload (push) images
└── Everyone shares images

Docker Hub (Default Registry)

What is Docker Hub?

Docker Hub = Official Docker Registry:
├── hub.docker.com
├── Free public registry
├── Millions of images available
├── Official images from companies
└── Community images from developers

Popular Images on Docker Hub:

Official Images:
├── nginx - Web server
├── mysql - Database
├── python - Python environment
├── node - Node.js environment
├── ubuntu - Ubuntu OS
├── redis - Cache database
└── postgres - Database

All free to download and use!

How Registry Works

1. Pulling Images (Downloading):

You want nginx image:

$ docker pull nginx
        ↓
Docker Client: "Get nginx from registry"
        ↓
Docker Daemon: "I'll download it"
        ↓
Docker Hub (Registry):
└── "Here's nginx image" → Downloads to your computer
        ↓
Stored locally: Ready to use!

Now you can run:
$ docker run nginx

Visual Flow:

┌─────────────────────┐
│   Docker Hub        │
│   (Registry)        │
│                     │
│  • nginx image      │
│  • ubuntu image     │
│  • python image     │
└──────────┬──────────┘
           │
           │ docker pull nginx
           ↓
┌─────────────────────┐
│   Your Computer     │
│                     │
│  • nginx image ✓    │ ← Downloaded
│                     │
│  Can now run:       │
│  docker run nginx   │
└─────────────────────┘

2. Pushing Images (Uploading):

You created custom image:

$ docker build -t myusername/myapp .
        ↓
Image built locally ✓

$ docker push myusername/myapp
        ↓
Docker Daemon: "I'll upload to registry"
        ↓
Docker Hub (Registry):
└── "myapp received" → Stored on Docker Hub
        ↓
Now others can download:
$ docker pull myusername/myapp

Registry Types

1. Docker Hub (Public):

├── Free tier available
├── Public images (anyone can download)
├── Private images (paid, only you can access)
└── Most commonly used

2. Private Registries:

Companies run their own:
├── Amazon ECR (AWS)
├── Google Container Registry
├── Azure Container Registry
├── Self-hosted registries
└── For private/company images

3. Alternative Registries:

├── Quay.io
├── GitHub Container Registry
└── GitLab Container Registry

How All Components Work Together

Complete Flow Example

Scenario: You want to run nginx web server

STEP 1: You give command
┌──────────────────┐
│  Your Terminal   │
│                  │
│ $ docker run     │
│   nginx          │
└────────┬─────────┘
         │
         │ ① Command typed
         ↓

STEP 2: Docker Client processes
┌──────────────────┐
│  Docker Client   │
│                  │
│ • Parses command │
│ • Validates      │
│ • Sends to       │
│   daemon         │
└────────┬─────────┘
         │
         │ ② API request
         ↓

STEP 3: Docker Daemon checks
┌──────────────────┐
│  Docker Daemon   │
│                  │
│ "Do I have       │
│  nginx image?"   │
│                  │
│ • Checks local   │
│   storage        │
└────────┬─────────┘
         │
         │ ③ Image check
         ↓

STEP 4: If not found, pull from registry
┌──────────────────┐
│  Docker Hub      │
│  (Registry)      │
│                  │
│ • Sends nginx    │
│   image          │
└────────┬─────────┘
         │
         │ ④ Download image
         ↓

STEP 5: Daemon tells containerd
┌──────────────────┐
│  containerd      │
│                  │
│ • Prepares       │
│   container      │
│ • Sets up        │
│   resources      │
└────────┬─────────┘
         │
         │ ⑤ Create request
         ↓

STEP 6: runc creates container
┌──────────────────┐
│  runc            │
│                  │
│ • Uses Linux     │
│   features       │
│ • Spawns         │
│   process        │
└────────┬─────────┘
         │
         │ ⑥ Container created
         ↓

STEP 7: Container running!
┌──────────────────┐
│  nginx Container │
│                  │
│ • Running        │
│ • Serving web    │
│   pages          │
└──────────────────┘

Architecture Summary Diagram

                    YOU (Developer)
                         │
                         │ Types commands
                         ↓
        ┌────────────────────────────────┐
        │      DOCKER CLIENT             │
        │  (CLI, Desktop, API)           │
        └────────────┬───────────────────┘
                     │
                     │ REST API calls
                     ↓
        ┌────────────────────────────────┐
        │      DOCKER DAEMON             │
        │  - High-level management       │
        │  - API server                  │
        │  - Image management            │
        └────────────┬───────────────────┘
                     │
           ┌─────────┴─────────┐
           │                   │
           ↓                   ↓
    ┌──────────┐      ┌───────────────┐
    │containerd│      │  DOCKER HUB   │
    │          │      │  (Registry)   │
    │ Container│←─────│  Image Store  │
    │ Runtime  │ Pull │               │
    └────┬─────┘      └───────────────┘
         │
         ↓
    ┌─────────┐
    │  runc   │
    │         │
    │ Creates │
    │Container│
    └────┬────┘
         │
         ↓
    ┌─────────────────────────────┐
    │     CONTAINERS              │
    │  ┌────┐ ┌────┐ ┌────┐      │
    │  │ C1 │ │ C2 │ │ C3 │      │
    │  └────┘ └────┘ └────┘      │
    └─────────────────────────────┘
         │
         ↓
    ┌─────────────────────────────┐
    │     HOST OPERATING SYSTEM   │
    │     (Linux Kernel)          │
    └─────────────────────────────┘
         │
         ↓
    ┌─────────────────────────────┐
    │     HARDWARE                │
    │  (CPU, RAM, Disk, Network)  │
    └─────────────────────────────┘

Key Takeaways

Four Main Components:

1. Docker Client
   └── Your interface to Docker (CLI/GUI)

2. Docker Daemon
   └── The background service doing the work

3. Docker Engine
   └── Complete system (Daemon + containerd + runc)

4. Docker Registry
   └── Storage for Docker images (Docker Hub)

How They Work Together:

You → Client → Daemon → Engine → Container
                 ↕
              Registry (for images)

Remember:

  • Client = What you interact with
  • Daemon = The brain doing the work
  • Engine = The complete machinery
  • Registry = Where images are stored

Complete Beginner Guide: Deploy HTML/CSS/JS Website with Docker, Docker Hub, AWS & CI/CD

WHAT WE ARE BUILDING You will write a simple website (HTML + CSS + JS), put it inside Docker, upload it to Docker Hub, host it on AWS, and s...