Page cover

dockerDocker Guide, Labs & Cheatsheet: From Zero to Container Hero

This guide serves two purposes: a comprehensive classroom walkthrough for Docker fundamentals, and a standalone reference you can revisit anytime. We start with the "why" before the "how"!

Table of Contents

  1. Core Conceptsarrow-up-right - The fundamental building blocks

What is Docker and Why Does It Exist?

Before Docker, deploying software was painful. Developers would write code on their machines, and when it moved to a test or production server, things would break. "But it works on my machine!" became the most frustrating phrase in software development.

The Problem Docker Solves:

Imagine you build a Python web application. It requires:

  • Python 3.11 (not 3.10, not 3.12 - exactly 3.11)

  • PostgreSQL client libraries

  • Specific versions of Flask, SQLAlchemy, and 15 other packages

  • Certain environment variables configured correctly

  • A specific folder structure

Now imagine deploying this to 50 servers. Or handing it to a colleague with a different operating system. Every difference in environment becomes a potential bug.

Docker's Solution: Containers

Docker packages your application with everything it needs to run - the code, runtime, libraries, and system tools - into a single, portable unit called a container. This container runs identically everywhere: your laptop, your colleague's Mac, a Linux server in the cloud, or a Kubernetes cluster.

Think of it like shipping. Before standardized shipping containers, loading a cargo ship was chaotic - different sized boxes, crates, and barrels. The invention of the standard shipping container revolutionized global trade. Docker does the same for software.

Containers vs Virtual Machines:

You might wonder: "Can't I just use a virtual machine?" You can, but containers are fundamentally different:

Aspect
Virtual Machine
Container

What it includes

Full OS (Windows, Linux)

Only app + dependencies

Size

Gigabytes

Megabytes

Startup time

Minutes

Seconds

Resource usage

Heavy (runs full OS)

Lightweight (shares host kernel)

Isolation

Complete (separate kernel)

Process-level (shared kernel)

Containers share the host operating system's kernel, making them incredibly efficient. You can run dozens of containers on a laptop that would struggle with 3 virtual machines.

When to Use Docker:

  • Consistent development environments across a team

  • Microservices architecture (multiple small services instead of one monolith)

  • CI/CD pipelines (test in the same environment as production)

  • Easy scaling (spin up more containers when traffic increases)

  • Dependency isolation (different apps can use different versions of the same library)

Part 1: Core Concepts

Before we start running commands, let's understand the key concepts. Docker has its own vocabulary, and confusing these terms leads to confusion later.

Image vs Container - The Most Important Distinction

If you confuse these two terms, everything else in Docker becomes confusing. Take time to internalize this:

circle-info

An image is a read-only template containing everything needed to run an application: operating system files, application code, dependencies, and configuration.

Think of it like a class definition in programming, or a recipe in cooking. It doesn't "run" - it's a blueprint.

circle-info

A container is a running instance of an image. When you "run" an image, Docker creates a container from it.

Think of it like an object instantiated from a class, or a dish made from a recipe. You can create many containers from one image.

The container adds a thin writable layer on top of the image layers. Any files you create or modify inside the container go into this writable layer. When you delete the container, this layer is gone - that's why containers are called "ephemeral."

Try these commands to see the difference:

Dockerfile - The Recipe for Building Images

A Dockerfile is a text file containing instructions that Docker executes step-by-step to build an image. You write the recipe once, and Docker follows it exactly every time.

Each instruction in a Dockerfile creates a new layer. Docker caches these layers, which speeds up subsequent builds - if a layer hasn't changed, Docker reuses the cached version instead of rebuilding it.

Here are the instructions you will use most frequently:

Instruction
Purpose
Example

FROM

Base image (always first)

FROM python:3.11-slim

RUN

Execute command during build

RUN pip install flask

COPY

Copy files from host to image

COPY app.py /app/

WORKDIR

Set working directory

WORKDIR /app

ENV

Set environment variable

ENV DEBUG=false

EXPOSE

Document which port app uses

EXPOSE 8080

USER

Switch to non-root user

USER appuser

ENTRYPOINT

Main command (fixed)

ENTRYPOINT ["python"]

CMD

Default arguments (overridable)

CMD ["app.py"]

The relationship between ENTRYPOINT and CMD confuses many people. Think of it this way:

ENTRYPOINT sets the executable that always runs. CMD provides default arguments that users can override. Together, they form the complete command that runs when a container starts.

