@icarusmx/creta 1.5.11 → 1.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/bin/creta.js +37 -1
  2. package/lib/data/command-help/aws-ec2.js +34 -0
  3. package/lib/data/command-help/grep.js +72 -0
  4. package/lib/data/command-help/index.js +9 -1
  5. package/lib/executors/CommandHelpExecutor.js +6 -1
  6. package/lib/executors/ExercisesExecutor.js +8 -0
  7. package/lib/exercises/.claude/settings.local.json +12 -0
  8. package/lib/exercises/01-developing-muscle-for-nvim.md +528 -0
  9. package/lib/exercises/{iterm2-pane-navigation.md → 02-iterm2-pane-navigation.md} +1 -1
  10. package/lib/exercises/05-svelte-first-steps.md +1340 -0
  11. package/lib/exercises/{curl-and-pipes.md → 06-curl-and-pipes.md} +187 -72
  12. package/lib/exercises/07-claude-api-first-steps.md +855 -0
  13. package/lib/exercises/08-playwright-svelte-guide.md +1384 -0
  14. package/lib/exercises/09-docker-first-steps.md +1475 -0
  15. package/lib/exercises/{railway-deployment.md → 10-railway-deployment.md} +1 -0
  16. package/lib/exercises/{aws-billing-detective.md → 11-aws-billing-detective.md} +215 -35
  17. package/lib/exercises/12-install-skills.md +755 -0
  18. package/lib/exercises/README.md +180 -0
  19. package/lib/exercises/utils/booklet-2up.js +133 -0
  20. package/lib/exercises/utils/booklet-manual-duplex.js +159 -0
  21. package/lib/exercises/utils/booklet-simple.js +136 -0
  22. package/lib/exercises/utils/create-booklet.js +116 -0
  23. package/lib/scripts/aws-ec2-all.sh +58 -0
  24. package/package.json +3 -2
  25. /package/lib/exercises/{git-stash-workflow.md → 03-git-stash-workflow.md} +0 -0
  26. /package/lib/exercises/{array-object-manipulation.md → 04-array-object-manipulation.md} +0 -0
