@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.
- package/bin/creta.js +37 -1
- package/lib/data/command-help/aws-ec2.js +34 -0
- package/lib/data/command-help/grep.js +72 -0
- package/lib/data/command-help/index.js +9 -1
- package/lib/executors/CommandHelpExecutor.js +6 -1
- package/lib/executors/ExercisesExecutor.js +8 -0
- package/lib/exercises/.claude/settings.local.json +12 -0
- package/lib/exercises/01-developing-muscle-for-nvim.md +528 -0
- package/lib/exercises/{iterm2-pane-navigation.md → 02-iterm2-pane-navigation.md} +1 -1
- package/lib/exercises/05-svelte-first-steps.md +1340 -0
- package/lib/exercises/{curl-and-pipes.md → 06-curl-and-pipes.md} +187 -72
- package/lib/exercises/07-claude-api-first-steps.md +855 -0
- package/lib/exercises/08-playwright-svelte-guide.md +1384 -0
- package/lib/exercises/09-docker-first-steps.md +1475 -0
- package/lib/exercises/{railway-deployment.md → 10-railway-deployment.md} +1 -0
- package/lib/exercises/{aws-billing-detective.md → 11-aws-billing-detective.md} +215 -35
- package/lib/exercises/12-install-skills.md +755 -0
- package/lib/exercises/README.md +180 -0
- package/lib/exercises/utils/booklet-2up.js +133 -0
- package/lib/exercises/utils/booklet-manual-duplex.js +159 -0
- package/lib/exercises/utils/booklet-simple.js +136 -0
- package/lib/exercises/utils/create-booklet.js +116 -0
- package/lib/scripts/aws-ec2-all.sh +58 -0
- package/package.json +3 -2
- /package/lib/exercises/{git-stash-workflow.md → 03-git-stash-workflow.md} +0 -0
- /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!" 🐳
|