Volumes - Making Data Persistent

Here's a common mistake beginners make: they store important data inside a container, delete the container, and lose everything. This happens because containers are ephemeral by design.

The Problem - Data Loss:

Let's see this problem in action:

This is exactly what happens with databases, user uploads, configuration files - anything written inside a container. When the container is removed, everything inside disappears.

The Solution - Volumes:

Volumes store data outside the container, on your host machine. The container accesses this data through a mount point, but the data lives independently of the container's lifecycle.

Docker provides three types of storage:

Type
Syntax
Use Case
Data Location

Named Volume

-v mydata:/app/data

Databases, persistent data

Managed by Docker

Bind Mount

-v /host/path:/container/path

Development (live code reload)

Exact folder on your computer

tmpfs

--tmpfs /tmp

Temporary/sensitive data

Only in memory (RAM)

Named Volumes - Best for Production Data:

Named volumes are managed by Docker. You don't need to know where they're physically stored - Docker handles that for you. This is the recommended approach for production data like databases.

Let's prove it works:

Bind Mounts - Best for Development:

Bind mounts map a specific folder on your computer directly into the container. This is perfect for development because any changes you make to files on your host immediately appear inside the container (and vice versa).

The -v flag explained in detail:

Read-Only Mounts for Security:

Sometimes you want a container to read files but not modify them. Use the :ro option:

Volume Commands Reference:

Real-World Example - PostgreSQL with Persistent Data:

Docker Networks - How Containers Communicate

By default, each container is isolated. It cannot see other containers, and other containers cannot see it. To enable communication, you put containers on the same Docker network.

Why Networks Matter:

Imagine you have a web application container and a database container. Without networking:

  • The web app cannot connect to the database

  • You cannot use nice hostnames like db - you'd need IP addresses

  • IP addresses change every time containers restart

  • No security isolation between different applications

Docker's Built-in DNS:

Docker provides automatic DNS resolution. When containers are on the same network, they can reach each other by container name instead of IP address. This is powerful because container IP addresses can change, but names stay constant.

Network Types:

Type
Description
Use Case

bridge

Default. Containers can communicate if on same bridge network

Most applications

host

Container shares host's network stack

High-performance, no isolation

none

No networking

Maximum isolation

overlay

Connect containers across multiple Docker hosts

Docker Swarm, distributed apps

Practical Example - Connecting Containers:

Let's create a network and connect two containers that can talk to each other:

Network Commands Explained:

The --network Flag Explained:

Real-World Example - Web App + Database + Redis:

Port Mapping vs Network Communication:

This confuses many beginners. Here's the difference:

Concept
Syntax
Who Can Connect?

Port mapping

-p 8080:80

Your host machine (localhost:8080)

Network connection

--network mynet

Other containers on same network

Security Best Practice - Network Isolation:

Use separate networks to isolate different parts of your application:

Clean up after network examples:

Docker Compose - Orchestrating Multiple Containers

Real applications rarely consist of a single container. A typical web application might need a web server, an application server, and a database. Managing these manually with individual docker run commands becomes tedious and error-prone.

Docker Compose solves this by defining your entire application stack in a single YAML file. One command starts everything, with the correct configuration, networks, and volumes.

Key commands:

Container Security Essentials

The Golden Rules:

  1. Use minimal base images (-slim, -alpine) - fewer packages = fewer vulnerabilities

  2. Never run as root - use USER instruction in Dockerfile

  3. Scan images before deployment - use Trivy, Docker Scout, etc.

  4. Keep images updated - rebuild regularly to get security patches

  5. Don't embed secrets - use environment variables or secret managers

Lab 1: Build a Secure Multi-Container App

Now that you understand the concepts, let's apply them by building a real multi-container application. This lab demonstrates how all the pieces fit together in a realistic scenario.

Goal: Create a complete web application with frontend (NGINX), backend (Python API), and database (PostgreSQL) using Docker Compose. Along the way, you'll apply every security best practice we've discussed.

Why This Lab Matters:

This is not just an exercise - this is exactly how professional applications are built and deployed in production. You'll learn:

  • How to structure a multi-service application

  • How containers communicate through Docker networks

  • How to persist data that survives container restarts

  • How to apply security best practices from the start

  • How to debug issues when things go wrong

What We're Building

We're creating a simple but realistic three-tier web application:

