Skip to main content

Docker Daily Workflow Lab

· 3 min read

Lab (Try it now!)​

Scenario: Run a web server, check it, and view the logs.

Start:

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

Verify: (See it running?)

`docker ps`

Check Logs: (You should see "Configuration complete...")

`docker logs lab-web`

Enter:

`docker exec -it lab-web bash`
  • (Now inside container) Type ls to see files.

  • Type exit to leave.

Stop:

`docker stop lab-web`

Lab: The "Custom Homepage" (Modifying Running Containers)​

Goal: Understand how to move files from your laptop into a container.

  • Create a file: Create a file named index.html on your desktop.

  • Content: Hello from DevSecOpsGuru!

  • Start Nginx: docker run -d --name my-website -p 8080:80 nginx

  • Check before change: Open browser http://localhost:8080. You see the default "Welcome to nginx".

  • The Magic Command (Copy): docker cp index.html my-website:/usr/share/nginx/html/index.html (This overwrites the default file inside the container with your file).

  • Check after change: Refresh your browser. It now says "Hello from DevSecOpsGuru!".

  • Cleanup: docker rm -f my-website

Lab: The "Self-Destruct" Container (Utility Tasks)​

Goal: Run a quick task that doesn't leave junk behind.

  • Run with --rm: docker run --rm -it alpine echo "I am here for 5 seconds"

  • Check: Immediately run docker ps -a.

  • Result: You will not see the container. It deleted itself after finishing the echo command.

  • Why? Great for one-time scripts or testing commands without filling your disk with garbage.


Lab: The "Persistent Database" (Data Safety)​

Goal: Prove that data survives even if the container is deleted.

  • Create a Volume: docker volume create my-db-data

  • Start Database (MySQL/Postgres) with Volume:docker run -d --name risky-db -e POSTGRES_PASSWORD=mysecret -v my-db-data:/var/lib/postgresql/data postgres

  • -v my-db-data:/var/lib/postgresql/data: Maps the volume to where Postgres stores data.

  • Write Data: docker exec -it risky-db psql -U postgres -c "CREATE TABLE users (id SERIAL PRIMARY KEY, name VARCHAR(50));"

  • Destroy the Container (Simulate a crash): docker rm -f risky-db (The container is gone. Is the data lost?)

  • Resurrect: Start a new container with the same volume. docker run -d --name safe-db -e POSTGRES_PASSWORD=mysecret -v my-db-data:/var/lib/postgresql/data postgres

  • Verify Data: docker exec -it safe-db psql -U postgres -c "\dt" (You will still see the 'users' table! The data survived inside the Volume).

Lab: The "Environment Configuration" (12-Factor App Principle)​

Goal: Change application behavior without changing code.

  • Run Python App: docker run --rm -e MY_NAME="Amit" python:3.9-slim python -c "import os; print('Hello ' + os.environ.get('MY_NAME'))"

  • Result: Output will be Hello Amit.

  • Change Config: docker run --rm -e MY_NAME="Priya" python:3.9-slim python -c "import os; print('Hello ' + os.environ.get('MY_NAME'))"

  • Result: Output will be Hello Priya.

  • Architect Note: This is how we configure apps for Dev, QA, and Prod using the same image.

Docker Daily Workflow Quiz

· One min read

Workflow

Score: 0/20

🐳 Master Quiz: Docker Workflow

Run, Exec, Log, Stop: Managing the Container Lifecycle.

🎉 Session Complete!​

Your Operational Efficiency:

0/20 Intern

  • **↺ New Session**

Docker Image Management Lab

· 3 min read

Lab 1: The Smallest Website

  • Create a folder: mkdir my-site and go inside it.

  • Create index.html: Write Hello World in it.

  • **Create Dockerfile:**DockerfileFROM nginx:alpine COPY index.html /usr/share/nginx/html/index.html

  • Build: docker build -t tiny-site:v1 .

  • Run: docker run -d -p 9999:80 tiny-site:v1

  • Test: Open http://localhost:9999.

Lab 2: The History Lesson

  • Run: docker history tiny-site:v1

  • Observe: Look at the "SIZE" column. You will see the base nginx layers are large, and your COPY layer is very small (just bytes).

  • Learn: This proves that your changes are just a tiny layer on top of the base.


Lab 1: The "Bloatware vs. Slim" Challenge​