@@ -0,0 +1,1475 @@
1
+ # Docker First Steps - Containers Without the Confusion
2
+
3
+ <!-- vim: set foldmethod=marker foldlevel=0: -->
4
+
5
+ ## 📖 LazyVim Reading Guide {{{
6
+
7
+ **Start with:** `zM` (close all folds) → Navigate with `za` (toggle fold under cursor)
8
+
9
+ This document uses fold markers `{{{` and `}}}` for organized reading.
10
+
11
+ }}}
12
+
13
+ ## 🚨 Problem: "It Works on My Machine" {{{
14
+
15
+ You've built something amazing on your laptop, but:
16
+ - **Teammate:** "It doesn't run on my computer"
17
+ - **Server:** "Missing dependencies"
18
+ - **You 6 months later:** "How did I even set this up?"
19
+
20
+ **Common complaints:**
21
+ - Different OS versions cause conflicts
22
+ - Forgotten dependencies
23
+ - Environment variables lost
24
+ - "Works locally, breaks in production"
25
+
26
+ **The root cause:** Your code depends on a specific environment that doesn't travel with your code.
27
+
28
+ }}}
29
+
30
+ ## ✅ Solution: Docker Containers {{{
31
+
32
+ Docker packages your app **WITH its environment** into a portable container.
33
+
34
+ **Think of it like this:**
35
+ - **Without Docker:** Shipping furniture assembly instructions to someone without tools
36
+ - **With Docker:** Shipping a fully assembled furniture piece
37
+
38
+ **What Docker gives you:**
39
+ - 📦 **Portability** - Runs the same everywhere (Mac, Linux, Windows, cloud)
40
+ - 🔒 **Isolation** - Each container is its own sandbox
41
+ - 🚀 **Consistency** - Same environment from dev to production
42
+ - ⚡ **Speed** - Lighter than virtual machines
43
+
44
+ **Core concept:** A container is a running instance of an image.
45
+
46
+ }}}
47
+
48
+ ## 🎯 Core Concepts {{{
49
+
50
+ ### Images vs Containers {{{
51
+
52
+ **Image:**
53
+ - Blueprint or recipe
54
+ - Static, read-only template
55
+ - Contains: code, dependencies, OS libraries
56
+ - Stored on your machine or Docker Hub
57
+
58
+ **Container:**
59
+ - Running instance of an image
60
+ - Active, can be started/stopped
61
+ - Isolated process with its own filesystem
62
+ - Dies when stopped (unless you persist data)
63
+
64
+ **Analogy:**
65
+ - **Image** = Cookie cutter
66
+ - **Container** = Actual cookie
67
+
68
+ **Example:**
69
+ ```bash
70
+ # Image = node:20 (blueprint for Node.js 20 environment)
71
+ # Container = your-running-app (actual running instance)
72
+ ```
73
+
74
+ }}}
75
+
76
+ ### Dockerfile: The Recipe {{{
77
+
78
+ A `Dockerfile` is a text file that defines **how to build an image**.
79
+
80
+ **Basic structure:**
81
+ ```dockerfile
82
+ FROM node:20 # Start from base image
83
+ WORKDIR /app # Set working directory
84
+ COPY . . # Copy your code
85
+ RUN npm install # Install dependencies
86
+ CMD ["npm", "start"] # Command to run when container starts
87
+ ```
88
+
89
+ **Think of it as:**
90
+ - `FROM` = "Start with a blank Node.js environment"
91
+ - `WORKDIR` = "Create and enter /app folder"
92
+ - `COPY` = "Copy my code into the container"
93
+ - `RUN` = "Execute commands during build"
94
+ - `CMD` = "What to run when container starts"
95
+
96
+ }}}
97
+
98
+ ### Docker Hub: The Library {{{
99
+
100
+ **Docker Hub** = GitHub for Docker images
101
+
102
+ **Official images available:**
103
+ - `node` - Node.js environments
104
+ - `python` - Python environments
105
+ - `nginx` - Web server
106
+ - `postgres` - PostgreSQL database
107
+ - `mysql` - MySQL database
108
+
109
+ **Usage:**
110
+ ```bash
111
+ # Pull official Node.js 20 image
112
+ docker pull node:20
113
+
114
+ # Pull specific version
115
+ docker pull postgres:15
116
+ ```
117
+
118
+ **Pro tip:** Always specify a version tag (e.g., `node:20`) instead of using `latest` for reproducibility.
119
+
120
+ }}}
121
+
122
+ }}}
123
+
124
+ ## 🔧 Installation {{{
125
+
126
+ ### macOS (Recommended: Docker Desktop) {{{
127
+
128
+ **Option 1: Download from website**
129
+ 1. Visit: https://www.docker.com/products/docker-desktop
130
+ 2. Download Docker Desktop for Mac
131
+ 3. Install (drag to Applications)
132
+ 4. Launch Docker Desktop
133
+ 5. Wait for Docker icon in menu bar to show "running"
134
+
135
+ **Option 2: Homebrew**
136
+ ```bash
137
+ brew install --cask docker
138
+ ```
139
+
140
+ **Verify installation:**
141
+ ```bash
142
+ docker --version
143
+ # Docker version 24.x.x
144
+
145
+ docker run hello-world
146
+ # Should download and run test container
147
+ ```
148
+
149
+ }}}
150
+
151
+ ### Linux (Ubuntu/Debian) {{{
152
+
153
+ **Install Docker Engine:**
154
+ ```bash
155
+ # Update package index
156
+ sudo apt update
157
+
158
+ # Install required packages
159
+ sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
160
+
161
+ # Add Docker's official GPG key
162
+ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
163
+
164
+ # Add Docker repository
165
+ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
166
+
167
+ # Install Docker
168
+ sudo apt update
169
+ sudo apt install -y docker-ce docker-ce-cli containerd.io
170
+
171
+ # Add your user to docker group (avoid sudo)
172
+ sudo usermod -aG docker $USER
173
+ newgrp docker # Apply group change
174
+
175
+ # Verify
176
+ docker --version
177
+ docker run hello-world
178
+ ```
179
+
180
+ }}}
181
+
182
+ ### Windows (WSL2 + Docker Desktop) {{{
183
+
184
+ **Prerequisites:**
185
+ 1. Windows 10 64-bit: Pro, Enterprise, or Education (Build 19041+)
186
+ 2. Enable WSL2
187
+
188
+ **Steps:**
189
+ 1. Download Docker Desktop from https://www.docker.com/products/docker-desktop
190
+ 2. Run installer
191
+ 3. Ensure "Use WSL 2 instead of Hyper-V" is checked
192
+ 4. Restart computer
193
+ 5. Launch Docker Desktop
194
+
195
+ **Verify in PowerShell or WSL:**
196
+ ```bash
197
+ docker --version
198
+ docker run hello-world
199
+ ```
200
+
201
+ }}}
202
+
203
+ }}}
204
+
205
+ ## 🏃 Your First Container {{{
206
+
207
+ ### Step 1: Run a Pre-built Image {{{
208
+
209
+ **Start with official nginx web server:**
210
+ ```bash
211
+ # Run nginx in detached mode
212
+ docker run -d -p 8080:80 --name my-nginx nginx
213
+
214
+ # Breakdown:
215
+ # -d = detached (runs in background)
216
+ # -p 8080:80 = map port 80 in container to 8080 on your machine
217
+ # --name my-nginx = give container a friendly name
218
+ # nginx = image to use
219
+ ```
220
+
221
+ **Test it:**
222
+ ```bash
223
+ # Open browser to http://localhost:8080
224
+ # You should see "Welcome to nginx!"
225
+ ```
226
+
227
+ **What just happened:**
228
+ 1. Docker checked if `nginx` image exists locally
229
+ 2. Didn't find it → downloaded from Docker Hub
230
+ 3. Created container from image
231
+ 4. Started nginx web server inside container
232
+ 5. Exposed port 80 (inside) as 8080 (outside)
233
+
234
+ }}}
235
+
236
+ ### Step 2: Interact with Running Container {{{
237
+
238
+ **View running containers:**
239
+ ```bash
240
+ docker ps
241
+
242
+ # Output shows:
243
+ # CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
244
+ # abc123... nginx ... ... Up 0.0.0.0:8080->80/tcp my-nginx
245
+ ```
246
+
247
+ **Execute command inside container:**
248
+ ```bash
249
+ # Open bash shell inside container
250
+ docker exec -it my-nginx bash
251
+
252
+ # You're now "inside" the container!
253
+ # Try:
254
+ ls /usr/share/nginx/html # See nginx files
255
+ exit # Leave container (it keeps running)
256
+ ```
257
+
258
+ **View logs:**
259
+ ```bash
260
+ docker logs my-nginx
261
+
262
+ # See nginx access logs
263
+ # Every time you visit http://localhost:8080, new log appears
264
+ ```
265
+
266
+ }}}
267
+
268
+ ### Step 3: Stop and Remove Container {{{
269
+
270
+ **Stop container:**
271
+ ```bash
272
+ docker stop my-nginx
273
+
274
+ # Container stops but still exists
275
+ ```
276
+
277
+ **Verify it's stopped:**
278
+ ```bash
279
+ docker ps # Shows running containers (empty)
280
+ docker ps -a # Shows all containers (includes stopped)
281
+ ```
282
+
283
+ **Remove container:**
284
+ ```bash
285
+ docker rm my-nginx
286
+
287
+ # Now it's completely gone
288
+ ```
289
+
290
+ **One-liner to stop and remove:**
291
+ ```bash
292
+ docker rm -f my-nginx
293
+ # -f forces removal even if running
294
+ ```
295
+
296
+ }}}
297
+
298
+ }}}
299
+
300
+ ## 🏗️ Build Your Own Image {{{
301
+
302
+ ### Step 1: Create a Simple Node.js App {{{
303
+
304
+ **Project structure:**
305
+ ```bash
306
+ my-docker-app/
307
+ ├── Dockerfile
308
+ ├── package.json
309
+ └── server.js
310
+ ```
311
+
312
+ **Create files:**
313
+
314
+ **server.js:**
315
+ ```javascript
316
+ const http = require('http');
317
+
318
+ const server = http.createServer((req, res) => {
319
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
320
+ res.end('Hello from Docker container!\n');
321
+ });
322
+
323
+ server.listen(3000, '0.0.0.0', () => {
324
+ console.log('Server running on port 3000');
325
+ });
326
+ ```
327
+
328
+ **package.json:**
329
+ ```json
330
+ {
331
+ "name": "my-docker-app",
332
+ "version": "1.0.0",
333
+ "main": "server.js",
334
+ "scripts": {
335
+ "start": "node server.js"
336
+ }
337
+ }
338
+ ```
339
+
340
+ }}}
341
+
342
+ ### Step 2: Write a Dockerfile {{{
343
+
344
+ **Dockerfile:**
345
+ ```dockerfile
346
+ # Use official Node.js 20 image as base
347
+ FROM node:20
348
+
349
+ # Set working directory inside container
350
+ WORKDIR /app
351
+
352
+ # Copy package.json (if you had dependencies, would run npm install here)
353
+ COPY package.json .
354
+
355
+ # Copy application code
356
+ COPY server.js .
357
+
358
+ # Expose port 3000 (documentation only, doesn't actually publish)
359
+ EXPOSE 3000
360
+
361
+ # Command to run when container starts
362
+ CMD ["npm", "start"]
363
+ ```
364
+
365
+ **Line-by-line explanation:**
366
+ 1. `FROM node:20` - Start with Node.js 20 environment
367
+ 2. `WORKDIR /app` - All subsequent commands run in /app directory
368
+ 3. `COPY package.json .` - Copy package.json from your machine to container
369
+ 4. `COPY server.js .` - Copy server.js to container
370
+ 5. `EXPOSE 3000` - Document that container listens on port 3000
371
+ 6. `CMD ["npm", "start"]` - Run `npm start` when container starts
372
+
373
+ }}}
374
+
375
+ ### Step 3: Build the Image {{{
376
+
377
+ **Build command:**
378
+ ```bash
379
+ # Navigate to project directory
380
+ cd my-docker-app
381
+
382
+ # Build image
383
+ docker build -t my-app:1.0 .
384
+
385
+ # Breakdown:
386
+ # build = build an image
387
+ # -t my-app:1.0 = tag it as "my-app" version "1.0"
388
+ # . = Dockerfile is in current directory
389
+ ```
390
+
391
+ **Watch the build process:**
392
+ ```
393
+ [+] Building 12.3s (9/9) FINISHED
394
+ => [internal] load build definition from Dockerfile
395
+ => [internal] load .dockerignore
396
+ => [1/4] FROM docker.io/library/node:20
397
+ => [2/4] WORKDIR /app
398
+ => [3/4] COPY package.json .
399
+ => [4/4] COPY server.js .
400
+ => exporting to image
401
+ => => naming to docker.io/library/my-app:1.0
402
+ ```
403
+
404
+ **Verify image exists:**
405
+ ```bash
406
+ docker images
407
+
408
+ # Output:
409
+ # REPOSITORY TAG IMAGE ID CREATED SIZE
410
+ # my-app 1.0 xyz789... 2 minutes ago 1.1GB
411
+ ```
412
+
413
+ }}}
414
+
415
+ ### Step 4: Run Your Custom Image {{{
416
+
417
+ **Start container from your image:**
418
+ ```bash
419
+ docker run -d -p 3000:3000 --name my-container my-app:1.0
420
+ ```
421
+
422
+ **Test it:**
423
+ ```bash
424
+ curl http://localhost:3000
425
+ # Hello from Docker container!
426
+ ```
427
+
428
+ **Check logs:**
429
+ ```bash
430
+ docker logs my-container
431
+ # Server running on port 3000
432
+ ```
433
+
434
+ **Success!** You've built and run your own Docker image. 🎉
435
+
436
+ }}}
437
+
438
+ }}}
439
+
440
+ ## 💾 Working with Data {{{
441
+
442
+ ### The Problem: Containers are Ephemeral {{{
443
+
444
+ **What happens:**
445
+ ```bash
446
+ # Start container
447
+ docker run -d --name test-db postgres
448
+
449
+ # Container writes data to /var/lib/postgresql/data (inside container)
450
+
451
+ # Stop and remove container
452
+ docker rm -f test-db
453
+
454
+ # All data is GONE! 💀
455
+ ```
456
+
457
+ **Why:** Containers are temporary. When removed, everything inside dies.
458
+
459
+ }}}
460
+
461
+ ### Solution: Volumes {{{
462
+
463
+ **Volumes** persist data outside containers.
464
+
465
+ **Named volume (recommended):**
466
+ ```bash
467
+ # Create volume
468
+ docker volume create my-data
469
+
470
+ # Run container with volume mounted
471
+ docker run -d \
472
+ --name test-db \
473
+ -v my-data:/var/lib/postgresql/data \
474
+ postgres
475
+
476
+ # Breakdown:
477
+ # -v my-data:/var/lib/postgresql/data
478
+ # ↑ ↑
479
+ # Volume name Path inside container
480
+ ```
481
+
482
+ **Now data persists:**
483
+ ```bash
484
+ # Remove container
485
+ docker rm -f test-db
486
+
487
+ # Data still exists in volume
488
+ docker volume ls
489
+ # DRIVER VOLUME NAME
490
+ # local my-data
491
+
492
+ # Start new container with same volume
493
+ docker run -d --name test-db-2 -v my-data:/var/lib/postgresql/data postgres
494
+ # All previous data is back!
495
+ ```
496
+
497
+ }}}
498
+
499
+ ### Bind Mounts: Development Workflow {{{
500
+
501
+ **Use case:** Live code changes without rebuilding
502
+
503
+ **Example:**
504
+ ```bash
505
+ # Mount your local code directory into container
506
+ docker run -d \
507
+ --name dev-server \
508
+ -p 3000:3000 \
509
+ -v $(pwd):/app \
510
+ node:20 \
511
+ sh -c "cd /app && npm install && npm start"
512
+
513
+ # Breakdown:
514
+ # -v $(pwd):/app
515
+ # ↑ ↑
516
+ # Your local directory → /app in container
517
+ ```
518
+
519
+ **Now:**
520
+ - Edit `server.js` on your machine
521
+ - Changes appear instantly in container
522
+ - Restart container to see changes (or use nodemon for auto-restart)
523
+
524
+ **Pro tip:** Use `.dockerignore` to exclude `node_modules/` from bind mounts.
525
+
526
+ }}}
527
+
528
+ }}}
529
+
530
+ ## 🌐 Docker Compose: Multi-Container Apps {{{
531
+
532
+ ### The Problem: Complex Multi-Service Apps {{{
533
+
534
+ **Typical web app needs:**
535
+ - Frontend (React/Svelte)
536
+ - Backend (Node.js/Express)
537
+ - Database (PostgreSQL)
538
+ - Cache (Redis)
539
+
540
+ **Without Docker Compose:**
541
+ ```bash
542
+ # Start database
543
+ docker run -d --name db -v db-data:/var/lib/postgresql/data postgres
544
+
545
+ # Start Redis
546
+ docker run -d --name cache redis
547
+
548
+ # Build backend
549
+ docker build -t backend .
550
+ docker run -d --name api -p 5000:5000 --link db --link cache backend
551
+
552
+ # Build frontend
553
+ docker build -t frontend .
554
+ docker run -d --name web -p 3000:3000 --link api frontend
555
+ ```
556
+
557
+ **Too much manual work!** 😫
558
+
559
+ }}}
560
+
561
+ ### Solution: docker-compose.yml {{{
562
+
563
+ **Define all services in one file:**
564
+
565
+ **docker-compose.yml:**
566
+ ```yaml
567
+ version: '3.8'
568
+
569
+ services:
570
+ # PostgreSQL database
571
+ db:
572
+ image: postgres:15
573
+ environment:
574
+ POSTGRES_PASSWORD: secret
575
+ POSTGRES_DB: myapp
576
+ volumes:
577
+ - db-data:/var/lib/postgresql/data
578
+
579
+ # Redis cache
580
+ cache:
581
+ image: redis:7
582
+
583
+ # Backend API
584
+ api:
585
+ build: ./backend
586
+ ports:
587
+ - "5000:5000"
588
+ environment:
589
+ DATABASE_URL: postgres://postgres:secret@db:5432/myapp
590
+ REDIS_URL: redis://cache:6379
591
+ depends_on:
592
+ - db
593
+ - cache
594
+
595
+ # Frontend
596
+ web:
597
+ build: ./frontend
598
+ ports:
599
+ - "3000:3000"
600
+ environment:
601
+ API_URL: http://api:5000
602
+ depends_on:
603
+ - api
604
+
605
+ volumes:
606
+ db-data:
607
+ ```
608
+
609
+ **Start entire stack:**
610
+ ```bash
611
+ docker-compose up -d
612
+
613
+ # All services start in correct order!
614
+ ```
615
+
616
+ **Stop entire stack:**
617
+ ```bash
618
+ docker-compose down
619
+ ```
620
+
621
+ **View logs:**
622
+ ```bash
623
+ # All services
624
+ docker-compose logs -f
625
+
626
+ # Specific service
627
+ docker-compose logs -f api
628
+ ```
629
+
630
+ }}}
631
+
632
+ ### Development Workflow with Compose {{{
633
+
634
+ **docker-compose.yml for development:**
635
+ ```yaml
636
+ version: '3.8'
637
+
638
+ services:
639
+ app:
640
+ build: .
641
+ ports:
642
+ - "3000:3000"
643
+ volumes:
644
+ # Bind mount for live code changes
645
+ - .:/app
646
+ - /app/node_modules # Don't overwrite node_modules
647
+ environment:
648
+ NODE_ENV: development
649
+ command: npm run dev # Use dev script with auto-reload
650
+ ```
651
+
652
+ **Workflow:**
653
+ ```bash
654
+ # Start dev environment
655
+ docker-compose up
656
+
657
+ # Edit code on your machine
658
+ # Changes reflect immediately (with hot reload)
659
+
660
+ # Stop
661
+ Ctrl+C
662
+ docker-compose down
663
+ ```
664
+
665
+ }}}
666
+
667
+ }}}
668
+
669
+ ## 🧪 Practice Exercises {{{
670
+
671
+ ### Exercise 1: Run and Explore {{{
672
+
673
+ **Goal:** Get comfortable with basic container operations
674
+
675
+ **Steps:**
676
+ ```bash
677
+ # 1. Run Ubuntu container interactively
678
+ docker run -it ubuntu bash
679
+
680
+ # 2. Inside container, explore:
681
+ pwd # Where am I? (/root)
682
+ ls # What's here?
683
+ apt update # Update package lists
684
+ apt install -y curl # Install curl
685
+ curl https://api.github.com/users/github | head -n 5
686
+ exit # Leave container
687
+
688
+ # 3. Container is now stopped but exists
689
+ docker ps -a | grep ubuntu
690
+
691
+ # 4. Restart it
692
+ docker start <container-id>
693
+ docker exec -it <container-id> bash
694
+
695
+ # curl still installed! (because container still exists)
696
+
697
+ # 5. Clean up
698
+ exit
699
+ docker rm -f <container-id>
700
+ ```
701
+
702
+ **Key learning:** Stopped containers preserve their state until removed.
703
+
704
+ }}}
705
+
706
+ ### Exercise 2: Build a Python Web App {{{
707
+
708
+ **Goal:** Create and containerize a simple Flask app
709
+
710
+ **Files to create:**
711
+
712
+ **app.py:**
713
+ ```python
714
+ from flask import Flask
715
+ app = Flask(__name__)
716
+
717
+ @app.route('/')
718
+ def hello():
719
+ return "Hello from Python in Docker!"
720
+
721
+ if __name__ == '__main__':
722
+ app.run(host='0.0.0.0', port=5000)
723
+ ```
724
+
725
+ **requirements.txt:**
726
+ ```
727
+ flask==3.0.0
728
+ ```
729
+
730
+ **Dockerfile:**
731
+ ```dockerfile
732
+ FROM python:3.11
733
+
734
+ WORKDIR /app
735
+
736
+ COPY requirements.txt .
737
+ RUN pip install -r requirements.txt
738
+
739
+ COPY app.py .
740
+
741
+ EXPOSE 5000
742
+
743
+ CMD ["python", "app.py"]
744
+ ```
745
+
746
+ **Build and run:**
747
+ ```bash
748
+ docker build -t flask-app .
749
+ docker run -d -p 5000:5000 --name my-flask flask-app
750
+
751
+ # Test
752
+ curl http://localhost:5000
753
+ # Hello from Python in Docker!
754
+
755
+ # Clean up
756
+ docker rm -f my-flask
757
+ ```
758
+
759
+ }}}
760
+
761
+ ### Exercise 3: Persistent Database {{{
762
+
763
+ **Goal:** Run PostgreSQL with persistent data
764
+
765
+ **Steps:**
766
+ ```bash
767
+ # 1. Create volume
768
+ docker volume create pg-data
769
+
770
+ # 2. Start PostgreSQL with volume
771
+ docker run -d \
772
+ --name my-postgres \
773
+ -e POSTGRES_PASSWORD=mypassword \
774
+ -e POSTGRES_DB=testdb \
775
+ -v pg-data:/var/lib/postgresql/data \
776
+ -p 5432:5432 \
777
+ postgres:15
778
+
779
+ # 3. Connect and create table
780
+ docker exec -it my-postgres psql -U postgres -d testdb
781
+
782
+ # Inside psql:
783
+ CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT);
784
+ INSERT INTO users (name) VALUES ('Alice'), ('Bob');
785
+ SELECT * FROM users;
786
+ \q
787
+
788
+ # 4. Destroy container
789
+ docker rm -f my-postgres
790
+
791
+ # 5. Start new container with same volume
792
+ docker run -d \
793
+ --name my-postgres-2 \
794
+ -e POSTGRES_PASSWORD=mypassword \
795
+ -e POSTGRES_DB=testdb \
796
+ -v pg-data:/var/lib/postgresql/data \
797
+ -p 5432:5432 \
798
+ postgres:15
799
+
800
+ # 6. Check data persisted
801
+ docker exec -it my-postgres-2 psql -U postgres -d testdb -c "SELECT * FROM users;"
802
+ # id | name
803
+ # ----+-------
804
+ # 1 | Alice
805
+ # 2 | Bob
806
+
807
+ # Data survived! 🎉
808
+ ```
809
+
810
+ }}}
811
+
812
+ ### Exercise 4: Full Stack with Compose {{{
813
+
814
+ **Goal:** Deploy a full web app stack
815
+
816
+ **Project structure:**
817
+ ```
818
+ fullstack-app/
819
+ ├── docker-compose.yml
820
+ ├── backend/
821
+ │ ├── Dockerfile
822
+ │ └── server.js
823
+ └── frontend/
824
+ ├── Dockerfile
825
+ └── index.html
826
+ ```
827
+
828
+ **docker-compose.yml:**
829
+ ```yaml
830
+ version: '3.8'
831
+
832
+ services:
833
+ backend:
834
+ build: ./backend
835
+ ports:
836
+ - "5000:5000"
837
+ environment:
838
+ DB_HOST: db
839
+ DB_NAME: myapp
840
+ DB_PASSWORD: secret
841
+ depends_on:
842
+ - db
843
+
844
+ frontend:
845
+ build: ./frontend
846
+ ports:
847
+ - "3000:3000"
848
+ depends_on:
849
+ - backend
850
+
851
+ db:
852
+ image: postgres:15
853
+ environment:
854
+ POSTGRES_PASSWORD: secret
855
+ POSTGRES_DB: myapp
856
+ volumes:
857
+ - db-data:/var/lib/postgresql/data
858
+
859
+ volumes:
860
+ db-data:
861
+ ```
862
+
863
+ **backend/Dockerfile:**
864
+ ```dockerfile
865
+ FROM node:20
866
+ WORKDIR /app
867
+ COPY package.json .
868
+ RUN npm install
869
+ COPY . .
870
+ EXPOSE 5000
871
+ CMD ["npm", "start"]
872
+ ```
873
+
874
+ **frontend/Dockerfile:**
875
+ ```dockerfile
876
+ FROM nginx:alpine
877
+ COPY index.html /usr/share/nginx/html/
878
+ EXPOSE 3000
879
+ ```
880
+
881
+ **Run:**
882
+ ```bash
883
+ cd fullstack-app
884
+ docker-compose up -d
885
+
886
+ # All services start together!
887
+ # Frontend: http://localhost:3000
888
+ # Backend: http://localhost:5000
889
+ # Database: localhost:5432
890
+
891
+ # Stop
892
+ docker-compose down
893
+ ```
894
+
895
+ }}}
896
+
897
+ }}}
898
+
899
+ ## 🛡️ Best Practices {{{
900
+
901
+ ### Always Use Specific Image Tags {{{
902
+
903
+ **❌ Bad:**
904
+ ```dockerfile
905
+ FROM node
906
+ FROM python
907
+ ```
908
+
909
+ **✅ Good:**
910
+ ```dockerfile
911
+ FROM node:20-alpine
912
+ FROM python:3.11-slim
913
+ ```
914
+
915
+ **Why:** `latest` changes over time, breaking reproducibility.
916
+
917
+ }}}
918
+
919
+ ### Use .dockerignore {{{
920
+
921
+ **Create .dockerignore:**
922
+ ```
923
+ node_modules/
924
+ npm-debug.log
925
+ .git/
926
+ .env
927
+ *.md
928
+ .DS_Store
929
+ ```
930
+
931
+ **Why:** Faster builds, smaller images, no sensitive data leaked.
932
+
933
+ }}}
934
+
935
+ ### Multi-Stage Builds for Production {{{
936
+
937
+ **Problem:** Build tools bloat final image
938
+
939
+ **Solution:**
940
+ ```dockerfile
941
+ # Build stage
942
+ FROM node:20 AS build
943
+ WORKDIR /app
944
+ COPY package*.json .
945
+ RUN npm install
946
+ COPY . .
947
+ RUN npm run build
948
+
949
+ # Production stage
950
+ FROM node:20-alpine
951
+ WORKDIR /app
952
+ COPY --from=build /app/dist ./dist
953
+ COPY package*.json .
954
+ RUN npm install --production
955
+ CMD ["node", "dist/server.js"]
956
+ ```
957
+
958
+ **Result:** Final image only has production dependencies and built code.
959
+
960
+ }}}
961
+
962
+ ### Don't Run as Root {{{
963
+
964
+ **❌ Bad:**
965
+ ```dockerfile
966
+ FROM node:20
967
+ WORKDIR /app
968
+ COPY . .
969
+ CMD ["npm", "start"] # Runs as root
970
+ ```
971
+
972
+ **✅ Good:**
973
+ ```dockerfile
974
+ FROM node:20
975
+ WORKDIR /app
976
+ COPY . .
977
+ RUN chown -R node:node /app
978
+ USER node # Switch to non-root user
979
+ CMD ["npm", "start"]
980
+ ```
981
+
982
+ **Why:** Security. Root exploits can escape container.
983
+
984
+ }}}
985
+
986
+ ### Clean Up After Yourself {{{
987
+
988
+ **Remove unused containers:**
989
+ ```bash
990
+ docker container prune
991
+ ```
992
+
993
+ **Remove unused images:**
994
+ ```bash
995
+ docker image prune -a
996
+ ```
997
+
998
+ **Remove unused volumes:**
999
+ ```bash
1000
+ docker volume prune
1001
+ ```
1002
+
1003
+ **Nuclear option (remove everything):**
1004
+ ```bash
1005
+ docker system prune -a --volumes
1006
+ ```
1007
+
1008
+ }}}
1009
+
1010
+ ### Use Health Checks {{{
1011
+
1012
+ **In Dockerfile:**
1013
+ ```dockerfile
1014
+ FROM node:20
1015
+ WORKDIR /app
1016
+ COPY . .
1017
+ EXPOSE 3000
1018
+
1019
+ # Health check
1020
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
1021
+ CMD curl -f http://localhost:3000/health || exit 1
1022
+
1023
+ CMD ["npm", "start"]
1024
+ ```
1025
+
1026
+ **In docker-compose.yml:**
1027
+ ```yaml
1028
+ services:
1029
+ api:
1030
+ build: .
1031
+ healthcheck:
1032
+ test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
1033
+ interval: 30s
1034
+ timeout: 3s
1035
+ retries: 3
1036
+ start_period: 5s
1037
+ ```
1038
+
1039
+ **Why:** Container might run but app is broken. Health checks detect this.
1040
+
1041
+ }}}
1042
+
1043
+ }}}
1044
+
1045
+ ## 📚 Quick Reference {{{
1046
+
1047
+ ### Essential Docker Commands {{{
1048
+
1049
+ ```bash
1050
+ # Images
1051
+ docker images # List images
1052
+ docker pull image:tag # Download image
1053
+ docker build -t name:tag . # Build image from Dockerfile
1054
+ docker rmi image-id # Remove image
1055
+
1056
+ # Containers
1057
+ docker ps # List running containers
1058
+ docker ps -a # List all containers
1059
+ docker run -d image # Run container in background
1060
+ docker run -it image bash # Run interactively
1061
+ docker exec -it container bash # Execute command in running container
1062
+ docker logs container # View logs
1063
+ docker stop container # Stop container
1064
+ docker start container # Start stopped container
1065
+ docker rm container # Remove container
1066
+ docker rm -f container # Force remove running container
1067
+
1068
+ # Volumes
1069
+ docker volume ls # List volumes
1070
+ docker volume create name # Create volume
1071
+ docker volume rm name # Remove volume
1072
+ docker volume inspect name # View volume details
1073
+
1074
+ # System
1075
+ docker system df # Show disk usage
1076
+ docker system prune # Remove unused data
1077
+ docker version # Show Docker version
1078
+ docker info # System-wide info
1079
+
1080
+ # Compose
1081
+ docker-compose up # Start services
1082
+ docker-compose up -d # Start in background
1083
+ docker-compose down # Stop and remove services
1084
+ docker-compose ps # List services
1085
+ docker-compose logs -f # Follow logs
1086
+ docker-compose build # Build/rebuild services
1087
+ ```
1088
+
1089
+ }}}
1090
+
1091
+ ### Common docker run Flags {{{
1092
+
1093
+ ```bash
1094
+ -d # Detached (background)
1095
+ -it # Interactive with TTY
1096
+ -p 8080:80 # Port mapping (host:container)
1097
+ -v name:/path # Named volume mount
1098
+ -v $(pwd):/app # Bind mount (current dir → /app)
1099
+ -e KEY=value # Environment variable
1100
+ --name my-container # Container name
1101
+ --rm # Auto-remove when stopped
1102
+ --network my-net # Connect to network
1103
+ --restart unless-stopped # Restart policy
1104
+ ```
1105
+
1106
+ }}}
1107
+
1108
+ ### Dockerfile Instructions {{{
1109
+
1110
+ ```dockerfile
1111
+ FROM image:tag # Base image
1112
+ WORKDIR /app # Set working directory
1113
+ COPY source dest # Copy files
1114
+ ADD source dest # Copy + extract archives
1115
+ RUN command # Execute command during build
1116
+ CMD ["cmd", "arg"] # Default command when container starts
1117
+ ENTRYPOINT ["cmd"] # Main command (CMD becomes args)
1118
+ EXPOSE 3000 # Document port (doesn't publish)
1119
+ ENV KEY=value # Environment variable
1120
+ USER username # Switch user
1121
+ VOLUME /data # Define volume mount point
1122
+ HEALTHCHECK CMD curl... # Health check command
1123
+ ```
1124
+
1125
+ }}}
1126
+
1127
+ ### docker-compose.yml Structure {{{
1128
+
1129
+ ```yaml
1130
+ version: '3.8'
1131
+
1132
+ services:
1133
+ service-name:
1134
+ build: ./path # Build from Dockerfile
1135
+ image: image:tag # Use existing image
1136
+ ports:
1137
+ - "8080:80" # Port mapping
1138
+ volumes:
1139
+ - vol-name:/path # Named volume
1140
+ - ./local:/container # Bind mount
1141
+ environment:
1142
+ KEY: value # Environment variables
1143
+ depends_on:
1144
+ - other-service # Start order
1145
+ networks:
1146
+ - my-network # Custom network
1147
+ restart: unless-stopped # Restart policy
1148
+ healthcheck:
1149
+ test: ["CMD", "curl", "-f", "http://localhost"]
1150
+ interval: 30s
1151
+
1152
+ volumes:
1153
+ vol-name:
1154
+
1155
+ networks:
1156
+ my-network:
1157
+ ```
1158
+
1159
+ }}}
1160
+
1161
+ }}}
1162
+
1163
+ ## 🐛 Troubleshooting {{{
1164
+
1165
+ ### Container Exits Immediately {{{
1166
+
1167
+ **Check logs:**
1168
+ ```bash
1169
+ docker logs container-name
1170
+ ```
1171
+
1172
+ **Common causes:**
1173
+ - Application crashed
1174
+ - Missing environment variables
1175
+ - Port already in use
1176
+
1177
+ **Debug interactively:**
1178
+ ```bash
1179
+ # Override CMD to keep container alive
1180
+ docker run -it image-name sh
1181
+ ```
1182
+
1183
+ }}}
1184
+
1185
+ ### Can't Connect to Container {{{
1186
+
1187
+ **Check port mapping:**
1188
+ ```bash
1189
+ docker ps
1190
+ # Look at PORTS column: 0.0.0.0:8080->80/tcp
1191
+ ```
1192
+
1193
+ **Check if app is listening on 0.0.0.0:**
1194
+ ```bash
1195
+ # ❌ Bad: app listens on localhost
1196
+ app.listen(3000, 'localhost')
1197
+
1198
+ # ✅ Good: app listens on all interfaces
1199
+ app.listen(3000, '0.0.0.0')
1200
+ ```
1201
+
1202
+ **Verify with curl:**
1203
+ ```bash
1204
+ # From host
1205
+ curl http://localhost:8080
1206
+
1207
+ # Inside container
1208
+ docker exec -it container-name curl http://localhost:80
1209
+ ```
1210
+
1211
+ }}}
1212
+
1213
+ ### Volume Data Not Persisting {{{
1214
+
1215
+ **Check volume is mounted:**
1216
+ ```bash
1217
+ docker inspect container-name | grep Mounts -A 10
1218
+ ```
1219
+
1220
+ **Verify volume exists:**
1221
+ ```bash
1222
+ docker volume ls
1223
+ docker volume inspect volume-name
1224
+ ```
1225
+
1226
+ **Common mistake:**
1227
+ ```bash
1228
+ # ❌ No volume specified - data dies with container
1229
+ docker run -d postgres
1230
+
1231
+ # ✅ Volume specified - data persists
1232
+ docker run -d -v pg-data:/var/lib/postgresql/data postgres
1233
+ ```
1234
+
1235
+ }}}
1236
+
1237
+ ### Out of Disk Space {{{
1238
+
1239
+ **Check Docker disk usage:**
1240
+ ```bash
1241
+ docker system df
1242
+
1243
+ # TYPE TOTAL ACTIVE SIZE RECLAIMABLE
1244
+ # Images 15 5 4.2GB 2.1GB (50%)
1245
+ # Containers 20 3 100MB 80MB (80%)
1246
+ # Volumes 10 3 1.5GB 800MB (53%)
1247
+ ```
1248
+
1249
+ **Clean up:**
1250
+ ```bash
1251
+ # Remove stopped containers
1252
+ docker container prune
1253
+
1254
+ # Remove unused images
1255
+ docker image prune -a
1256
+
1257
+ # Remove unused volumes
1258
+ docker volume prune
1259
+
1260
+ # Everything
1261
+ docker system prune -a --volumes
1262
+ ```
1263
+
1264
+ }}}
1265
+
1266
+ ### Build Fails with "No Space Left" {{{
1267
+
1268
+ **Check build cache:**
1269
+ ```bash
1270
+ docker builder prune
1271
+ ```
1272
+
1273
+ **Free up Docker disk:**
1274
+ ```bash
1275
+ # macOS: Docker Desktop → Settings → Resources → Disk Image Size
1276
+ # Linux: Clean up /var/lib/docker/
1277
+ ```
1278
+
1279
+ }}}
1280
+
1281
+ ### Container Can't Resolve DNS {{{
1282
+
1283
+ **Test DNS inside container:**
1284
+ ```bash
1285
+ docker exec -it container-name ping google.com
1286
+ ```
1287
+
1288
+ **Fix: Specify DNS servers:**
1289
+ ```bash
1290
+ docker run -d --dns 8.8.8.8 --dns 8.8.4.4 image-name
1291
+ ```
1292
+
1293
+ **In docker-compose.yml:**
1294
+ ```yaml
1295
+ services:
1296
+ app:
1297
+ image: myapp
1298
+ dns:
1299
+ - 8.8.8.8
1300
+ - 8.8.4.4
1301
+ ```
1302
+
1303
+ }}}
1304
+
1305
+ }}}
1306
+
1307
+ ## 🎓 Advanced Topics {{{
1308
+
1309
+ ### Networking Modes {{{
1310
+
1311
+ **Bridge (default):**
1312
+ ```bash
1313
+ # Containers can talk to each other by name
1314
+ docker network create my-network
1315
+ docker run -d --network my-network --name api backend-image
1316
+ docker run -d --network my-network frontend-image
1317
+ # Frontend can reach backend at http://api:5000
1318
+ ```
1319
+
1320
+ **Host (share host network):**
1321
+ ```bash
1322
+ docker run -d --network host nginx
1323
+ # Container uses host's network directly
1324
+ # No port mapping needed
1325
+ # Less isolation
1326
+ ```
1327
+
1328
+ **None (no networking):**
1329
+ ```bash
1330
+ docker run -d --network none my-image
1331
+ # Completely isolated
1332
+ ```
1333
+
1334
+ }}}
1335
+
1336
+ ### Docker Secrets (Production) {{{
1337
+
1338
+ **For Swarm/Kubernetes:**
1339
+ ```bash
1340
+ # Create secret
1341
+ echo "mysecretpassword" | docker secret create db-password -
1342
+
1343
+ # Use in service
1344
+ docker service create \
1345
+ --name db \
1346
+ --secret db-password \
1347
+ postgres
1348
+ ```
1349
+
1350
+ **Alternative for dev:**
1351
+ ```bash
1352
+ # Use environment file
1353
+ docker run -d --env-file .env my-app
1354
+ ```
1355
+
1356
+ }}}
1357
+
1358
+ ### Multi-Architecture Builds {{{
1359
+
1360
+ **Build for ARM and x86:**
1361
+ ```bash
1362
+ # Setup buildx
1363
+ docker buildx create --use
1364
+
1365
+ # Build for multiple platforms
1366
+ docker buildx build \
1367
+ --platform linux/amd64,linux/arm64 \
1368
+ -t myapp:latest \
1369
+ --push \
1370
+ .
1371
+ ```
1372
+
1373
+ **Why:** Deploy same image to AWS (x86) and Raspberry Pi (ARM).
1374
+
1375
+ }}}
1376
+
1377
+ }}}
1378
+
1379
+ ## 💡 Pro Tips {{{
1380
+
1381
+ 1. **Use Alpine images for smaller sizes:**
1382
+ ```dockerfile
1383
+ FROM node:20-alpine # ~100MB instead of ~900MB
1384
+ ```
1385
+
1386
+ 2. **Layer caching optimization:**
1387
+ ```dockerfile
1388
+ # Copy package.json first (changes less often)
1389
+ COPY package*.json .
1390
+ RUN npm install
1391
+ # Then copy code (changes frequently)
1392
+ COPY . .
1393
+ ```
1394
+
1395
+ 3. **Combine RUN commands to reduce layers:**
1396
+ ```dockerfile
1397
+ # ❌ Creates 3 layers
1398
+ RUN apt update
1399
+ RUN apt install -y curl
1400
+ RUN apt clean
1401
+
1402
+ # ✅ Creates 1 layer
1403
+ RUN apt update && \
1404
+ apt install -y curl && \
1405
+ apt clean
1406
+ ```
1407
+
1408
+ 4. **Use docker-compose override files:**
1409
+ ```bash
1410
+ # docker-compose.yml - base config
1411
+ # docker-compose.override.yml - local dev overrides
1412
+ # docker-compose.prod.yml - production overrides
1413
+
1414
+ # Dev (auto-applies override)
1415
+ docker-compose up
1416
+
1417
+ # Production
1418
+ docker-compose -f docker-compose.yml -f docker-compose.prod.yml up
1419
+ ```
1420
+
1421
+ 5. **Shell into Alpine containers:**
1422
+ ```bash
1423
+ # Alpine uses 'sh' not 'bash'
1424
+ docker exec -it container-name sh
1425
+ ```
1426
+
1427
+ 6. **Copy files out of container:**
1428
+ ```bash
1429
+ docker cp container-name:/app/logs/error.log ./local-path/
1430
+ ```
1431
+
1432
+ 7. **Monitor resource usage:**
1433
+ ```bash
1434
+ docker stats # Live CPU/Memory/Network usage
1435
+ ```
1436
+
1437
+ 8. **Save and load images:**
1438
+ ```bash
1439
+ # Save image to tar
1440
+ docker save my-app:latest > my-app.tar
1441
+
1442
+ # Load on another machine
1443
+ docker load < my-app.tar
1444
+ ```
1445
+
1446
+ }}}
1447
+
1448
+ ## 🚀 Next Steps {{{
1449
+
1450
+ **Master these progressively:**
1451
+
1452
+ 1. ✅ **Level 1:** Run pre-built containers (`docker run`)
1453
+ 2. ✅ **Level 2:** Build simple images (`Dockerfile`)
1454
+ 3. ✅ **Level 3:** Manage data with volumes
1455
+ 4. 🎯 **Level 4:** Multi-container apps (`docker-compose`)
1456
+ 5. 🎯 **Level 5:** Optimize images (multi-stage builds, Alpine)
1457
+ 6. 🎯 **Level 6:** Production deployments (CI/CD, orchestration)
1458
+
1459
+ **Related skills:**
1460
+ - **Docker Compose** - Multi-container orchestration
1461
+ - **Kubernetes** - Production container orchestration
1462
+ - **CI/CD Pipelines** - Automated Docker builds and deployments
1463
+ - **Container Security** - Scanning, secrets management
1464
+ - **Docker Swarm** - Native Docker clustering
1465
+
1466
+ **Resources:**
1467
+ - Docker Hub: https://hub.docker.com (explore official images)
1468
+ - Docker Docs: https://docs.docker.com
1469
+ - Play with Docker: https://labs.play-with-docker.com (browser-based playground)
1470
+
1471
+ }}}
1472
+
1473
+ ---
1474
+
1475
+ **Remember:** Docker isn't scary - it's just a way to package your app with everything it needs. Start simple, experiment often, and soon "it works on my machine" becomes "it works everywhere!" 🐳