How the pieces work together:

Component
Role
Why We Use It

NGINX

Reverse proxy, serves frontend

Handles HTTP, SSL, load balancing

Python API

Business logic, data processing

Processes requests, talks to database

PostgreSQL

Data storage

Stores persistent data safely

The Request Flow:

  1. User opens http://localhost:8080 in their browser

  2. Browser connects to NGINX container (port 8080 on your machine → port 80 in container)

  3. NGINX sees the request path and decides what to do:

    • /api/* requests → forwards to Python API container

    • /health → forwards to Python API for health check

    • Everything else → returns a simple frontend response

  4. Python API processes the request, potentially querying PostgreSQL

  5. Response travels back through NGINX to the user's browser

Step 1: Create Project Structure

What you're doing: Creating the folder structure for our application. Each service has its own folder with its configuration files.

Your structure will be:

Why this structure?

  • Each service is isolated in its own folder

  • Docker Compose can build each service from its folder

  • Easy to manage, test, and deploy independently

Step 2: Create the Python API

What you're doing: Creating a simple Flask API that will serve as our backend. This API exposes endpoints that NGINX will forward requests to.

File: api/requirements.txt

Why these packages?

  • flask==3.0.0 - A lightweight Python web framework for building APIs

  • psycopg2-binary==2.9.9 - PostgreSQL driver for Python (so our API can talk to the database)

flask is a lightweight Python web framework for building APIs. psycopg2-binary is the PostgreSQL database driver for Python.

File: api/app.py

Key points explained:

Code
Explanation

host='0.0.0.0'

Listen on all network interfaces, not just localhost. Required in containers because requests come from outside.

os.getenv("DB_HOST")

Read environment variable. Docker Compose will set this.

/health endpoint

Standard practice - allows Docker/Kubernetes to check if service is alive.

File: api/Dockerfile

Step 3: Create NGINX Configuration

File: nginx/nginx.conf

Key concepts explained:

Concept
Explanation

Reverse Proxy

NGINX sits between users and your app, handling routing, SSL, caching

upstream api_backend

Defines where to forward requests. api is the Docker service name.

proxy_pass

Forwards the incoming request to the backend server

Docker DNS

Docker Compose creates a network where services can find each other by name

Step 4: Create Docker Compose File

File: docker-compose.yml

Step 5: Build and Run

What this command does:

Flag
Meaning

up

Create and start containers

-d

Detached mode (run in background)

--build

Rebuild images before starting

Watch the startup:

Verify everything is running:

Expected output:

Step 6: Test the Application

Expected responses:

Step 7: Cleanup

Lab 2: Security Audit with Trivy

Goal: Learn to scan Docker images for vulnerabilities, understand what the results mean, and know how to fix security issues in your containers.

Why This Matters:

Every Docker image you pull from the internet contains hundreds of software packages - the base operating system, libraries, utilities, and your application dependencies. Each of these packages might have security vulnerabilities that attackers can exploit.

In 2023, over 26,000 new CVEs (Common Vulnerabilities and Exposures) were published. Running old, unpatched images in production is like leaving your front door open - you're inviting attackers in.

What is Trivy?

Trivy is a comprehensive security scanner created by Aqua Security. It's free, open-source, and widely used in the industry. Trivy finds vulnerabilities in:

  • OS packages - apt, apk, yum packages in the base image (e.g., openssl, curl, libc)

  • Application dependencies - pip, npm, gem, cargo packages you install (e.g., flask, requests)

  • Misconfigurations - Security issues in Dockerfiles, Kubernetes manifests

  • Secrets - Accidentally committed passwords, API keys, private keys

How Trivy Works:

Step 1: Install Trivy

macOS:

Linux (Debian/Ubuntu/Kali):

Using Docker (works everywhere - no installation needed):

Verify installation:

Step 2: Scan a Vulnerable Image - See the Problem

Let's scan an intentionally old image to understand what vulnerabilities look like and why they matter:

What you're doing: You're asking Trivy to analyze every package installed in this Docker image and check each one against a database of known vulnerabilities.

Understanding the Output:

The scan produces a lot of output. Let's break down what you'll see:

1. Summary Line (at the top):

Category
Meaning

CRITICAL

Actively exploited, easy to attack, gives attacker full control

HIGH

Serious issue, could lead to data breach or system compromise

MEDIUM

Moderate risk, requires specific conditions to exploit

LOW

Minor issue, difficult to exploit

UNKNOWN

Severity not yet determined by security researchers

2. Detailed Table:

How to read this:

  • CVE-2023-0286 in OpenSSL is a real vulnerability that allows attackers to read sensitive memory

  • Installed Version shows what's in your image (outdated)

  • Fixed Version shows what version patches the vulnerability

Filter to see only critical issues:

Why This Matters - Real Attack Scenarios:

CVE Example
What An Attacker Could Do

CVE-2023-4911

Gain root access to the container (Looney Tunables glibc bug)

CVE-2023-0286

Read sensitive data from memory (OpenSSL vulnerability)

CVE-2023-38545

Execute arbitrary code via crafted URL (curl SOCKS5 heap overflow)

An attacker who exploits even ONE of these could gain full control of your container, access databases, steal secrets, or use your infrastructure to attack others.

Step 3: Compare with a Secure Image

Now scan a modern, minimal image to see how proper image selection dramatically reduces risk:

Expected result:

The difference is dramatic:

Metric
python:3.6
python:3.11-slim
Reduction

Total vulnerabilities

~1247

~68

95%

CRITICAL

~39

0

100%

HIGH

~256

~2

99%

Image size

~900 MB

~150 MB

83%

Why such a big difference?

Factor
Old Image (python:3.6)
Modern Image (python:3.11-slim)

Base OS

Debian 10 (oldoldstable, limited updates)

Debian 12 (stable, actively patched)

Python version

3.6 (end-of-life Dec 2021)

3.11 (actively maintained)

Included packages

Full OS with compilers, docs, etc.

Minimal - only what's needed to run Python

Security patches

None for 3+ years

Monthly updates

Step 4: Scan Your Own Image

Now let's scan the image we built in Lab 1 to verify our security practices work:

What you should see:

Because we used python:3.11-slim as our base image, you should see a relatively clean report with zero or very few CRITICAL vulnerabilities.

If you find vulnerabilities, here's how to fix them:

Issue
Solution

Old base image

Update FROM python:3.11-slim to python:3.12-slim

Vulnerable Python package

Update version in requirements.txt

Vulnerable OS package

Run apt-get upgrade in Dockerfile

No fix available

Wait for patch, or use alternative package

Step 5: Scan More Than Just Images

Trivy can scan many things beyond just Docker images:

Scan a filesystem (your project folder):

Scan a Dockerfile for misconfigurations:

Common misconfigurations Trivy finds:

Issue
Why It's Bad

Running as root

If container is compromised, attacker has root

ADD instead of COPY

ADD can download from URLs, security risk

latest tag

Unpredictable - could suddenly have vulnerabilities

No HEALTHCHECK

Can't detect if application is actually working

Secrets in Dockerfile

Credentials exposed in image history

Scan for secrets accidentally committed:

Step 6: Automated Security Gate

In real projects, you want to automatically block deployments that have critical vulnerabilities. This is how professional teams prevent vulnerable code from reaching production.

Using exit codes in scripts:

How this works in CI/CD:

Why this matters:

  • Developers get immediate feedback when they introduce vulnerabilities

  • Vulnerable images never reach production

  • Security becomes part of the development process, not an afterthought

Step 7: Generate Reports

Generate reports for documentation, compliance, or sharing with your team:

Security Best Practices Summary

Practice
How To Implement

Use slim/alpine images

FROM python:3.11-slim instead of FROM python:3.11

Use specific version tags

FROM python:3.11.7-slim not FROM python:latest

Run as non-root

USER appuser in Dockerfile

Scan in CI/CD

trivy image --exit-code 1 --severity CRITICAL

Update base images monthly

Rebuild images regularly to get patches

Scan before pushing

trivy image myapp:latest before docker push

Use multi-stage builds

Separate build dependencies from runtime

Lab 2 Key Takeaways

Concept
Key Takeaway

CVE database

Trivy checks against NVD and vendor databases

Image age matters

Old images accumulate hundreds of vulnerabilities over time

Slim images

Fewer packages = smaller attack surface = fewer vulnerabilities

Defense in depth

Scan images, Dockerfiles, filesystems, and secrets

CI/CD integration

Use --exit-code 1 to automatically block vulnerable deployments

Regular updates

Rebuild images monthly to get security patches

Lab 3: Debugging Container Issues

Goal: Learn how to troubleshoot the most common Docker problems. When containers don't work as expected, you need to know how to investigate and fix issues quickly.

Why This Matters:

Containers can fail for many reasons: missing ports, wrong configuration, application crashes, resource limits, network issues. Knowing how to debug these problems is essential for any DevOps or development work.

Scenario 1: Container Exits Immediately

The Problem:

You run a container but it exits right away:

Why This Happens:

A container runs only as long as its main process runs. Here's the key concept:

The Ubuntu image's default command is bash. When there's no terminal attached (no -it flags), bash has nothing to do and exits immediately. When bash exits, the container stops.

The Solution - Keep It Running:

Flag Reference:

Flag
What It Does

-i

Interactive - keep STDIN open so you can type

-t

TTY - allocate a terminal so you see formatted output

-d

Detached - run in background, return control to your terminal

-it (combined)

Interactive terminal - the way you normally "enter" a container

sleep infinity

A command that never exits - keeps container alive

Clean up:

Scenario 2: Cannot Connect to Container

The Problem:

Your app is running but you can't access it from your browser or curl:

Why This Happens:

NGINX is running on port 80 inside the container, but containers are isolated. Your computer cannot reach into the container without explicit port mapping.

The Solution - Map Ports:

Port Mapping Syntax:

Debugging Port Issues:

Scenario 3: Check What's Inside a Running Container

The Problem:

Your container is running but something isn't working. You need to look inside to debug.

The Solution - Docker Exec:

What Docker Exec Does:

Command
Effect

docker exec app ls

Run ls inside container, show output, exit

docker exec -it app bash

Start interactive bash shell inside container

docker exec -u root app bash

Run as root user (even if container runs as non-root)

docker exec -w /tmp app ls

Run command in specific directory

Common Debugging Commands Inside Container:

Scenario 4: View Container Logs

The Problem:

Your container is crashing or behaving strangely. You need to see what it's outputting.

The Solution - Docker Logs:

Understanding Docker Logs:

Docker captures everything your application writes to STDOUT (standard output) and STDERR (standard error). This is why in Docker:

  • Applications should log to console, not files

  • Use print() in Python, console.log() in Node.js

  • Configure your app's logging framework to output to stdout

Scenario 5: Inspect Container Details

The Problem:

You need detailed information about a container: its IP address, environment variables, mounts, configuration.

The Solution - Docker Inspect:

Scenario 6: Container Uses Too Many Resources

The Problem:

A container is consuming too much CPU or memory, affecting other containers or your system.

The Solution - Monitor and Limit:

Understanding the Stats:

Column
What It Means

CPU %

CPU usage as percentage of host CPU

MEM USAGE / LIMIT

Current memory / Maximum allowed

MEM %

Memory as percentage of limit

NET I/O

Network bytes received / sent

BLOCK I/O

Disk bytes read / written

PIDS

Number of processes in container

Set Resource Limits:

Scenario 7: Clean Up Disk Space

The Problem:

Docker is using a lot of disk space. You need to reclaim it.

The Solution - Docker System Commands:

Before Using Prune Commands:

Always check what will be removed:

Debugging Cheatsheet

Problem
Commands to Investigate

Container exits immediately

docker logs <container>

docker inspect <container> --format '{{.State}}'

Can't connect to container

docker port <container>

docker inspect <container> (check ports)

Need to look inside

docker exec -it <container> bash

docker exec <container> cat /some/file

Check if healthy

docker ps (look for health status)

docker inspect <container> --format '{{.State.Health}}'

Out of disk space

docker system df

docker system prune -a

High CPU/memory

docker stats

Add --memory and --cpus limits

Network issues

docker network inspect <network>

docker exec <container> ping <other-container>

Clean Up After This Lab

Docker Cheatsheet

Container Lifecycle

Image Management

Volumes

Networks

Docker Compose

Cleanup

Debugging

Dockerfile Instructions

Docker Compose YAML

Security Scanning (Trivy)

Quick Reference Card

Most Used Commands

Task
Command

Run container

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

Stop container

docker stop web

View logs

docker logs -f web

Shell into container

docker exec -it web bash

Build image

docker build -t myapp .

List containers

docker ps -a

List images

docker images

Start Compose

docker compose up -d

Stop Compose

docker compose down

View Compose logs

docker compose logs -f

Clean up

docker system prune -a

Scan image

trivy image myapp

Port Mapping

Volume Mounting

Last updated