Goal: See the massive difference a good base image makes.

  • Create a folder: mkdir compare-images and go inside.

  • **Create Dockerfile.fat:**DockerfileFROM ubuntu:latest RUN apt-get update && apt-get install -y python3 CMD ["python3", "--version"]

  • Build Fat Image: docker build -f Dockerfile.fat -t my-python:fat .

  • **Create Dockerfile.slim:**DockerfileFROM python:alpine CMD ["python", "--version"]

  • Build Slim Image: docker build -f Dockerfile.slim -t my-python:slim .

  • Compare Sizes:docker images | grep my-python

  • Result: You will likely see fat is ~100MB-500MB, while slim is ~50MB.

  • Lesson: Always choose specific Alpine or Slim images, not generic OS images like Ubuntu/CentOS.


Lab 2: Multi-Stage Builds (The Professional Standard)​

Goal: Compile code in one container, but ship ONLY the binary in the final image.

  • Create a Go file (main.go): (Even if you don't know Go, just copy this).Gopackage main import "fmt" func main() { fmt.Println("Hello from a Tiny Container!") }

  • **Create Dockerfile:**Dockerfile# Stage 1: The Builder (Has all the heavy tools) FROM golang:1.21 AS builder WORKDIR /app COPY main.go . RUN go build -o myapp main.go # Stage 2: The Runner (Has almost nothing) FROM alpine:latest WORKDIR /root/ # Copy ONLY the compiled file from the builder stage COPY --from=builder /app/myapp . CMD ["./myapp"]

  • Build: docker build -t my-go-app:v1 .

  • Check Size:docker images my-go-app:v1

  • Result: The image will be tiny (maybe 10MB). If you used the standard way, it would be 800MB+!

Lab 3: The Private Registry (Simulating Corporate Environment)​

Goal: Run your own "Docker Hub" locally and push images to it.

  • Start a Registry: docker run -d -p 5000:5000 --name local-registry registry:2 (You now have a private app store running on port 5000).

  • Tag an Image for Localhost: docker tag my-go-app:v1 localhost:5000/my-secret-app:v1

  • Push to Local Registry: docker push localhost:5000/my-secret-app:v1

  • Verify: Delete the image from your main list: docker rmi localhost:5000/my-secret-app:v1 Now pull it back: docker pull localhost:5000/my-secret-app:v1

  • Cleanup: docker rm -f local-registry

Docker Image Management Quiz

· One min read

Build & Ship

Score: 0/20

🐳 Master Quiz: Image Management

Building, Pushing, and Securing Docker Images for the Supply Chain.

🎉 Deployment Ready!​

Your Mastery:

0/20 Intern

  • **↺ Re-Build**

Docker Network Cli Lab

· 3 min read

Lab 1: The "Two Worlds" (Isolation Test)​

Goal: Prove that networks are truly isolated.

  • Create Networks: docker network create blue-net docker network create red-net

  • Start Containers: docker run -d --name blue-box --network blue-net alpine sleep 1000 docker run -d --name red-box --network red-net alpine sleep 1000

  • Test Ping (Fail):docker exec -it blue-box ping red-box

  • Result: bad address 'red-box'. They cannot see each other. Security works!

  • Connect (Bridge the gap): docker network connect blue-net red-box (Now Red Box has a leg in the Blue world).

  • Test Ping (Success):docker exec -it blue-box ping red-box

  • Result: It works!

Lab 2: The "DNS Discovery" (Service Name)​

Goal: Connect a Web App to Redis using names.

  • Create Network: docker network create shop-net

  • Start Redis: docker run -d --name my-cache --network shop-net redis

  • Start App (Simulated): docker run -it --rm --network shop-net alpine sh

  • Verify DNS: Inside the alpine shell, type: ping my-cache

  • Result: You see 64 bytes from 172.x.x.x. Docker automatically found the Redis container IP.


Lab 1: The "Forbidden Path" (Three-Tier Segmentation)​

Goal: Build a secure architecture where the Frontend cannot reach the Database directly.

  • Create Networks: docker network create frontend-net docker network create backend-net

  • Start Database (Backend Only): docker run -d --name secret-db --network backend-net alpine sleep 1000

  • Start API (The Middleman):

  • First, connect to backend: docker run -d --name api-server --network backend-net alpine sleep 1000

  • Second, connect to frontend: docker network connect frontend-net api-server

  • Note: The API server now has two network cards. It bridges the gap.

  • Start Web Server (Frontend Only): docker run -d --name public-web --network frontend-net alpine sleep 1000

  • The Hacker Test:

  • Valid Path: docker exec public-web ping api-server -> Success.

  • Invalid Path: docker exec public-web ping secret-db -> Fail (Bad Address).

  • Result: Even if a hacker compromises your Web Server, they cannot touch the Database.

Lab 2: The "Host Escape" (Connecting to Localhost)​

Goal: Your container needs to access a database running on your laptop (not in Docker).

  • Simulate a Host Service:

  • Open a terminal on your laptop (not Docker).

  • Run a simple python server: python3 -m http.server 9000

  • Now your laptop is listening on port 9000.

  • The Failure (Standard Localhost):

  • docker run --rm alpine apk add curl && curl http://localhost:9000

  • Result: Connection Refused. (Because localhost inside the container means the container itself!).

  • The Success (Magic Address):

  • docker run --rm alpine apk add curl && curl http://host.docker.internal:9000

  • (Note: On Linux, add --add-host=host.docker.internal:host-gateway if it fails).

  • Result: You see the directory listing of your laptop!


Lab 3: The "Network Detective" (Netshoot Troubleshooting)​

Goal: Use professional tools to debug DNS and Port issues.

  • Setup a Broken Scenario:

  • docker network create broken-net

  • docker run -d --name nginx-server --network broken-net nginx

  • Launch the Troubleshooter:

  • We use nicolaka/netshoot. It’s the "Swiss Army Knife" of Docker networking.

  • docker run -it --rm --network broken-net nicolaka/netshoot

  • Task 1: DNS Check (Dig):

  • Type: dig nginx-server

  • Output: Look for the "ANSWER SECTION". It shows the IP (e.g., 172.19.0.2). This proves Docker DNS is working.

  • Task 2: Port Scan (Nmap):

  • Type: nmap -p 80 nginx-server

  • Output: 80/tcp open http. This proves the application is actually listening.

  • Task 3: Simulation:

  • Type: curl http://nginx-server:80

  • Output: The HTML code of Nginx.

  • Lesson: If dig works but curl fails, the network is fine, but the app crashed. If dig fails, the network is broken.

Docker Network Cli Quiz

· One min read

Ping Test

Score: 0/20

🌐 Master Quiz: Docker Networks

Connect, Isolate, and Debug container communications.

🎉 Connection Established!​

Your Network Status:

0/20 Offline

  • **↺ Re-Connect**

Docker Volume Cli Lab

· 3 min read

Lab 1: The "Magical Data" Test (Named Volumes)​

  • Create Volume: docker volume create magic-box

  • Start Container 1: docker run -d --name writer -v magic-box:/data alpine sh -c "echo 'I was here' > /data/secret.txt && sleep 1000"

  • Check Data: docker exec writer cat /data/secret.txt (Output: I was here)

  • Destroy Container 1: docker rm -f writer

  • Start Container 2: docker run -d --name reader -v magic-box:/app_data alpine sleep 1000 (Notice we mounted it to a different path /app_data just to prove we can!)

  • Verify: docker exec reader cat /app_data/secret.txt Result: Output is I was here. The data survived!

Lab 2: The "Live Code" Test (Bind Mounts)​

Best for Web Developers

  • Create a folder: mkdir my-site

  • Create file: Inside my-site, create index.html with text Version 1.

  • Run Nginx: docker run -d -p 8080:80 -v $(pwd)/my-site:/usr/share/nginx/html --name web nginx

  • Check Browser: Go to localhost:8080. You see Version 1.

  • Modify File: Edit index.html on your laptop to say Version 2 - Live Update!. Save it.

  • Refresh Browser: Instantly, you see Version 2. No rebuild required!


Lab 1: The "Disaster Recovery" (Backup & Restore)​

Goal: Simulate a database corruption and restore from a backup file.

  • Create Data & Populate:

  • docker volume create precious-data

  • docker run -d --name production-db -v precious-data:/data alpine sh -c "echo 'Important Financial Records' > /data/money.txt && sleep 1000"

  • Perform Backup (The "Sidecar" Method):

  • Run a temporary container that sees both the volume AND your current folder.

  • docker run --rm -v precious-data:/volume-data -v $(pwd):/backup alpine tar cvf /backup/snapshot.tar /volume-data

  • Check: You should see snapshot.tar on your laptop now.

  • Simulate Disaster:

  • docker rm -f production-db

  • docker volume rm precious-data (Data is GONE!)

  • Restore:

  • docker volume create new-data

  • docker run --rm -v new-data:/volume-data -v $(pwd):/backup alpine tar xvf /backup/snapshot.tar -C /

  • Verify:

  • docker run --rm -v new-data:/data alpine cat /data/money.txt

  • Result: "Important Financial Records" is back!


5: Extremely Detailed Labs for "DevSecOps Architect Level"​

Lab 2: The "Manual Full Stack" (WordPress + MySQL)​

Goal: Manually connect a frontend and backend. This is "The Hard Way."

Create Network: docker network create my-blog-net

Start MySQL (The Backend):

docker run -d \
--name mysql-db \
--network my-blog-net \
-v mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=somewordpress \
-e MYSQL_DATABASE=wordpress \
mysql:5.7

Start WordPress (The Frontend):

docker run -d \
--name my-blog \
--network my-blog-net \
-p 8080:80 \
-e WORDPRESS_DB_HOST=mysql-db \
-e WORDPRESS_DB_USER=root \
-e WORDPRESS_DB_PASSWORD=somewordpress \
wordpress
  • Note: See -e WORDPRESS_DB_HOST=mysql-db? We are using the container name as the address!

Test: Open http://localhost:8080. You will see the WordPress installation screen.

Cleanup (Painful, isn't it?): docker rm -f my-blog mysql-db docker network rm my-blog-net docker volume rm mysql-data

Takeaway: Running these huge commands every time is annoying. This is why we use Docker Compose (next topic).

Lab 3: The "Security Lockdown" (Read-Only Containers)​

Goal: Prevent a hacked application from modifying configuration files.

  • Create a Dummy Config: echo "admin_access=true" > config.txt

  • Run in Read-Only Mode:docker run -d --name secure-app -v $(pwd)/config.txt:/app/config.txt:ro alpine sleep 1000

  • Note the :ro at the end of the volume path.

  • Attempt Hack (Try to change config): docker exec -it secure-app sh -c "echo 'admin_access=FALSE' > /app/config.txt"

  • Result:sh: can't create /app/config.txt: Read-only file system

  • Success: The container prevented the modification.

Docker Volume Cli Quiz

· One min read

Storage Check

Score: 0/20

📦 Master Quiz: Docker Volumes

Test your knowledge on Persistence, Bind Mounts, and Data Security.

🎉 Data Saved!​

Your Persistence Score:

0/20 Intern

  • **↺ Mount Again**

Accessing & Slicing in Python Lists Lab

· 2 min read

Lab 1: The Reverser Goal: Reverse a string using list slicing logic.

text = "DevSecOps"
# Strings support slicing too!
reversed_text = text[::-1]
print(reversed_text) # Output: spOceSveD

Lab 2: The Data sampler Goal: Extract the first 3 items and the last 3 items.

data = [10, 20, 30, 40, 50, 60, 70, 80]
first_three = data[:3]
last_three = data[-3:]
print(first_three) # [10, 20, 30]
print(last_three) # [60, 70, 80]

Lab 3: The Data Scrubber (DevSecOps Focus)​

Scenario: You have a list of raw credit card numbers. You need to display them in the logs, but for security (PCI-DSS compliance), you must mask everything except the last 4 digits.

# Raw sensitive data
cc_numbers = ["1234-5678-9012-3456", "9876-5432-1098-7654"]

sanitized_logs = []

for card in cc_numbers:
# 1. Slice the last 4 visible digits
visible_part = card[-4:]

# 2. Calculate how many characters need masking (total length - 4)
# We use card[:-4] to get the part we want to hide
masked_length = len(card[:-4])

# 3. Create the mask string
mask = "X" * masked_length

# 4. Combine
sanitized_logs.append(mask + visible_part)

print(sanitized_logs)
# Output: ['XXXXXXXXXXXXXXX3456', 'XXXXXXXXXXXXXXX7654']

Lab 4: The Matrix Hacker (Nested List Manipulation)​

Scenario: You have a 3x3 grid. You need to extract the diagonal numbers (top-left to bottom-right).

matrix = [
[10, 20, 30], # Row 0
[40, 50, 60], # Row 1
[70, 80, 90] # Row 2
]

diagonal = []

# Logic: The diagonal items are at indices [0][0], [1][1], [2][2]
for i in range(len(matrix)):
diagonal.append(matrix[i][i])

print(f"Diagonal Data: {diagonal}")
# Output: Diagonal Data: [10, 50, 90]

Lab 5: The "Batch Processor" (Chunking Data)​

Scenario: You have 100 User IDs. Your API can only handle 10 IDs at a time. You need to slice the main list into batches of 10.

user_ids = list(range(100)) # Generates 0 to 99
batch_size = 10

# We use a range with a 'step' equal to batch_size
for i in range(0, len(user_ids), batch_size):
# Slice from current 'i' to 'i + 10'
batch = user_ids[i : i + batch_size]
print(f"Processing Batch starting at {i}: {batch}")

# This safely handles the last batch even if it has fewer than 10 items!

Accessing & Slicing in Python Lists Quiz

· One min read

Hallway Pass

Score: 0/20

🔐 Master Quiz: Accessing & Slicing

Open the lockers, slice the data, and master the [Start:Stop:Step] logic.

🎉 Class Dismissed!​

Your Grade:

0/20 Freshman

  • **↺ Retake Exam**